├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── demo_project ├── .gitlab-ci.yml ├── Dockerfile ├── README.md ├── django_svelte_demo │ ├── django_svelte_demo │ │ ├── __init__.py │ │ ├── api │ │ │ ├── urls.py │ │ │ └── views.py │ │ ├── asgi.py │ │ ├── settings │ │ │ ├── __init__.py │ │ │ ├── base.py │ │ │ └── test.py │ │ ├── templates │ │ │ ├── base.html │ │ │ ├── basic.html │ │ │ ├── svelte_base.html │ │ │ └── svelte_component.html │ │ ├── urls.py │ │ ├── views.py │ │ └── wsgi.py │ ├── manage.py │ └── static │ │ ├── Hashed-asdfsafd.js │ │ ├── Plain.js │ │ ├── Something.css │ │ ├── Something.js │ │ ├── fake_manifest.json │ │ ├── real_file.js │ │ └── realistic_manifest.json ├── docker-compose.yml ├── entrypoint.sh ├── makefile ├── requirements.txt └── svelte │ ├── .gitignore │ ├── README.md │ ├── jsconfig.json │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── App.js │ ├── AuthComponent.js │ ├── PostComponent.js │ ├── components │ │ ├── App.svelte │ │ ├── AuthComponent.svelte │ │ └── PostComponent.svelte │ ├── lib │ │ └── api.js │ └── vite-env.d.ts │ ├── svelte.config.js │ └── vite.config.js ├── django_svelte ├── __init__.py ├── apps.py ├── conf.py ├── models.py ├── templates │ └── django_svelte │ │ ├── display_svelte.html │ │ └── display_svelte_css.html ├── templatetags │ ├── __init__.py │ └── django_svelte.py └── views.py ├── pyproject.toml ├── setup.py ├── tests ├── conftest.py ├── test_conf.py ├── test_django_svelte.py └── test_views.py └── tox.ini /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | name: Build distribution 📦 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Set up Python 13 | uses: actions/setup-python@v5 14 | with: 15 | python-version: "3.x" 16 | cache: pip 17 | cache-dependency-path: | 18 | setup.py 19 | tox.ini 20 | - name: Install pypa/build 21 | run: >- 22 | python3 -m 23 | pip install 24 | build 25 | --user 26 | - name: Build a binary wheel and a source tarball 27 | run: python3 -m build 28 | - name: Store the distribution packages 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: python-package-distributions 32 | path: dist/ 33 | 34 | publish-to-pypi: 35 | name: >- 36 | Publish Python 🐍 distribution 📦 to PyPI 37 | if: startsWith(github.ref, 'refs/tags/') 38 | needs: 39 | - build 40 | runs-on: ubuntu-latest 41 | environment: 42 | name: release 43 | url: https://pypi.org/p/django-svelte 44 | permissions: 45 | id-token: write 46 | 47 | steps: 48 | - name: Download all the dists 49 | uses: actions/download-artifact@v4 50 | with: 51 | name: python-package-distributions 52 | path: dist/ 53 | - name: Publish distribution 📦 to PyPI 54 | uses: pypa/gh-action-pypi-publish@release/v1 55 | 56 | github-release: 57 | name: >- 58 | Sign the Python 🐍 distribution 📦 with Sigstore 59 | and upload them to GitHub Release 60 | needs: 61 | - publish-to-pypi 62 | runs-on: ubuntu-latest 63 | 64 | permissions: 65 | contents: write # IMPORTANT: mandatory for making GitHub Releases 66 | id-token: write # IMPORTANT: mandatory for sigstore 67 | 68 | steps: 69 | - name: Download all the dists 70 | uses: actions/download-artifact@v4 71 | with: 72 | name: python-package-distributions 73 | path: dist/ 74 | - name: Sign the dists with Sigstore 75 | uses: sigstore/gh-action-sigstore-python@v1.2.3 76 | with: 77 | inputs: >- 78 | ./dist/*.tar.gz 79 | ./dist/*.whl 80 | - name: Create GitHub Release 81 | env: 82 | GITHUB_TOKEN: ${{ github.token }} 83 | run: >- 84 | gh release create 85 | '${{ github.ref_name }}' 86 | --repo '${{ github.repository }}' 87 | --notes "" 88 | - name: Upload artifact signatures to GitHub Release 89 | env: 90 | GITHUB_TOKEN: ${{ github.token }} 91 | # Upload to GitHub Release using the `gh` CLI. 92 | # `dist/` contains the built packages, and the 93 | # sigstore-produced signatures and certificates. 94 | run: >- 95 | gh release upload 96 | '${{ github.ref_name }}' dist/** 97 | --repo '${{ github.repository }}' 98 | 99 | # publish-to-testpypi: 100 | # name: Publish Python 🐍 distribution 📦 to TestPyPI 101 | # needs: 102 | # - build 103 | # runs-on: ubuntu-latest 104 | 105 | # environment: 106 | # name: testpypi 107 | # url: https://test.pypi.org/p/django-svelte 108 | 109 | # permissions: 110 | # id-token: write # IMPORTANT: mandatory for trusted publishing 111 | 112 | # steps: 113 | # - name: Download all the dists 114 | # uses: actions/download-artifact@v4 115 | # with: 116 | # name: python-package-distributions 117 | # path: dist/ 118 | # - name: Publish distribution 📦 to TestPyPI 119 | # uses: pypa/gh-action-pypi-publish@release/v1 120 | # with: 121 | # repository-url: https://test.pypi.org/legacy/ -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: ['3.8', '3.9', '3.10', '3.11'] 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | cache: pip 21 | cache-dependency-path: | 22 | setup.py 23 | tox.ini 24 | 25 | - name: Install dependencies 26 | run: | 27 | python -m pip install --upgrade pip 28 | python -m pip install tox tox-gh-actions 29 | - name: Test with tox 30 | run: tox -v 31 | - name: Upload coverage 32 | uses: codecov/codecov-action@v4 33 | with: 34 | token: ${{ secrets.CODECOV_TOKEN }} 35 | name: Python ${{ matrix.python-version }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__/* 2 | **/node_modules/* 3 | *.pyc 4 | *.sqlite3 5 | *.log 6 | *.final 7 | *.egg-info/* 8 | 9 | .coverage 10 | coverage.xml 11 | .tox 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 0.2.1 4 | ### Added 5 | * Recursive lookup of CSS bundles in vite manifest. 6 | * Support for displaying several CSS files per component. 7 | 8 | 9 | ## Version 0.2.0 10 | ### Added 11 | * This changelog! 12 | * Demo project into this package illustrating working Svelte 4 deployment. 13 | * Unittests! 14 | * Setting `DJANGO_SVELTE_VITE_MANIFEST_PATH` which locates the Vite build manifest so that hashed files can be looked up. 15 | * Setting `DJANGO_SVELTE_ENTRYPOINT_PREFIX` with default (`src/`) to indicate the directory of the Svelte entrypoint files (formerly referred to like `main-.js`, now format like `.js` is preferred) 16 | * Setting `DJANGO_SVELTE_VITE_ASSETSDIR` with default (`assets/`) to indicate the directory where Vite will output the various bundles for reference. 17 | * Support for looking up hashed file names from the Vite manifest. 18 | ### Changed 19 | * Handling of CSS to dedicated templatetag which can appropriately be invoked in the `head` of the template. 20 | * Documentation to speak more directly to handling CSS in the two most common use cases. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2021 Paul Stiverson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include MANIFEST.in 3 | include README.md 4 | recursive-include django_svelte/templates * 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEST_PATH = "" 2 | 3 | migrations: 4 | cd demo_project/django_svelte_demo && python manage.py makemigrations 5 | init: 6 | cd demo_project/django_svelte_demo && python manage.py migrate 7 | run: 8 | cd demo_project/django_svelte_demo && python manage.py runserver 0.0.0.0:8000 9 | lint: 10 | black django_svelte 11 | isort django_svelte 12 | cd django_svelte && pflake8 13 | test: 14 | DJANGO_SETTINGS_MODULE=django_svelte_demo.settings.test pytest --cov=django_svelte --cov-report term-missing tests/$(TEST_PATH) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Svelte 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/django-svelte?color=156741&logo=python&logoColor=ffffff&style=for-the-badge)](https://pypi.org/project/django-svelte/) 4 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/thismatters/django-svelte/test.yml?branch=main&color=156741&label=CI&logo=github&style=for-the-badge)](https://github.com/thismatters/django-svelte/actions) 5 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-svelte?color=156741&logo=python&logoColor=white&style=for-the-badge)](https://pypi.org/project/django-svelte/) 6 | [![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-svelte?color=156741&logo=django&logoColor=ffffff&style=for-the-badge)](https://pypi.org/project/django-svelte/) 7 | [![Codecov](https://img.shields.io/codecov/c/github/thismatters/django-svelte?color=156741&logo=codecov&logoColor=ffffff&style=for-the-badge)](https://codecov.io/gh/thismatters/django-svelte) 8 | 9 | 10 | Incorporate a Svelte frontend into a Django site with minimal impact to deployment strategy and authentication story. 11 | 12 | ## Scope 13 | 14 | This package mainly consists of a templatetags which facilitate the import of JS/CSS bundles created by Svelte/Vite/Node.js into your template. For this package to be useful you will also need the Svelte/Vite/Node.js which produces the JS/CSS bundle. The [svelte demo project](demo_project/svelte/) walks you through setting this up from scratch. 15 | 16 | ## Compatibility 17 | 18 | This package was originally written during the Svelte 3 days and was bundled by Rollup. Version 0.1.7 and prior were devoted to supporting this stack. Newer versions of this package _ought_ to support that old stack, but they are untested. 19 | 20 | Version 0.2.0 and on support Svelte 4 bundled by Vite. 21 | 22 | ## Installation 23 | 24 | Install the package: 25 | 26 | ```sh 27 | pip install django-svelte 28 | ``` 29 | 30 | Add to INSTALLED_APPS: 31 | 32 | ```py 33 | INSTALLED_APPS = ( 34 | ... 35 | "django_svelte", 36 | ... 37 | ) 38 | ``` 39 | 40 | Tell Django where your Svelte JS/CSS bundles will be found (this guide assumes that you place your svelte directory beside your django project directory as shown in `demo_project`): 41 | 42 | ```py 43 | STATICFILES_DIRS = [ 44 | # ... 45 | BASE_DIR.parent / "svelte" / "dist" / "assets", 46 | ] 47 | ``` 48 | 49 | If you are using Vite as the bundler for Svelte then you'll also want to tell Django where to find the Vite manifest so that components can be located by hash (Vite does not generate a manifest by default, see the [`build.manifest` option](https://vitejs.dev/config/build-options.html#build-manifest)): 50 | 51 | ```py 52 | DJANGO_SVELTE_VITE_MANIFEST_PATH = BASE_DIR.parent / "svelte" / "dist" / ".vite" / "manifest.json" , 53 | ``` 54 | 55 | ## Usage 56 | 57 | To use a Svelte component within your Django template load the `django_svelte` templatetag library and use the `display_svelte` and `display_svelte_css` templatetag: 58 | 59 | ``` 60 | {% load django_svelte %} 61 | 62 | ... 63 | 64 | {% display_svelte_css "MySpecialComponent" %} 65 | 66 | {% display_svelte "MySpecialComponent" %} 67 | ``` 68 | 69 | You can optionally pass some context (specifically a JSON serializable `dict`) to the component: 70 | 71 | ``` 72 | {% display_svelte "MySpecialComponent" component_props %} 73 | ``` 74 | 75 | Versions prior to 0.2.0 required using `.svelte` at the end of the component name, e.g. 76 | 77 | ``` 78 | {% display_svelte "MySpecialComponent.svelte" component_props %} 79 | ``` 80 | 81 | This is no longer required. 82 | 83 | ## Lets talk about CSS 84 | 85 | There are many ways that Svelte generates CSS bundles. 86 | Because of the flexibility of CSS many arrangements are possible, but they'll require you to update your Django templates accordingly. 87 | Django templates are highly variable per project, so it is difficult to provide one-sized-fits-all advice in this arena. 88 | It is likely that one or both of the use cases described below will work for your project. 89 | 90 | If your project follows the standard `base.html -> site_base.html -> .html` paradigm then you might find it convenient to also provide a `svelte_base.html` which includes a block (namely `svelte_styles`) in the `head` which will allow individual page templates to inject CSS where it ought to exist: 91 | 92 | ### Monolithic CSS 93 | 94 | If your project collects all its CSS into a single stylesheet, then you can make that CSS file accessible as a staticfile and use a regular `link` to incorporate it into your template as shown below. It may be convenient to put this import for your stylesheet in your base template to limit boilerplate. 95 | 96 | ``` 97 | {% load static %} 98 | {% load django_svelte %} 99 | 100 | 101 | 102 | ... 103 | {% block svelte_styles %} 104 | {{ block.super }} 105 | 108 | ... 109 | {% display_svelte "Component" %} 110 | 111 | 112 | ``` 113 | 114 | See also the Vite [build option `cssCodeSplit`](https://vitejs.dev/config/build-options.html#build-csscodesplit) 115 | 116 | ### Per Component CSS 117 | 118 | If you include `style` tags in your individual Svelte components then you'll want to include the per component stylesheets when you display your component: 119 | 120 | ``` 121 | {% extends "svelte_base.html" %} 122 | {% load django_svelte %} 123 | 124 | {% block svelte_styles %} 125 | {{ block.super }} 126 | {% display_svelte_css "Component" %} 127 | {% endblock %} 128 | 129 | 130 | {% block main_content %} 131 | {# or whatever... #} 132 | {% display_svelte "Component" %} 133 | {% endblock %} 134 | ``` 135 | 136 | Note the use of `display_svelte_css` to specifically inject the CSS for the named component within the `svelte_styles` block. 137 | 138 | ## Recommended Patterns 139 | 140 | In addition to the `svelte_base.html` template described above, there are some other tips that have proved effective when using this package. Basic implementations of the ideas described below are included with the demo project, but due to the extreme variability of django templates in practice you'll have to provide your own implementations. 141 | 142 | ### Class-based Wrapper View 143 | 144 | If you have many components that each get loaded as the main content of their own pages then a reusable class-based view can reduce boilerplate. Subclass the `SvelteTemplateView` class provided in `views.py` to utilize this pattern; be sure to provide your own `get_svelte_props` method so that your component will have data! See the [sample implementation](demo_project/django_svelte_demo/django_svelte_demo/views.py) 145 | 146 | ### Default Svelte Template 147 | 148 | Once you have `svelte_base.html` in place, a subsequent template like `svelte_component.html` is a convenient template for loading in a single component. If you're using the class based view approach describe above then this template should include a `{{ page_title }}` as well as the use of `{% display_svelte_css component_name %}` in the `head` of the template, and `{% display_svelte component_name %}` in its body. See the [sample implementation](demo_project/django_svelte_demo/django_svelte_demo/templates/svelte_component.html) in the demo project. 149 | 150 | ## What about Svelte!? 151 | 152 | The Svelte side of things is demonstrated in the [Svelte demo project](demo_project/svelte/) which shows how a default Vite+Svelte project can be altered to output JS/CSS bundles useful for this package. It is configured to output JS/CSS bundles for several different components which can be imported independently. 153 | 154 | ### New Svelte, who dis? 155 | 156 | The newer versions of Svelte are defaulting to SvelteKit, because routing is important stuff. If you're here then you're probably using Django for your routing, so there is no need for SvelteKit. Fortunately, Svelte (sans-Kit) is still available [by installing `vite`](https://svelte.dev/docs/introduction#start-a-new-project-alternatives-to-sveltekit). 157 | 158 | The config for outputting multiple bundles got a bit easier from prior versions, and I changed the conventions around entrypoints in the demo project. What used to be called `main-.js` is now called `.js` and referenced as such in the `build.rollupOptions.input` array. 159 | 160 | Vite, by default and with much difficulty to alter, hashes files and appends the hash to the filename; probably a good practice, but kind of annoying for integrated packages. Instruct Vite to output a manifest by setting the [`build.manifest` option](https://vitejs.dev/config/build-options.html#build-manifest). The complementary setting `DJANGO_SVELTE_VITE_MANIFEST_PATH` allows you to specify where in the file tree the manifest file lives. After setting this you may go about using the component name (sans hash) in the various templatetags. Alternately, it is possible to prevent Vite from putting hashes into its output filenames but you're on your own for that! 161 | 162 | **NOTE:** This manifest arrangement _will_ get bothersome during dev as you have to cause Django to reload the manifest by saving a Django file each time you save a Svelte file (and the bundle hash changes). If **anybody** knows a way to make this more manageable, please please tell me! 163 | 164 | Depending on how you have organized your Svelte project you may have specify where your Svelte component "Entrypoints" are located (Entrypoints are the thin javascript files that first import the Svelte component, see [App.js](demo_project/svelte/src/App.js). In the demo project, the entrypoints are directly in the `svelte/src/` directory. If you have put your entrypoints somewhere else then you'll need to say where in the `DJANGO_SVELTE_ENTRYPOIN_PREFIX` setting (value needs to be relative to the `svelte` directory and do include the trailing slash, e.g. the default value `src/`). It is necessary that your entrypoints follow the naming convention `.js`; if your top-level Svelte component is called `MySpecialComponent.svelte` then the entrypoint needs to be called `MySpecialComponent.js` or this package will not function correctly! 165 | 166 | It may be necessary to specify the `DJANGO_SVELTE_VITE_ASSETSDIR` setting. It must match the value used in `vite.config.js` under the key `build.assetsDir`. The default case (value `assets/` ) is covered. 167 | 168 | ## Devops concerns 169 | 170 | So, this package isn't magic. For this to work you will need to have Node.js _somewhere_ in the mix. Fortunately, you won't need Node.js running in your production environment, but you will need it somewhere in your CI pipeline and probably in your dev environment. For a practical example of what this might look like for a production environment see [demo_project](demo_project). 171 | 172 | ## Shoutouts 173 | 174 | This work is inspired by the sentiments of [Owais Lone's writing](https://owais.lone.pw/blog/modern-frontends-with-django/) about the limitations of Django's frontend. 175 | 176 | This work takes some technical direction from [a blog series on cbsofyalioglu](https://www.cbsofyalioglu.com/post/django-and-modern-js-libraries-svelte/). These references were immensely helpful in fleshing out this integration. 177 | -------------------------------------------------------------------------------- /demo_project/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - pre-build 3 | - build 4 | 5 | build-svelte: 6 | stage: pre-build 7 | image: node:current-alpine 8 | script: 9 | - cd svelte 10 | - npm install 11 | - npm run build 12 | artifacts: 13 | paths: 14 | - svelte/dist/ 15 | 16 | build-app: 17 | stage: build 18 | image: 19 | name: gcr.io/kaniko-project/executor:debug 20 | entrypoint: [""] 21 | script: 22 | - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json 23 | - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile Dockerfile --destination $CI_REGISTRY_IMAGE:latest 24 | dependencies: 25 | - build-svelte 26 | -------------------------------------------------------------------------------- /demo_project/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-alpine as builder 2 | # RUN adduser -D worker -u 1000 3 | 4 | RUN apk add --no-cache git 5 | 6 | # Get the python dependencies 7 | COPY requirements.txt /app/ 8 | RUN python -m pip install -r /app/requirements.txt 9 | 10 | # Copy the app itself 11 | COPY django_svelte_demo /app/src 12 | WORKDIR /app/src 13 | 14 | # Get the frontend component staticfiles 15 | COPY svelte/dist /app/svelte/dist 16 | 17 | # Catalog the staticfiles. This is needed in production, but in the dev 18 | # environment the svelte staticfiles will be drawn from 19 | # the svelte/public/build directory 20 | RUN python manage.py collectstatic --noinput 21 | 22 | # Delete the original staticfiles 23 | RUN rm -rf /app/svelte 24 | 25 | # get the runtime instructions 26 | COPY entrypoint.sh /app 27 | RUN chmod +x /app/entrypoint.sh 28 | 29 | # set the runtime user 30 | # USER worker 31 | EXPOSE 8000 32 | ENTRYPOINT ["sh", "/app/entrypoint.sh"] 33 | -------------------------------------------------------------------------------- /demo_project/README.md: -------------------------------------------------------------------------------- 1 | # `django-svelte` Demo Project 2 | 3 | This project aims to demonstrate the functionality of the `django-svelte` package. It is intended to illustrate how Node.js can fit into the development and build environments so that it doesn't have to be used in a production environment. 4 | 5 | Pay special attention to the `Dockerfile`, `docker-compose.yml`, and `gitlab-ci.yml` (I'm using Gitlab CI here because it is really great and it is what I use for all my professional projects; if there is enough interest I'll try to replicate the functionality in Github CI). 6 | 7 | ## Disclaimer 8 | 9 | This demo probably will run fine, but you're on your own getting it to run. 10 | 11 | ## Django Project 12 | 13 | What you'll find is a relatively vanilla Django project, created in the standard `django start-project` way. To it is added Django Rest Framework and this package, `django-svelte`, some templates and views along with the necessary serializers and api views. 14 | 15 | ## Svelte Project 16 | 17 | What you'll find is a relatively vanilla Vite+Svelte project, with only a few lines added to the Vite config file. See the [readme within the `svelte` project](svelte/README.md). 18 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thismatters/django-svelte/a211d4585dd3019ee9ff38c5c994649f48f663c1/demo_project/django_svelte_demo/django_svelte_demo/__init__.py -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from drf_yasg import openapi 3 | from drf_yasg.views import get_schema_view 4 | from rest_framework import permissions 5 | 6 | from django_svelte_demo.api import views 7 | 8 | 9 | schema_view = get_schema_view( 10 | openapi.Info( 11 | title="Django-Svelte Demo API", 12 | default_version="v1", 13 | description="Some test endpoints to verify that auth works right", 14 | ), 15 | public=True, 16 | permission_classes=(permissions.AllowAny,), 17 | ) 18 | 19 | urlpatterns = [ 20 | path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), 21 | path( 22 | "docs/", 23 | schema_view.with_ui("swagger", cache_timeout=0), 24 | name="schema-swagger-ui", 25 | ), 26 | path("requires-auth/", views.RequiresAuthAPIView.as_view(), name="requires-auth"), 27 | path("public/", views.PublicAPIView.as_view(), name="requires-auth"), 28 | ] 29 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.views import APIView 2 | from rest_framework.permissions import IsAuthenticated 3 | from rest_framework.response import Response 4 | 5 | 6 | class SgoodMixin: 7 | def get(self, request, *args, **kwargs): 8 | return Response({"sgood": True}) 9 | 10 | def post(self, request, *args, **kwargs): 11 | return Response({"sgood": True, "did_post": True}) 12 | 13 | 14 | class RequiresAuthAPIView(APIView, SgoodMixin): 15 | permission_classes = (IsAuthenticated,) 16 | 17 | 18 | class PublicAPIView(APIView, SgoodMixin): 19 | pass 20 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for django_svelte_demo project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_svelte_demo.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thismatters/django-svelte/a211d4585dd3019ee9ff38c5c994649f48f663c1/demo_project/django_svelte_demo/django_svelte_demo/settings/__init__.py -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_svelte_demo project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "^*8@r7lwme4sm9**rxd5g6+q4fbkalgi#1au9o%cow)otux$ok" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ["*"] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "django_svelte", 41 | "rest_framework", 42 | "drf_yasg", 43 | "django_svelte_demo", 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | "django.middleware.security.SecurityMiddleware", 48 | "django.contrib.sessions.middleware.SessionMiddleware", 49 | "django.middleware.common.CommonMiddleware", 50 | "django.middleware.csrf.CsrfViewMiddleware", 51 | "django.contrib.auth.middleware.AuthenticationMiddleware", 52 | "django.contrib.messages.middleware.MessageMiddleware", 53 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 54 | ] 55 | 56 | ROOT_URLCONF = "django_svelte_demo.urls" 57 | 58 | TEMPLATES = [ 59 | { 60 | "BACKEND": "django.template.backends.django.DjangoTemplates", 61 | "DIRS": [], 62 | "APP_DIRS": True, 63 | "OPTIONS": { 64 | "context_processors": [ 65 | "django.template.context_processors.debug", 66 | "django.template.context_processors.request", 67 | "django.contrib.auth.context_processors.auth", 68 | "django.contrib.messages.context_processors.messages", 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = "django_svelte_demo.wsgi.application" 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 79 | 80 | DATABASES = { 81 | "default": { 82 | "ENGINE": "django.db.backends.sqlite3", 83 | "NAME": BASE_DIR.parent / "db.sqlite3", 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 100 | }, 101 | { 102 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 103 | }, 104 | ] 105 | 106 | 107 | # Internationalization 108 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 109 | 110 | LANGUAGE_CODE = "en-us" 111 | 112 | TIME_ZONE = "UTC" 113 | 114 | USE_I18N = True 115 | 116 | USE_L10N = True 117 | 118 | USE_TZ = True 119 | 120 | 121 | # Static files (CSS, JavaScript, Images) 122 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 123 | 124 | STATIC_URL = "/static/" 125 | 126 | STATIC_ROOT = BASE_DIR.parent / "site_media" / "static" 127 | 128 | STATICFILES_DIRS = [ 129 | BASE_DIR.parent / "static", 130 | BASE_DIR.parent.parent / "svelte" / "dist" / "assets", 131 | ] 132 | 133 | DJANGO_SVELTE_VITE_MANIFEST_PATH = ( 134 | BASE_DIR.parent.parent / "svelte" / "dist" / ".vite" / "manifest.json" 135 | ) 136 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/settings/test.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa: F401, F403 2 | 3 | DJANGO_SVELTE_VITE_MANIFEST_PATH = None 4 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ page_title|default:"Django Svelte Demo" }} 4 | {% block styles %} 5 | {% endblock %} 6 | 7 | 8 | {% block content %} 9 | {% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/templates/basic.html: -------------------------------------------------------------------------------- 1 | {% load django_svelte %} 2 | 3 | 4 | 5 | django-svelting change 6 | {% display_svelte_css "App" %} 7 | {% display_svelte_css "AuthComponent" %} 8 | {% display_svelte_css "PostComponent" %} 9 | 10 | 11 | This text is in the django template. Below is the vanilla svelte app with some basic props passed in from the context. 12 |
13 | {% display_svelte "App" component_props %} 14 |
15 | And we can have multiple different components (they must be different): 16 |
17 | {% display_svelte "AuthComponent" %} 18 |
19 | And we should be able to POST data as well! 20 |
21 | {% display_svelte "PostComponent" %} 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/templates/svelte_base.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block styles %} 5 | {{ block.super }} 6 | {% block svelte_styles %} 7 | 8 | {% endblock %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/templates/svelte_component.html: -------------------------------------------------------------------------------- 1 | {% extends "svelte_base.html" %} 2 | {% load django_svelte %} 3 | 4 | {% block svelte_styles %} 5 | {{ block.super }} 6 | {% display_svelte_css component_name %} 7 | {% endblock %} 8 | 9 | {% block content %} 10 | {% display_svelte component_name component_props %} 11 | {% endblock %} -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/urls.py: -------------------------------------------------------------------------------- 1 | """django_svelte_demo URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | 19 | from django.views.generic import TemplateView 20 | 21 | from .views import MySvelteTemplateView, MyContextSvelteTemplateView 22 | 23 | urlpatterns = [ 24 | path( 25 | "", 26 | TemplateView.as_view( 27 | template_name="basic.html", 28 | extra_context={"component_props": {"name": "django-svelte"}}, 29 | ), 30 | name="home", 31 | ), 32 | path( 33 | "single/main/", 34 | MyContextSvelteTemplateView.as_view( 35 | page_title="Main Component", component_name="App" 36 | ), 37 | name="main-component", 38 | ), 39 | path( 40 | "single/auth/", 41 | MySvelteTemplateView.as_view( 42 | page_title="Auth Component", component_name="AuthComponent" 43 | ), 44 | name="auth-component", 45 | ), 46 | path( 47 | "single/post/", 48 | MySvelteTemplateView.as_view( 49 | page_title="Post Component", component_name="PostComponent" 50 | ), 51 | name="post-component", 52 | ), 53 | path("admin/", admin.site.urls), 54 | path("api/v1/", include("django_svelte_demo.api.urls")), 55 | ] 56 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/views.py: -------------------------------------------------------------------------------- 1 | from django_svelte.views import SvelteTemplateView 2 | 3 | 4 | class MySvelteTemplateView(SvelteTemplateView): 5 | template_name = "svelte_component.html" 6 | 7 | def get_svelte_props(self, **kwargs): 8 | return kwargs 9 | 10 | 11 | class MyContextSvelteTemplateView(MySvelteTemplateView): 12 | def get_svelte_props(self, **kwargs): 13 | kwargs.update({"name": "single component view"}) 14 | return kwargs 15 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/django_svelte_demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_svelte_demo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_svelte_demo.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_svelte_demo.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/Hashed-asdfsafd.js: -------------------------------------------------------------------------------- 1 | class Hashed { 2 | 3 | } -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/Plain.js: -------------------------------------------------------------------------------- 1 | class Something { 2 | 3 | } -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/Something.css: -------------------------------------------------------------------------------- 1 | .base { 2 | text-decoration: underline; 3 | } -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/Something.js: -------------------------------------------------------------------------------- 1 | class Something { 2 | 3 | } -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/fake_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "testkey": "testvalue" 3 | } -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/real_file.js: -------------------------------------------------------------------------------- 1 | This is an actual file! 2 | -------------------------------------------------------------------------------- /demo_project/django_svelte_demo/static/realistic_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/App.js": { 3 | "css": [ 4 | "assets/App-EY9ns7CU.css" 5 | ], 6 | "file": "assets/App-ivDvN-Hb.js", 7 | "imports": [ 8 | "_index-XiwIJ_QN.js" 9 | ], 10 | "isEntry": true, 11 | "src": "src/App.js" 12 | }, 13 | "src/AuthComponent.js": { 14 | "css": [ 15 | "assets/AuthComponent--tKiZTRp.css" 16 | ], 17 | "file": "assets/AuthComponent-ew7efgSP.js", 18 | "imports": [ 19 | "_index-XiwIJ_QN.js", 20 | "_api-S8e2CqEv.js" 21 | ], 22 | "isEntry": true, 23 | "src": "src/AuthComponent.js" 24 | }, 25 | "src/NoCSS.js": { 26 | "file": "assets/NoCSS-w0j-em_E.js", 27 | "imports": [ 28 | "_index-XiwIJ_QN.js", 29 | "_api-S8e2CqEv.js" 30 | ], 31 | "isEntry": true, 32 | "src": "src/PostComponent.js" 33 | }, 34 | "otherPrefix/FakeComponent.js": { 35 | "file": "otherAssetDir/FakeComponent-asdfasdf.js", 36 | "isEntry": true, 37 | "css": [ 38 | "otherAssetDir/FakeComponent-asdfsdaf.css", 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /demo_project/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | app: 5 | build: . 6 | environment: 7 | - APP_ENV=dev 8 | ports: 9 | - "8000:8000" 10 | volumes: 11 | - ./django_svelte_demo:/app/src 12 | - ./svelte/dist:/app/svelte/dist/ 13 | depends_on: 14 | - node 15 | networks: 16 | - dev-net 17 | node: 18 | image: node:current-alpine 19 | user: "node" 20 | working_dir: /home/node/app 21 | environment: 22 | - NODE_ENV=development 23 | - API_BASE_URL=localhost:8000 24 | volumes: 25 | - ./svelte:/home/node/app 26 | command: "npm run dev" 27 | 28 | networks: 29 | dev-net: -------------------------------------------------------------------------------- /demo_project/entrypoint.sh: -------------------------------------------------------------------------------- 1 | if [[ $APP_ENV == "dev" ]]; then 2 | python manage.py runserver 0.0.0.0:8000 3 | else 4 | # do something more production-y 5 | echo "try gunicorn!" 6 | fi -------------------------------------------------------------------------------- /demo_project/makefile: -------------------------------------------------------------------------------- 1 | build: 2 | @mkdir -p svelte/public/build/ 3 | @docker compose -p django-svelte-demo build 4 | init: 5 | @docker compose -p django-svelte-demo run app ./manage.py migrate 6 | @docker compose -p django-svelte-demo run node npm install 7 | run: 8 | @docker compose -p django-svelte-demo up -d 9 | stop: 10 | @docker compose -p django-svelte-demo down 11 | applogs: 12 | @docker compose -p django-svelte-demo logs -f app 13 | nodelogs: 14 | @docker compose -p django-svelte-demo logs -f node 15 | shell: 16 | @docker compose -p django-svelte-demo exec app sh 17 | django-shell: 18 | @docker compose -p django-svelte-demo exec app ./manage.py shell 19 | -------------------------------------------------------------------------------- /demo_project/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==5.0.1 2 | 3 | # demo dependencies 4 | djangorestframework==3.14.0 5 | drf-yasg==1.21.7 6 | 7 | # code quality 8 | black==24.1.1 9 | isort==5.13.2 10 | pyproject-flake8==6.1.0 11 | 12 | # basic testing 13 | pytest==8.0.0 14 | pytest-cov==4.1.0 15 | pytest-django==4.8.0 16 | 17 | # matrix testing 18 | tox==4.12.1 -------------------------------------------------------------------------------- /demo_project/svelte/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /demo_project/svelte/README.md: -------------------------------------------------------------------------------- 1 | # Django Svelte Demo Svelte Project 2 | 3 | This is very nearly a default Vite + Svelte deployment which can be gotten by "running `npm create vite@latest` and selecting the `svelte` option".([docs](https://svelte.dev/docs/introduction#start-a-new-project-alternatives-to-sveltekit)) The changes made to that default project are thus: 4 | * Added components and such to the `src/components` and `src/lib` directories, 5 | * Added entrypoints to the `src/` directory, 6 | * Added the `build` key to the `defineConfig` in `vite.config.js` with the following config: 7 | 8 | ```js 9 | export default defineConfig({ 10 | build: { 11 | rollupOptions: { 12 | input: [ 13 | 'src/App.js', 14 | 'src/AuthComponent.js', 15 | 'src/PostComponent.js' 16 | ], // change these out for your own components! 17 | }, 18 | manifest: true, // need this so that Django Svelte can locate hashed files 19 | }, 20 | plugins: [svelte()], 21 | }) 22 | ``` 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo_project/svelte/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "verbatimModuleSyntax": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | /** 22 | * Typecheck JS in `.svelte` and `.js` files by default. 23 | * Disable this if you'd like to use dynamic types. 24 | */ 25 | "checkJs": true 26 | }, 27 | /** 28 | * Use global.d.ts instead of compilerOptions.types 29 | * to avoid limiting type declarations. 30 | */ 31 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 32 | } 33 | -------------------------------------------------------------------------------- /demo_project/svelte/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "svelte-app", 9 | "version": "0.0.0", 10 | "devDependencies": { 11 | "@sveltejs/vite-plugin-svelte": "^3.0.1", 12 | "svelte": "^4.2.8", 13 | "vite": "^5.0.8" 14 | } 15 | }, 16 | "node_modules/@ampproject/remapping": { 17 | "version": "2.2.1", 18 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", 19 | "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", 20 | "dev": true, 21 | "dependencies": { 22 | "@jridgewell/gen-mapping": "^0.3.0", 23 | "@jridgewell/trace-mapping": "^0.3.9" 24 | }, 25 | "engines": { 26 | "node": ">=6.0.0" 27 | } 28 | }, 29 | "node_modules/@esbuild/aix-ppc64": { 30 | "version": "0.19.12", 31 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", 32 | "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", 33 | "cpu": [ 34 | "ppc64" 35 | ], 36 | "dev": true, 37 | "optional": true, 38 | "os": [ 39 | "aix" 40 | ], 41 | "engines": { 42 | "node": ">=12" 43 | } 44 | }, 45 | "node_modules/@esbuild/android-arm": { 46 | "version": "0.19.12", 47 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", 48 | "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", 49 | "cpu": [ 50 | "arm" 51 | ], 52 | "dev": true, 53 | "optional": true, 54 | "os": [ 55 | "android" 56 | ], 57 | "engines": { 58 | "node": ">=12" 59 | } 60 | }, 61 | "node_modules/@esbuild/android-arm64": { 62 | "version": "0.19.12", 63 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", 64 | "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", 65 | "cpu": [ 66 | "arm64" 67 | ], 68 | "dev": true, 69 | "optional": true, 70 | "os": [ 71 | "android" 72 | ], 73 | "engines": { 74 | "node": ">=12" 75 | } 76 | }, 77 | "node_modules/@esbuild/android-x64": { 78 | "version": "0.19.12", 79 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", 80 | "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", 81 | "cpu": [ 82 | "x64" 83 | ], 84 | "dev": true, 85 | "optional": true, 86 | "os": [ 87 | "android" 88 | ], 89 | "engines": { 90 | "node": ">=12" 91 | } 92 | }, 93 | "node_modules/@esbuild/darwin-arm64": { 94 | "version": "0.19.12", 95 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", 96 | "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", 97 | "cpu": [ 98 | "arm64" 99 | ], 100 | "dev": true, 101 | "optional": true, 102 | "os": [ 103 | "darwin" 104 | ], 105 | "engines": { 106 | "node": ">=12" 107 | } 108 | }, 109 | "node_modules/@esbuild/darwin-x64": { 110 | "version": "0.19.12", 111 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", 112 | "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", 113 | "cpu": [ 114 | "x64" 115 | ], 116 | "dev": true, 117 | "optional": true, 118 | "os": [ 119 | "darwin" 120 | ], 121 | "engines": { 122 | "node": ">=12" 123 | } 124 | }, 125 | "node_modules/@esbuild/freebsd-arm64": { 126 | "version": "0.19.12", 127 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", 128 | "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", 129 | "cpu": [ 130 | "arm64" 131 | ], 132 | "dev": true, 133 | "optional": true, 134 | "os": [ 135 | "freebsd" 136 | ], 137 | "engines": { 138 | "node": ">=12" 139 | } 140 | }, 141 | "node_modules/@esbuild/freebsd-x64": { 142 | "version": "0.19.12", 143 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", 144 | "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", 145 | "cpu": [ 146 | "x64" 147 | ], 148 | "dev": true, 149 | "optional": true, 150 | "os": [ 151 | "freebsd" 152 | ], 153 | "engines": { 154 | "node": ">=12" 155 | } 156 | }, 157 | "node_modules/@esbuild/linux-arm": { 158 | "version": "0.19.12", 159 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", 160 | "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", 161 | "cpu": [ 162 | "arm" 163 | ], 164 | "dev": true, 165 | "optional": true, 166 | "os": [ 167 | "linux" 168 | ], 169 | "engines": { 170 | "node": ">=12" 171 | } 172 | }, 173 | "node_modules/@esbuild/linux-arm64": { 174 | "version": "0.19.12", 175 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", 176 | "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", 177 | "cpu": [ 178 | "arm64" 179 | ], 180 | "dev": true, 181 | "optional": true, 182 | "os": [ 183 | "linux" 184 | ], 185 | "engines": { 186 | "node": ">=12" 187 | } 188 | }, 189 | "node_modules/@esbuild/linux-ia32": { 190 | "version": "0.19.12", 191 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", 192 | "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", 193 | "cpu": [ 194 | "ia32" 195 | ], 196 | "dev": true, 197 | "optional": true, 198 | "os": [ 199 | "linux" 200 | ], 201 | "engines": { 202 | "node": ">=12" 203 | } 204 | }, 205 | "node_modules/@esbuild/linux-loong64": { 206 | "version": "0.19.12", 207 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", 208 | "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", 209 | "cpu": [ 210 | "loong64" 211 | ], 212 | "dev": true, 213 | "optional": true, 214 | "os": [ 215 | "linux" 216 | ], 217 | "engines": { 218 | "node": ">=12" 219 | } 220 | }, 221 | "node_modules/@esbuild/linux-mips64el": { 222 | "version": "0.19.12", 223 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", 224 | "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", 225 | "cpu": [ 226 | "mips64el" 227 | ], 228 | "dev": true, 229 | "optional": true, 230 | "os": [ 231 | "linux" 232 | ], 233 | "engines": { 234 | "node": ">=12" 235 | } 236 | }, 237 | "node_modules/@esbuild/linux-ppc64": { 238 | "version": "0.19.12", 239 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", 240 | "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", 241 | "cpu": [ 242 | "ppc64" 243 | ], 244 | "dev": true, 245 | "optional": true, 246 | "os": [ 247 | "linux" 248 | ], 249 | "engines": { 250 | "node": ">=12" 251 | } 252 | }, 253 | "node_modules/@esbuild/linux-riscv64": { 254 | "version": "0.19.12", 255 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", 256 | "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", 257 | "cpu": [ 258 | "riscv64" 259 | ], 260 | "dev": true, 261 | "optional": true, 262 | "os": [ 263 | "linux" 264 | ], 265 | "engines": { 266 | "node": ">=12" 267 | } 268 | }, 269 | "node_modules/@esbuild/linux-s390x": { 270 | "version": "0.19.12", 271 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", 272 | "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", 273 | "cpu": [ 274 | "s390x" 275 | ], 276 | "dev": true, 277 | "optional": true, 278 | "os": [ 279 | "linux" 280 | ], 281 | "engines": { 282 | "node": ">=12" 283 | } 284 | }, 285 | "node_modules/@esbuild/linux-x64": { 286 | "version": "0.19.12", 287 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", 288 | "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", 289 | "cpu": [ 290 | "x64" 291 | ], 292 | "dev": true, 293 | "optional": true, 294 | "os": [ 295 | "linux" 296 | ], 297 | "engines": { 298 | "node": ">=12" 299 | } 300 | }, 301 | "node_modules/@esbuild/netbsd-x64": { 302 | "version": "0.19.12", 303 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", 304 | "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", 305 | "cpu": [ 306 | "x64" 307 | ], 308 | "dev": true, 309 | "optional": true, 310 | "os": [ 311 | "netbsd" 312 | ], 313 | "engines": { 314 | "node": ">=12" 315 | } 316 | }, 317 | "node_modules/@esbuild/openbsd-x64": { 318 | "version": "0.19.12", 319 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", 320 | "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", 321 | "cpu": [ 322 | "x64" 323 | ], 324 | "dev": true, 325 | "optional": true, 326 | "os": [ 327 | "openbsd" 328 | ], 329 | "engines": { 330 | "node": ">=12" 331 | } 332 | }, 333 | "node_modules/@esbuild/sunos-x64": { 334 | "version": "0.19.12", 335 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", 336 | "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", 337 | "cpu": [ 338 | "x64" 339 | ], 340 | "dev": true, 341 | "optional": true, 342 | "os": [ 343 | "sunos" 344 | ], 345 | "engines": { 346 | "node": ">=12" 347 | } 348 | }, 349 | "node_modules/@esbuild/win32-arm64": { 350 | "version": "0.19.12", 351 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", 352 | "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", 353 | "cpu": [ 354 | "arm64" 355 | ], 356 | "dev": true, 357 | "optional": true, 358 | "os": [ 359 | "win32" 360 | ], 361 | "engines": { 362 | "node": ">=12" 363 | } 364 | }, 365 | "node_modules/@esbuild/win32-ia32": { 366 | "version": "0.19.12", 367 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", 368 | "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", 369 | "cpu": [ 370 | "ia32" 371 | ], 372 | "dev": true, 373 | "optional": true, 374 | "os": [ 375 | "win32" 376 | ], 377 | "engines": { 378 | "node": ">=12" 379 | } 380 | }, 381 | "node_modules/@esbuild/win32-x64": { 382 | "version": "0.19.12", 383 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", 384 | "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", 385 | "cpu": [ 386 | "x64" 387 | ], 388 | "dev": true, 389 | "optional": true, 390 | "os": [ 391 | "win32" 392 | ], 393 | "engines": { 394 | "node": ">=12" 395 | } 396 | }, 397 | "node_modules/@jridgewell/gen-mapping": { 398 | "version": "0.3.3", 399 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", 400 | "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", 401 | "dev": true, 402 | "dependencies": { 403 | "@jridgewell/set-array": "^1.0.1", 404 | "@jridgewell/sourcemap-codec": "^1.4.10", 405 | "@jridgewell/trace-mapping": "^0.3.9" 406 | }, 407 | "engines": { 408 | "node": ">=6.0.0" 409 | } 410 | }, 411 | "node_modules/@jridgewell/resolve-uri": { 412 | "version": "3.1.1", 413 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 414 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 415 | "dev": true, 416 | "engines": { 417 | "node": ">=6.0.0" 418 | } 419 | }, 420 | "node_modules/@jridgewell/set-array": { 421 | "version": "1.1.2", 422 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", 423 | "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", 424 | "dev": true, 425 | "engines": { 426 | "node": ">=6.0.0" 427 | } 428 | }, 429 | "node_modules/@jridgewell/sourcemap-codec": { 430 | "version": "1.4.15", 431 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 432 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 433 | "dev": true 434 | }, 435 | "node_modules/@jridgewell/trace-mapping": { 436 | "version": "0.3.22", 437 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", 438 | "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", 439 | "dev": true, 440 | "dependencies": { 441 | "@jridgewell/resolve-uri": "^3.1.0", 442 | "@jridgewell/sourcemap-codec": "^1.4.14" 443 | } 444 | }, 445 | "node_modules/@rollup/rollup-android-arm-eabi": { 446 | "version": "4.9.6", 447 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", 448 | "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", 449 | "cpu": [ 450 | "arm" 451 | ], 452 | "dev": true, 453 | "optional": true, 454 | "os": [ 455 | "android" 456 | ] 457 | }, 458 | "node_modules/@rollup/rollup-android-arm64": { 459 | "version": "4.9.6", 460 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", 461 | "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", 462 | "cpu": [ 463 | "arm64" 464 | ], 465 | "dev": true, 466 | "optional": true, 467 | "os": [ 468 | "android" 469 | ] 470 | }, 471 | "node_modules/@rollup/rollup-darwin-arm64": { 472 | "version": "4.9.6", 473 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", 474 | "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", 475 | "cpu": [ 476 | "arm64" 477 | ], 478 | "dev": true, 479 | "optional": true, 480 | "os": [ 481 | "darwin" 482 | ] 483 | }, 484 | "node_modules/@rollup/rollup-darwin-x64": { 485 | "version": "4.9.6", 486 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", 487 | "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", 488 | "cpu": [ 489 | "x64" 490 | ], 491 | "dev": true, 492 | "optional": true, 493 | "os": [ 494 | "darwin" 495 | ] 496 | }, 497 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 498 | "version": "4.9.6", 499 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", 500 | "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", 501 | "cpu": [ 502 | "arm" 503 | ], 504 | "dev": true, 505 | "optional": true, 506 | "os": [ 507 | "linux" 508 | ] 509 | }, 510 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 511 | "version": "4.9.6", 512 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", 513 | "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", 514 | "cpu": [ 515 | "arm64" 516 | ], 517 | "dev": true, 518 | "optional": true, 519 | "os": [ 520 | "linux" 521 | ] 522 | }, 523 | "node_modules/@rollup/rollup-linux-arm64-musl": { 524 | "version": "4.9.6", 525 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", 526 | "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", 527 | "cpu": [ 528 | "arm64" 529 | ], 530 | "dev": true, 531 | "optional": true, 532 | "os": [ 533 | "linux" 534 | ] 535 | }, 536 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 537 | "version": "4.9.6", 538 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", 539 | "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", 540 | "cpu": [ 541 | "riscv64" 542 | ], 543 | "dev": true, 544 | "optional": true, 545 | "os": [ 546 | "linux" 547 | ] 548 | }, 549 | "node_modules/@rollup/rollup-linux-x64-gnu": { 550 | "version": "4.9.6", 551 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", 552 | "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", 553 | "cpu": [ 554 | "x64" 555 | ], 556 | "dev": true, 557 | "optional": true, 558 | "os": [ 559 | "linux" 560 | ] 561 | }, 562 | "node_modules/@rollup/rollup-linux-x64-musl": { 563 | "version": "4.9.6", 564 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", 565 | "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", 566 | "cpu": [ 567 | "x64" 568 | ], 569 | "dev": true, 570 | "optional": true, 571 | "os": [ 572 | "linux" 573 | ] 574 | }, 575 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 576 | "version": "4.9.6", 577 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", 578 | "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", 579 | "cpu": [ 580 | "arm64" 581 | ], 582 | "dev": true, 583 | "optional": true, 584 | "os": [ 585 | "win32" 586 | ] 587 | }, 588 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 589 | "version": "4.9.6", 590 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", 591 | "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", 592 | "cpu": [ 593 | "ia32" 594 | ], 595 | "dev": true, 596 | "optional": true, 597 | "os": [ 598 | "win32" 599 | ] 600 | }, 601 | "node_modules/@rollup/rollup-win32-x64-msvc": { 602 | "version": "4.9.6", 603 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", 604 | "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", 605 | "cpu": [ 606 | "x64" 607 | ], 608 | "dev": true, 609 | "optional": true, 610 | "os": [ 611 | "win32" 612 | ] 613 | }, 614 | "node_modules/@sveltejs/vite-plugin-svelte": { 615 | "version": "3.0.2", 616 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.0.2.tgz", 617 | "integrity": "sha512-MpmF/cju2HqUls50WyTHQBZUV3ovV/Uk8k66AN2gwHogNAG8wnW8xtZDhzNBsFJJuvmq1qnzA5kE7YfMJNFv2Q==", 618 | "dev": true, 619 | "dependencies": { 620 | "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", 621 | "debug": "^4.3.4", 622 | "deepmerge": "^4.3.1", 623 | "kleur": "^4.1.5", 624 | "magic-string": "^0.30.5", 625 | "svelte-hmr": "^0.15.3", 626 | "vitefu": "^0.2.5" 627 | }, 628 | "engines": { 629 | "node": "^18.0.0 || >=20" 630 | }, 631 | "peerDependencies": { 632 | "svelte": "^4.0.0 || ^5.0.0-next.0", 633 | "vite": "^5.0.0" 634 | } 635 | }, 636 | "node_modules/@sveltejs/vite-plugin-svelte-inspector": { 637 | "version": "2.0.0", 638 | "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.0.0.tgz", 639 | "integrity": "sha512-gjr9ZFg1BSlIpfZ4PRewigrvYmHWbDrq2uvvPB1AmTWKuM+dI1JXQSUu2pIrYLb/QncyiIGkFDFKTwJ0XqQZZg==", 640 | "dev": true, 641 | "dependencies": { 642 | "debug": "^4.3.4" 643 | }, 644 | "engines": { 645 | "node": "^18.0.0 || >=20" 646 | }, 647 | "peerDependencies": { 648 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 649 | "svelte": "^4.0.0 || ^5.0.0-next.0", 650 | "vite": "^5.0.0" 651 | } 652 | }, 653 | "node_modules/@types/estree": { 654 | "version": "1.0.5", 655 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", 656 | "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", 657 | "dev": true 658 | }, 659 | "node_modules/acorn": { 660 | "version": "8.11.3", 661 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 662 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 663 | "dev": true, 664 | "bin": { 665 | "acorn": "bin/acorn" 666 | }, 667 | "engines": { 668 | "node": ">=0.4.0" 669 | } 670 | }, 671 | "node_modules/aria-query": { 672 | "version": "5.3.0", 673 | "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", 674 | "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", 675 | "dev": true, 676 | "dependencies": { 677 | "dequal": "^2.0.3" 678 | } 679 | }, 680 | "node_modules/axobject-query": { 681 | "version": "4.0.0", 682 | "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", 683 | "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", 684 | "dev": true, 685 | "dependencies": { 686 | "dequal": "^2.0.3" 687 | } 688 | }, 689 | "node_modules/code-red": { 690 | "version": "1.0.4", 691 | "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", 692 | "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", 693 | "dev": true, 694 | "dependencies": { 695 | "@jridgewell/sourcemap-codec": "^1.4.15", 696 | "@types/estree": "^1.0.1", 697 | "acorn": "^8.10.0", 698 | "estree-walker": "^3.0.3", 699 | "periscopic": "^3.1.0" 700 | } 701 | }, 702 | "node_modules/css-tree": { 703 | "version": "2.3.1", 704 | "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", 705 | "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", 706 | "dev": true, 707 | "dependencies": { 708 | "mdn-data": "2.0.30", 709 | "source-map-js": "^1.0.1" 710 | }, 711 | "engines": { 712 | "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" 713 | } 714 | }, 715 | "node_modules/debug": { 716 | "version": "4.3.4", 717 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 718 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 719 | "dev": true, 720 | "dependencies": { 721 | "ms": "2.1.2" 722 | }, 723 | "engines": { 724 | "node": ">=6.0" 725 | }, 726 | "peerDependenciesMeta": { 727 | "supports-color": { 728 | "optional": true 729 | } 730 | } 731 | }, 732 | "node_modules/deepmerge": { 733 | "version": "4.3.1", 734 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 735 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 736 | "dev": true, 737 | "engines": { 738 | "node": ">=0.10.0" 739 | } 740 | }, 741 | "node_modules/dequal": { 742 | "version": "2.0.3", 743 | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", 744 | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", 745 | "dev": true, 746 | "engines": { 747 | "node": ">=6" 748 | } 749 | }, 750 | "node_modules/esbuild": { 751 | "version": "0.19.12", 752 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", 753 | "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", 754 | "dev": true, 755 | "hasInstallScript": true, 756 | "bin": { 757 | "esbuild": "bin/esbuild" 758 | }, 759 | "engines": { 760 | "node": ">=12" 761 | }, 762 | "optionalDependencies": { 763 | "@esbuild/aix-ppc64": "0.19.12", 764 | "@esbuild/android-arm": "0.19.12", 765 | "@esbuild/android-arm64": "0.19.12", 766 | "@esbuild/android-x64": "0.19.12", 767 | "@esbuild/darwin-arm64": "0.19.12", 768 | "@esbuild/darwin-x64": "0.19.12", 769 | "@esbuild/freebsd-arm64": "0.19.12", 770 | "@esbuild/freebsd-x64": "0.19.12", 771 | "@esbuild/linux-arm": "0.19.12", 772 | "@esbuild/linux-arm64": "0.19.12", 773 | "@esbuild/linux-ia32": "0.19.12", 774 | "@esbuild/linux-loong64": "0.19.12", 775 | "@esbuild/linux-mips64el": "0.19.12", 776 | "@esbuild/linux-ppc64": "0.19.12", 777 | "@esbuild/linux-riscv64": "0.19.12", 778 | "@esbuild/linux-s390x": "0.19.12", 779 | "@esbuild/linux-x64": "0.19.12", 780 | "@esbuild/netbsd-x64": "0.19.12", 781 | "@esbuild/openbsd-x64": "0.19.12", 782 | "@esbuild/sunos-x64": "0.19.12", 783 | "@esbuild/win32-arm64": "0.19.12", 784 | "@esbuild/win32-ia32": "0.19.12", 785 | "@esbuild/win32-x64": "0.19.12" 786 | } 787 | }, 788 | "node_modules/estree-walker": { 789 | "version": "3.0.3", 790 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 791 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 792 | "dev": true, 793 | "dependencies": { 794 | "@types/estree": "^1.0.0" 795 | } 796 | }, 797 | "node_modules/fsevents": { 798 | "version": "2.3.3", 799 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 800 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 801 | "dev": true, 802 | "hasInstallScript": true, 803 | "optional": true, 804 | "os": [ 805 | "darwin" 806 | ], 807 | "engines": { 808 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 809 | } 810 | }, 811 | "node_modules/is-reference": { 812 | "version": "3.0.2", 813 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", 814 | "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", 815 | "dev": true, 816 | "dependencies": { 817 | "@types/estree": "*" 818 | } 819 | }, 820 | "node_modules/kleur": { 821 | "version": "4.1.5", 822 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", 823 | "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", 824 | "dev": true, 825 | "engines": { 826 | "node": ">=6" 827 | } 828 | }, 829 | "node_modules/locate-character": { 830 | "version": "3.0.0", 831 | "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", 832 | "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", 833 | "dev": true 834 | }, 835 | "node_modules/magic-string": { 836 | "version": "0.30.7", 837 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", 838 | "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", 839 | "dev": true, 840 | "dependencies": { 841 | "@jridgewell/sourcemap-codec": "^1.4.15" 842 | }, 843 | "engines": { 844 | "node": ">=12" 845 | } 846 | }, 847 | "node_modules/mdn-data": { 848 | "version": "2.0.30", 849 | "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", 850 | "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", 851 | "dev": true 852 | }, 853 | "node_modules/ms": { 854 | "version": "2.1.2", 855 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 856 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 857 | "dev": true 858 | }, 859 | "node_modules/nanoid": { 860 | "version": "3.3.7", 861 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 862 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 863 | "dev": true, 864 | "funding": [ 865 | { 866 | "type": "github", 867 | "url": "https://github.com/sponsors/ai" 868 | } 869 | ], 870 | "bin": { 871 | "nanoid": "bin/nanoid.cjs" 872 | }, 873 | "engines": { 874 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 875 | } 876 | }, 877 | "node_modules/periscopic": { 878 | "version": "3.1.0", 879 | "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", 880 | "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", 881 | "dev": true, 882 | "dependencies": { 883 | "@types/estree": "^1.0.0", 884 | "estree-walker": "^3.0.0", 885 | "is-reference": "^3.0.0" 886 | } 887 | }, 888 | "node_modules/picocolors": { 889 | "version": "1.0.0", 890 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 891 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 892 | "dev": true 893 | }, 894 | "node_modules/postcss": { 895 | "version": "8.4.34", 896 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz", 897 | "integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==", 898 | "dev": true, 899 | "funding": [ 900 | { 901 | "type": "opencollective", 902 | "url": "https://opencollective.com/postcss/" 903 | }, 904 | { 905 | "type": "tidelift", 906 | "url": "https://tidelift.com/funding/github/npm/postcss" 907 | }, 908 | { 909 | "type": "github", 910 | "url": "https://github.com/sponsors/ai" 911 | } 912 | ], 913 | "dependencies": { 914 | "nanoid": "^3.3.7", 915 | "picocolors": "^1.0.0", 916 | "source-map-js": "^1.0.2" 917 | }, 918 | "engines": { 919 | "node": "^10 || ^12 || >=14" 920 | } 921 | }, 922 | "node_modules/rollup": { 923 | "version": "4.9.6", 924 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", 925 | "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", 926 | "dev": true, 927 | "dependencies": { 928 | "@types/estree": "1.0.5" 929 | }, 930 | "bin": { 931 | "rollup": "dist/bin/rollup" 932 | }, 933 | "engines": { 934 | "node": ">=18.0.0", 935 | "npm": ">=8.0.0" 936 | }, 937 | "optionalDependencies": { 938 | "@rollup/rollup-android-arm-eabi": "4.9.6", 939 | "@rollup/rollup-android-arm64": "4.9.6", 940 | "@rollup/rollup-darwin-arm64": "4.9.6", 941 | "@rollup/rollup-darwin-x64": "4.9.6", 942 | "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", 943 | "@rollup/rollup-linux-arm64-gnu": "4.9.6", 944 | "@rollup/rollup-linux-arm64-musl": "4.9.6", 945 | "@rollup/rollup-linux-riscv64-gnu": "4.9.6", 946 | "@rollup/rollup-linux-x64-gnu": "4.9.6", 947 | "@rollup/rollup-linux-x64-musl": "4.9.6", 948 | "@rollup/rollup-win32-arm64-msvc": "4.9.6", 949 | "@rollup/rollup-win32-ia32-msvc": "4.9.6", 950 | "@rollup/rollup-win32-x64-msvc": "4.9.6", 951 | "fsevents": "~2.3.2" 952 | } 953 | }, 954 | "node_modules/source-map-js": { 955 | "version": "1.0.2", 956 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 957 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 958 | "dev": true, 959 | "engines": { 960 | "node": ">=0.10.0" 961 | } 962 | }, 963 | "node_modules/svelte": { 964 | "version": "4.2.10", 965 | "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.10.tgz", 966 | "integrity": "sha512-Ep06yCaCdgG1Mafb/Rx8sJ1QS3RW2I2BxGp2Ui9LBHSZ2/tO/aGLc5WqPjgiAP6KAnLJGaIr/zzwQlOo1b8MxA==", 967 | "dev": true, 968 | "dependencies": { 969 | "@ampproject/remapping": "^2.2.1", 970 | "@jridgewell/sourcemap-codec": "^1.4.15", 971 | "@jridgewell/trace-mapping": "^0.3.18", 972 | "@types/estree": "^1.0.1", 973 | "acorn": "^8.9.0", 974 | "aria-query": "^5.3.0", 975 | "axobject-query": "^4.0.0", 976 | "code-red": "^1.0.3", 977 | "css-tree": "^2.3.1", 978 | "estree-walker": "^3.0.3", 979 | "is-reference": "^3.0.1", 980 | "locate-character": "^3.0.0", 981 | "magic-string": "^0.30.4", 982 | "periscopic": "^3.1.0" 983 | }, 984 | "engines": { 985 | "node": ">=16" 986 | } 987 | }, 988 | "node_modules/svelte-hmr": { 989 | "version": "0.15.3", 990 | "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz", 991 | "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==", 992 | "dev": true, 993 | "engines": { 994 | "node": "^12.20 || ^14.13.1 || >= 16" 995 | }, 996 | "peerDependencies": { 997 | "svelte": "^3.19.0 || ^4.0.0" 998 | } 999 | }, 1000 | "node_modules/vite": { 1001 | "version": "5.0.12", 1002 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", 1003 | "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", 1004 | "dev": true, 1005 | "dependencies": { 1006 | "esbuild": "^0.19.3", 1007 | "postcss": "^8.4.32", 1008 | "rollup": "^4.2.0" 1009 | }, 1010 | "bin": { 1011 | "vite": "bin/vite.js" 1012 | }, 1013 | "engines": { 1014 | "node": "^18.0.0 || >=20.0.0" 1015 | }, 1016 | "funding": { 1017 | "url": "https://github.com/vitejs/vite?sponsor=1" 1018 | }, 1019 | "optionalDependencies": { 1020 | "fsevents": "~2.3.3" 1021 | }, 1022 | "peerDependencies": { 1023 | "@types/node": "^18.0.0 || >=20.0.0", 1024 | "less": "*", 1025 | "lightningcss": "^1.21.0", 1026 | "sass": "*", 1027 | "stylus": "*", 1028 | "sugarss": "*", 1029 | "terser": "^5.4.0" 1030 | }, 1031 | "peerDependenciesMeta": { 1032 | "@types/node": { 1033 | "optional": true 1034 | }, 1035 | "less": { 1036 | "optional": true 1037 | }, 1038 | "lightningcss": { 1039 | "optional": true 1040 | }, 1041 | "sass": { 1042 | "optional": true 1043 | }, 1044 | "stylus": { 1045 | "optional": true 1046 | }, 1047 | "sugarss": { 1048 | "optional": true 1049 | }, 1050 | "terser": { 1051 | "optional": true 1052 | } 1053 | } 1054 | }, 1055 | "node_modules/vitefu": { 1056 | "version": "0.2.5", 1057 | "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", 1058 | "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", 1059 | "dev": true, 1060 | "peerDependencies": { 1061 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" 1062 | }, 1063 | "peerDependenciesMeta": { 1064 | "vite": { 1065 | "optional": true 1066 | } 1067 | } 1068 | } 1069 | } 1070 | } 1071 | -------------------------------------------------------------------------------- /demo_project/svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^3.0.1", 13 | "svelte": "^4.2.8", 14 | "vite": "^5.0.8" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo_project/svelte/src/App.js: -------------------------------------------------------------------------------- 1 | import App from './components/App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById("app-target"), 5 | props: JSON.parse(document.getElementById("app-props").textContent), 6 | }); 7 | 8 | export default app; -------------------------------------------------------------------------------- /demo_project/svelte/src/AuthComponent.js: -------------------------------------------------------------------------------- 1 | import AuthComponent from './components/AuthComponent.svelte'; 2 | 3 | const authcomponent = new AuthComponent({ 4 | target: document.getElementById("authcomponent-target"), 5 | props: JSON.parse(document.getElementById("authcomponent-props").textContent), 6 | }); 7 | 8 | export default authcomponent; -------------------------------------------------------------------------------- /demo_project/svelte/src/PostComponent.js: -------------------------------------------------------------------------------- 1 | import PostComponent from './components/PostComponent.svelte'; 2 | 3 | const postcomponent = new PostComponent({ 4 | target: document.getElementById("postcomponent-target"), 5 | props: JSON.parse(document.getElementById("postcomponent-props").textContent), 6 | }); 7 | 8 | export default postcomponent; -------------------------------------------------------------------------------- /demo_project/svelte/src/components/App.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |

{value} {name}!

8 | 9 |

Visit the Svelte tutorial to learn how to build Svelte apps.

10 |
11 | 12 | -------------------------------------------------------------------------------- /demo_project/svelte/src/components/AuthComponent.svelte: -------------------------------------------------------------------------------- 1 | 15 | 16 |

Check your auth status

17 | 18 | {#if isAuth} 19 |

You're Auth!

20 | {:else if didClick} 21 |

You're not Auth!

22 | {/if} 23 | 24 | 25 | -------------------------------------------------------------------------------- /demo_project/svelte/src/components/PostComponent.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |

POST something. make it good.

13 | 14 | 15 | 16 | {#if res} 17 |

{res.sgood ? "Good" : "Bad"} 18 | {res.did_post ? "did post!" : "didn't post :("}

19 | {/if} -------------------------------------------------------------------------------- /demo_project/svelte/src/lib/api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a really basic implementation of a fetch wrapper appropriate 3 | * for use with `django-svelte`. This works for _same origin_ requests, 4 | * where the API backend has the same URL as the frontend. If your setup 5 | * differs then you'll have to build support for that here(ish). 6 | */ 7 | 8 | function getCookie(name) { 9 | let cookieValue = null; 10 | if (document.cookie && document.cookie !== '') { 11 | const cookies = document.cookie.split(';'); 12 | for (let i = 0; i < cookies.length; i++) { 13 | const cookie = cookies[i].trim(); 14 | // Does this cookie string begin with the name we want? 15 | if (cookie.substring(0, name.length + 1) === (name + '=')) { 16 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 17 | break; 18 | } 19 | } 20 | } 21 | return cookieValue; 22 | } 23 | 24 | async function send({ method, path, data }) { 25 | const fetch = window.fetch; 26 | const opts = { method, headers: {} }; 27 | 28 | if (data) { 29 | opts.headers['Content-Type'] = 'application/json'; 30 | opts.headers['X-CSRFToken'] = getCookie('csrftoken'); 31 | opts.body = JSON.stringify(data); 32 | } 33 | let url = `/${path}`; 34 | console.log(opts, url); 35 | return await fetch(url, opts) 36 | .then(r => r.text()) 37 | .then(json => { 38 | try { 39 | return JSON.parse(json); 40 | } catch (err) { 41 | console.log(json); 42 | return json; 43 | } 44 | }) 45 | .catch(e => { 46 | console.log(`[${e}]: ${url} with ${JSON.stringify(opts)} failed`); 47 | return {}; 48 | }); 49 | } 50 | 51 | export function get(path) { 52 | return send({ method: 'GET', path }); 53 | } 54 | 55 | export function del(path) { 56 | return send({ method: 'DELETE', path }); 57 | } 58 | 59 | export function post(path, data) { 60 | return send({ method: 'POST', path, data }); 61 | } 62 | 63 | export function put(path, data) { 64 | return send({ method: 'PUT', path, data }); 65 | } 66 | 67 | export function patch(path, data) { 68 | return send({ method: 'PATCH', path, data }); 69 | } 70 | -------------------------------------------------------------------------------- /demo_project/svelte/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /demo_project/svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /demo_project/svelte/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | build: { 7 | rollupOptions: { 8 | input: ['src/App.js', 'src/AuthComponent.js', 'src/PostComponent.js'], 9 | }, 10 | manifest: true, 11 | }, 12 | plugins: [svelte()], 13 | }) 14 | -------------------------------------------------------------------------------- /django_svelte/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thismatters/django-svelte/a211d4585dd3019ee9ff38c5c994649f48f663c1/django_svelte/__init__.py -------------------------------------------------------------------------------- /django_svelte/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DjangoSvelteConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "django_svelte" 7 | -------------------------------------------------------------------------------- /django_svelte/conf.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from appconf import AppConf 4 | from django.conf import settings # noqa: F401 5 | from django.core.exceptions import ImproperlyConfigured 6 | 7 | 8 | class DjangoSvelteAppConf(AppConf): 9 | VITE_MANIFEST_PATH = None 10 | _VITE_MANIFEST = None 11 | ENTRYPOINT_PREFIX = "src/" 12 | VITE_ASSETSDIR = "assets/" 13 | 14 | def configure__vite_manifest(self, value): 15 | if value is not None: 16 | raise ImproperlyConfigured( 17 | "Do not set the `DJANGO_SVELTE__VITE_MANIFEST` setting, " 18 | "its value is generated!" 19 | ) 20 | return None 21 | 22 | def configure_entrypoint_prefix(self, value): 23 | if value is None: 24 | value = "" 25 | if value and not value.endswith("/"): 26 | raise ImproperlyConfigured( 27 | "Non-blank entrypoint prefix must end with a slash '/'." 28 | ) 29 | return value 30 | 31 | def configure_vite_assetsdir(self, value): 32 | if value is None: 33 | value = "" 34 | if value and not value.endswith("/"): 35 | raise ImproperlyConfigured( 36 | "Non-blank vite assetsdir must end with a slash '/'." 37 | ) 38 | return value 39 | 40 | def configure(self): 41 | """Load the vite manifest into memory""" 42 | _path = self.configured_data["VITE_MANIFEST_PATH"] 43 | if _path is None: 44 | return self.configured_data 45 | if not _path.exists(): 46 | raise ImproperlyConfigured(f"Vite manifest must exist at path! {_path}") 47 | with open(_path) as manifest: 48 | self.configured_data["_VITE_MANIFEST"] = json.load(manifest) 49 | return self.configured_data 50 | -------------------------------------------------------------------------------- /django_svelte/models.py: -------------------------------------------------------------------------------- 1 | # this is needed to load in the package settings 2 | from django_svelte.conf import settings # noqa: F401 3 | -------------------------------------------------------------------------------- /django_svelte/templates/django_svelte/display_svelte.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Create a target element 3 | load the compiled bundle 4 | instantiate the bundle with the json context 5 | {% endcomment %} 6 | 7 |
8 | 9 | {{ props|json_script:props_name }} 10 | 11 | -------------------------------------------------------------------------------- /django_svelte/templates/django_svelte/display_svelte_css.html: -------------------------------------------------------------------------------- 1 | {% for css_bundle_url in css_bundle_urls %} 2 | 3 | {% endfor %} -------------------------------------------------------------------------------- /django_svelte/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thismatters/django-svelte/a211d4585dd3019ee9ff38c5c994649f48f663c1/django_svelte/templatetags/__init__.py -------------------------------------------------------------------------------- /django_svelte/templatetags/django_svelte.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.contrib.staticfiles import finders 4 | from django.contrib.staticfiles.storage import staticfiles_storage 5 | 6 | register = template.Library() 7 | 8 | 9 | def clean_hashed_filename(filename): 10 | # strip the vite `build.assetsDir` from the filename 11 | if filename.startswith(settings.DJANGO_SVELTE_VITE_ASSETSDIR): 12 | _len = len(settings.DJANGO_SVELTE_VITE_ASSETSDIR) 13 | filename = filename[_len:] 14 | return filename 15 | 16 | 17 | def get_bundle_hashed_filename(*, component_name): 18 | key = f"{settings.DJANGO_SVELTE_ENTRYPOINT_PREFIX}{component_name}.js" 19 | entry = settings.DJANGO_SVELTE__VITE_MANIFEST.get(key) 20 | if entry is None: 21 | return None 22 | if not entry.get("isEntry", False): 23 | return None 24 | filename = entry["file"] 25 | filename = clean_hashed_filename(filename) 26 | return filename 27 | 28 | 29 | def _get_css_bundle_hashed_filenames(*, key): 30 | entry = settings.DJANGO_SVELTE__VITE_MANIFEST.get(key) 31 | bundles = entry.get("css", []) 32 | for subkey in entry.get("imports", []): 33 | bundles.extend(_get_css_bundle_hashed_filenames(key=subkey)) 34 | return bundles 35 | 36 | 37 | def get_css_bundle_hashed_filenames(*, component_name): 38 | key = f"{settings.DJANGO_SVELTE_ENTRYPOINT_PREFIX}{component_name}.js" 39 | bundles = _get_css_bundle_hashed_filenames(key=key) 40 | return [clean_hashed_filename(b) for b in set(bundles)] 41 | 42 | 43 | def get_css_bundle_urls(*, component_name): 44 | filenames = [] 45 | if settings.DJANGO_SVELTE__VITE_MANIFEST is not None: 46 | filenames = get_css_bundle_hashed_filenames(component_name=component_name) 47 | else: 48 | filenames.append(f"{component_name}.css") 49 | cleaned_bundles = [] 50 | for filename in filenames: 51 | found_file = finders.find(filename) 52 | if found_file is None: 53 | continue 54 | cleaned_bundles.append(staticfiles_storage.url(filename)) 55 | return cleaned_bundles 56 | 57 | 58 | def get_bundle_url(*, component_name): 59 | filename = None 60 | if settings.DJANGO_SVELTE__VITE_MANIFEST is not None: 61 | filename = get_bundle_hashed_filename(component_name=component_name) 62 | else: 63 | filename = f"{component_name}.js" 64 | if filename is None: 65 | return None 66 | static_file = finders.find(filename) 67 | if static_file is None: 68 | return None 69 | return staticfiles_storage.url(filename) 70 | 71 | 72 | def de_svelte(name): 73 | """Removes the .svelte suffix from a name, if present""" 74 | if name.endswith(".svelte"): 75 | return name[:-7] 76 | return name 77 | 78 | 79 | @register.inclusion_tag("django_svelte/display_svelte_css.html") 80 | def display_svelte_css(component_name): 81 | component_name = de_svelte(component_name) 82 | 83 | return { 84 | "css_bundle_urls": get_css_bundle_urls(component_name=component_name), 85 | } 86 | 87 | 88 | @register.inclusion_tag("django_svelte/display_svelte.html") 89 | def display_svelte(component_name, component_props=None): 90 | component_name = de_svelte(component_name) 91 | component_props = component_props or {} 92 | 93 | return { 94 | "bundle_url": get_bundle_url(component_name=component_name), 95 | "element_id": f"{component_name.lower()}-target", 96 | "props_name": f"{component_name.lower()}-props", 97 | "props": component_props, 98 | } 99 | -------------------------------------------------------------------------------- /django_svelte/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import TemplateView 2 | from django.views.generic.base import ContextMixin 3 | 4 | 5 | class SvelteContextMixin(ContextMixin): 6 | component_name = None 7 | page_title = "Svelte Component" 8 | 9 | def get_svelte_props(self, **kwargs): 10 | raise NotImplementedError( 11 | "Define `get_svelte_props` in BaseSvelteComponentView subclass." 12 | ) 13 | 14 | def get_context_data(self, *, svelte_props=None, **kwargs): 15 | context = super().get_context_data(**kwargs) 16 | _svelte_props = svelte_props or {} 17 | context.update( 18 | { 19 | "page_title": self.page_title, 20 | "component_name": self.component_name, 21 | "component_props": self.get_svelte_props(**_svelte_props), 22 | } 23 | ) 24 | return context 25 | 26 | 27 | class SvelteTemplateView(SvelteContextMixin, TemplateView): 28 | """Display Svelte component. (Provide your own conformant template)""" 29 | 30 | pass 31 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | exclude = 'migrations/' 3 | 4 | [tool.isort] 5 | profile = "black" 6 | extend_skip_glob = ["*/migrations"] 7 | 8 | [tool.flake8] 9 | exclude = ["migrations"] 10 | ignore = ["E402", "W503"] 11 | max-line-length = 100 12 | max-complexity = 10 13 | 14 | [tool.pytest.ini_options] 15 | testpaths = ["tests"] 16 | pythonpath = ["demo_project/django_svelte_demo"] 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setup( 7 | name="django-svelte", 8 | author="Paul Stiverson", 9 | url="https://github.com/thismatters/django-svelte/", 10 | version="0.2.1", 11 | packages=find_packages(exclude=("tests", "demo_project")), 12 | license="MIT", 13 | description="Facilitates adding Svelte frontend to Django", 14 | long_description=long_description, 15 | long_description_content_type="text/markdown", 16 | python_requires=">=3.8", 17 | include_package_data=True, 18 | install_requires=[ 19 | "Django>=3.2.0", 20 | "django-appconf>=1.0.0", 21 | ], 22 | classifiers=[ 23 | "Development Status :: 4 - Beta", 24 | "License :: OSI Approved :: MIT License", 25 | "Intended Audience :: Developers", 26 | "Natural Language :: English", 27 | "Operating System :: OS Independent", 28 | "Programming Language :: Python", 29 | "Programming Language :: Python :: 3", 30 | "Programming Language :: Python :: 3.8", 31 | "Programming Language :: Python :: 3.9", 32 | "Programming Language :: Python :: 3.10", 33 | "Programming Language :: Python :: 3.11", 34 | "Programming Language :: Python :: 3.12", 35 | "Framework :: Django", 36 | "Framework :: Django :: 3.2", 37 | "Framework :: Django :: 4.1", 38 | "Framework :: Django :: 4.2", 39 | "Framework :: Django :: 5.0", 40 | "Topic :: Internet", 41 | "Topic :: Software Development :: Libraries", 42 | "Topic :: Software Development :: Libraries :: Python Modules", 43 | "Topic :: Utilities", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thismatters/django-svelte/a211d4585dd3019ee9ff38c5c994649f48f663c1/tests/conftest.py -------------------------------------------------------------------------------- /tests/test_conf.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | from django.conf import settings 5 | from django.core.exceptions import ImproperlyConfigured 6 | 7 | from django_svelte.conf import DjangoSvelteAppConf 8 | 9 | 10 | class TestDjangoSvelteAppConf: 11 | def test_configure__vite_manifest(self): 12 | assert DjangoSvelteAppConf().configure__vite_manifest(None) is None 13 | 14 | def test_configure__vite_manifest_bad(self): 15 | with pytest.raises(ImproperlyConfigured): 16 | DjangoSvelteAppConf().configure__vite_manifest("") 17 | 18 | def test_configure_entrypoint_prefix(self): 19 | assert DjangoSvelteAppConf().configure_entrypoint_prefix("slash/") == "slash/" 20 | 21 | def test_configure_entrypoint_prefix_none(self): 22 | assert DjangoSvelteAppConf().configure_entrypoint_prefix(None) == "" 23 | 24 | def test_configure_entrypoint_prefix_bad(self): 25 | with pytest.raises(ImproperlyConfigured): 26 | DjangoSvelteAppConf().configure_entrypoint_prefix("noslash") 27 | 28 | def test_configure_vite_assetsdir(self): 29 | assert DjangoSvelteAppConf().configure_vite_assetsdir("slash/") == "slash/" 30 | 31 | def test_configure_vite_assetsdir_none(self): 32 | assert DjangoSvelteAppConf().configure_vite_assetsdir(None) == "" 33 | 34 | def test_configure_vite_assetsdir_bad(self): 35 | with pytest.raises(ImproperlyConfigured): 36 | DjangoSvelteAppConf().configure_vite_assetsdir("noslash") 37 | 38 | def test_configure(self): 39 | conf = DjangoSvelteAppConf() 40 | conf._meta.configured_data = { 41 | "VITE_MANIFEST_PATH": Path( 42 | settings.BASE_DIR.parent / "static" / "fake_manifest.json" 43 | ) 44 | } 45 | ret = conf.configure() 46 | assert ret["_VITE_MANIFEST"] == {"testkey": "testvalue"} 47 | 48 | def test_configure_no_file(self): 49 | conf = DjangoSvelteAppConf() 50 | conf._meta.configured_data = {"VITE_MANIFEST_PATH": Path("bad/path")} 51 | with pytest.raises(ImproperlyConfigured): 52 | ret = conf.configure() 53 | -------------------------------------------------------------------------------- /tests/test_django_svelte.py: -------------------------------------------------------------------------------- 1 | from django.test import override_settings 2 | 3 | from django_svelte.templatetags.django_svelte import ( 4 | clean_hashed_filename, 5 | get_bundle_hashed_filename, 6 | _get_css_bundle_hashed_filenames, 7 | get_css_bundle_hashed_filenames, 8 | get_css_bundle_urls, 9 | get_bundle_url, 10 | de_svelte, 11 | display_svelte_css, 12 | display_svelte, 13 | ) 14 | 15 | 16 | class TestGetStaticFileUrl: 17 | def test_none(self): 18 | assert get_bundle_url(component_name="not-a_file") is None 19 | 20 | def test_real(self): 21 | assert get_bundle_url(component_name="real_file") == "/static/real_file.js" 22 | 23 | @override_settings( 24 | DJANGO_SVELTE__VITE_MANIFEST={ 25 | "src/Hashed.js": {"isEntry": True, "file": "assets/Hashed-asdfsafd.js"} 26 | } 27 | ) 28 | def test_hashed(self): 29 | assert get_bundle_url(component_name="Hashed") == "/static/Hashed-asdfsafd.js" 30 | 31 | @override_settings( 32 | DJANGO_SVELTE__VITE_MANIFEST={ 33 | "src/Bashed.js": {"isEntry": True, "file": "assets/Bashed-asdfsafd.js"} 34 | } 35 | ) 36 | def test_hashed_missing(self): 37 | assert get_bundle_url(component_name="Bashed") is None 38 | 39 | @override_settings( 40 | DJANGO_SVELTE__VITE_MANIFEST={ 41 | "src/Bashed.js": {"isEntry": True, "file": "assets/Bashed-asdfsafd.js"} 42 | } 43 | ) 44 | def test_hashed_absent(self): 45 | assert get_bundle_url(component_name="Hashed") is None 46 | 47 | 48 | def test_de_svelte(): 49 | assert de_svelte("something.svelte") == "something" 50 | 51 | 52 | def test_de_svelte_no_change(): 53 | assert de_svelte("something.smelte") == "something.smelte" 54 | 55 | 56 | def test_display_svelte_css(): 57 | assert display_svelte_css("Something.svelte") == { 58 | "css_bundle_urls": ["/static/Something.css"] 59 | } 60 | 61 | 62 | def test_display_svelte_css_missing(): 63 | assert display_svelte_css("Plain.svelte") == {"css_bundle_urls": []} 64 | 65 | 66 | def test_display_svelte(): 67 | assert display_svelte("Something.svelte", component_props={"great": "stuff"}) == { 68 | "bundle_url": "/static/Something.js", 69 | "element_id": "something-target", 70 | "props_name": "something-props", 71 | "props": {"great": "stuff"}, 72 | } 73 | 74 | 75 | class TestGetHashedFilename: 76 | @override_settings( 77 | DJANGO_SVELTE__VITE_MANIFEST={ 78 | "src/App.js": {"isEntry": True, "file": "assets/App-asdfsafd.js"} 79 | } 80 | ) 81 | def test_good(self): 82 | assert get_bundle_hashed_filename(component_name="App") == "App-asdfsafd.js" 83 | 84 | @override_settings( 85 | DJANGO_SVELTE__VITE_MANIFEST={ 86 | "a.js": {"imports": ["b.js", "c.js"], "css": ["assets/a.css"]}, 87 | "src/App.js": { 88 | "isEntry": True, 89 | "css": ["assets/App-asdfsafd.css"], 90 | "imports": ["a.js", "b.js"], 91 | }, 92 | "b.js": {"imports": ["c.js"], "css": ["assets/b.css"]}, 93 | "c.js": {}, 94 | } 95 | ) 96 | def test_css(self): 97 | css = _get_css_bundle_hashed_filenames(key="src/App.js") 98 | print(css) 99 | assert "assets/App-asdfsafd.css" in css 100 | assert "assets/a.css" in css 101 | assert "assets/b.css" in css 102 | # this list is not deduped! 103 | assert len(css) == 4 104 | 105 | @override_settings( 106 | DJANGO_SVELTE__VITE_MANIFEST={ 107 | "a.js": {"imports": ["b.js", "c.js"], "css": ["assets/a.css"]}, 108 | "src/App.js": { 109 | "isEntry": True, 110 | "css": ["assets/App-asdfsafd.css"], 111 | "imports": ["a.js", "b.js"], 112 | }, 113 | "b.js": {"imports": ["c.js"], "css": ["assets/b.css"]}, 114 | "c.js": {}, 115 | } 116 | ) 117 | def test_get_css_bundle_hashed_filenames(self): 118 | css = get_css_bundle_hashed_filenames(component_name="App") 119 | print(css) 120 | assert "App-asdfsafd.css" in css 121 | assert "a.css" in css 122 | assert "b.css" in css 123 | assert len(css) == 3 124 | 125 | @override_settings(DJANGO_SVELTE__VITE_MANIFEST={"src/App.js": {"isEntry": True}}) 126 | def test_css_none(self): 127 | assert get_css_bundle_urls(component_name="App") == [] 128 | 129 | @override_settings( 130 | DJANGO_SVELTE__VITE_MANIFEST={ 131 | "src/App.js": {"isEntry": True, "file": "assets/App-asdfsafd.js"} 132 | } 133 | ) 134 | def test_nonexistent(self): 135 | assert get_bundle_hashed_filename(component_name="Abb") is None 136 | 137 | @override_settings( 138 | DJANGO_SVELTE__VITE_MANIFEST={"src/App.js": {"file": "assets/App-asdfsafd.js"}} 139 | ) 140 | def test_nonentry(self): 141 | assert get_bundle_hashed_filename(component_name="App") is None 142 | 143 | @override_settings( 144 | DJANGO_SVELTE__VITE_MANIFEST={ 145 | "src/App.js": {"file": "bassets/App-asdfsafd.js"} 146 | }, 147 | DJANGO_SVELTE_VITE_ASSETSDIR="bassets/", 148 | ) 149 | def test_different_assetsdir(self): 150 | assert get_bundle_hashed_filename(component_name="App") is None 151 | 152 | @override_settings( 153 | DJANGO_SVELTE__VITE_MANIFEST={ 154 | "src/entrypoints/App.js": {"file": "assets/App-asdfsafd.js"} 155 | }, 156 | DJANGO_SVELTE_ENTRYPOINT_PREFIX="src/entrypoints/", 157 | ) 158 | def test_different_prefix(self): 159 | assert get_bundle_hashed_filename(component_name="App") is None 160 | -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | 3 | import pytest 4 | 5 | from django_svelte import views 6 | 7 | 8 | def mocked_get_svelte_props(self, **kwargs): 9 | kwargs.update( 10 | { 11 | "new_prop": "here", 12 | } 13 | ) 14 | return kwargs 15 | 16 | 17 | class TestSvelteComponentMixin: 18 | @patch.object(views.SvelteContextMixin, "get_svelte_props", mocked_get_svelte_props) 19 | def test_get_context_data(self): 20 | mixin = views.SvelteContextMixin() 21 | mixin.component_name = "FakeComponent" 22 | mixin.page_title = "Fake Component" 23 | ctx = mixin.get_context_data() 24 | assert "page_title" in ctx 25 | assert ctx["page_title"] == "Fake Component" 26 | assert "component_name" in ctx 27 | assert ctx["component_name"] == "FakeComponent" 28 | assert "component_props" in ctx 29 | assert ctx["component_props"] == {"new_prop": "here"} 30 | 31 | def test_get_context_data_no_subclass(self): 32 | mixin = views.SvelteContextMixin() 33 | with pytest.raises(NotImplementedError): 34 | ctx = mixin.get_context_data() 35 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | requires = 3 | tox>=4 4 | env_list = lint, py{38,39,310,311}-django{32,41,42}, py{310,311}-django{50} 5 | 6 | 7 | [gh-actions] 8 | python = 9 | 3.8: py38 10 | 3.9: py39 11 | 3.10: py310 12 | 3.11: py311 13 | 3.12: py312 14 | 15 | 16 | [testenv] 17 | description = run unit tests 18 | setenv = 19 | DJANGO_SETTINGS_MODULE=django_svelte_demo.settings.test 20 | deps = 21 | pytest>=7 22 | pytest-django 23 | pytest-cov 24 | djangorestframework==3.14.0 25 | drf-yasg==1.21.7 26 | django32: Django>=3.2,<3.3 27 | django41: Django>=4.1,<4.2 28 | django42: Django>=4.2,<4.3 29 | django50: Django>=5.0,<5.1 30 | commands = 31 | pytest --cov --cov-report xml {posargs:tests} 32 | usedevelop = True 33 | 34 | [testenv:lint] 35 | description = run linters 36 | skip_install = true 37 | deps = 38 | black==24.1.1 39 | isort==5.13.2 40 | pyproject-flake8==6.1.0 41 | changedir = django_svelte 42 | commands = 43 | black --check {posargs:.} 44 | isort --check --diff {posargs:.} 45 | pflake8 46 | --------------------------------------------------------------------------------