diff --git a/.eslintrc.js b/.eslintrc.js index 4c2043012828bd16438eb4f36472ad48460eb6e4..3bdc1f231b3a75f10ae3ba0687e8c649d7218082 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,7 +9,6 @@ module.exports = { "vue/multi-word-component-names": "off", }, env: { - browser: true, - node: true, + es2021: true, }, }; diff --git a/.gitignore b/.gitignore index dd85b53f78b12d78a2ff1053f4ab17e2601a62b2..9f60735a5e85703360c8669a34fa01622a079afd 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,5 @@ yarn.lock *.code-workspace /cache +/node_modules +.vite diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 22864314148d1bada0cccb4ef5f828235a9b8cd5..6efe90b75c7e1ae7ba4a387bcc08417172eab585 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,12 @@ and this project adheres to `Semantic Versioning`_. Unreleased ---------- +Deprecated +~~~~~~~~~~ + +* The `webpack_bundle` management command is replaced by the new `vite` + command. The `webpack_bundle` command will be removed in AlekSIS-Core 4.0. + Added ~~~~~ @@ -19,6 +25,8 @@ Changed ~~~~~~~ * Rewrite of frontend using Vuetify + * The runuwsgi dev server now starts a Vite dev server with HMR in the + background * OIDC scope "profile" now exposes the avatar instead of the official photo * Based on Django 4.0 * Use built-in Redis cache backend @@ -27,6 +35,7 @@ Changed requests * Incorporate SPDX license list for app licenses on About page * [Dev] The undocumented field `check` on `DataCheckResult` was renamed to `data_check` +* Frontend bundling migrated from Webpack to Vite Fixed ~~~~~ diff --git a/Dockerfile b/Dockerfile index 914ee330f0d7e6646ba62c4d630747501b72d4f4..44159bb3fd185d4eb11224e48139319c9468b019 100644 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ CMD ["/usr/local/bin/aleksis-docker-startup"] # Install assets FROM core as assets -RUN eatmydata aleksis-admin webpack_bundle; \ +RUN eatmydata aleksis-admin vite build; \ eatmydata aleksis-admin collectstatic --no-input; \ rm -rf /usr/local/share/.cache # FIXME Introduce deletion after we don't need materializecss anymore for SASS @@ -124,7 +124,7 @@ ONBUILD RUN set -e; \ if [ -n "$APPS" ]; then \ eatmydata pip install $APPS; \ fi; \ - eatmydata aleksis-admin webpack_bundle; \ + eatmydata aleksis-admin vite build; \ eatmydata aleksis-admin collectstatic --no-input; \ rm -rf /usr/local/share/.cache; \ eatmydata apt-get remove --purge -y yarnpkg $BUILD_DEPS; \ diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js index ed2e2caafa8f6cd2078f956d05600c844f4cf842..53eb31376cf1a3b949d8ba4aae3e9d95d2abdb03 100644 --- a/aleksis/core/assets/app.js +++ b/aleksis/core/assets/app.js @@ -86,6 +86,7 @@ import MessageBox from "./components/MessageBox.vue"; import NotificationList from "./components/notifications/NotificationList.vue"; import SidenavSearch from "./components/SidenavSearch.vue"; import CeleryProgressBottom from "./components/celery_progress/CeleryProgressBottom.vue"; +import gqlSystemProperties from "./systemProperties.graphql"; Vue.component(MessageBox.name, MessageBox); // Load MessageBox globally as other components depend on it @@ -120,7 +121,7 @@ const app = new Vue({ }, }), apollo: { - systemProperties: require("./systemProperties.graphql"), + systemProperties: gqlSystemProperties, }, watch: { systemProperties: function (newProperties) { diff --git a/aleksis/core/assets/components/SidenavSearch.vue b/aleksis/core/assets/components/SidenavSearch.vue index 58bf7bf6d718302279cd9d2d5d9451b6bdf9a644..ea044a9b6a0bca4ed7a84fdcc4ae4ef156826e87 100644 --- a/aleksis/core/assets/components/SidenavSearch.vue +++ b/aleksis/core/assets/components/SidenavSearch.vue @@ -1,4 +1,6 @@ <script> +import gqlSearchSnippets from "./searchSnippets.graphql"; + export default { methods: { submit: function () { @@ -22,7 +24,7 @@ export default { <template> <ApolloQuery - :query="require('./searchSnippets.graphql')" + :query="gqlSearchSnippets" :variables="{ q, }" diff --git a/aleksis/core/assets/components/about/InstalledAppsList.vue b/aleksis/core/assets/components/about/InstalledAppsList.vue index 491e3df15ece49cf736f037212a0bc668a924bed..704eedc97e172f7f840f4023d0a472a0d2fc7907 100644 --- a/aleksis/core/assets/components/about/InstalledAppsList.vue +++ b/aleksis/core/assets/components/about/InstalledAppsList.vue @@ -1,5 +1,5 @@ <template> - <ApolloQuery :query="require('./installedApps.graphql')"> + <ApolloQuery :query="gqlInstalledApps"> <template #default="{ result: { error, data }, isLoading }"> <v-row v-if="isLoading"> <v-col @@ -31,6 +31,7 @@ <script> import InstalledAppCard from "./InstalledAppCard.vue"; +import gqlInstalledApps from "./installedApps.graphql"; export default { name: "InstalledAppsList", diff --git a/aleksis/core/assets/components/celery_progress/CeleryProgress.vue b/aleksis/core/assets/components/celery_progress/CeleryProgress.vue index 06b28fc37f52f5440442815207d901fe42cdcd69..86cc128471428a50f474a28d38ea19925e9b6d04 100644 --- a/aleksis/core/assets/components/celery_progress/CeleryProgress.vue +++ b/aleksis/core/assets/components/celery_progress/CeleryProgress.vue @@ -86,13 +86,15 @@ <script> import BackButton from "../BackButton.vue"; import MessageBox from "../MessageBox.vue"; +import gqlCeleryProgress from "./celeryProgress.graphql"; +import gqlCeleryProgressFetched from "./celeryProgressFetched.graphql"; export default { name: "CeleryProgress", components: { BackButton, MessageBox }, apollo: { celeryProgressByTaskId: { - query: require("./celeryProgress.graphql"), + query: gqlCeleryProgress, variables() { return { taskId: this.$route.params.taskId, @@ -114,7 +116,7 @@ export default { if (newState === "SUCCESS" || newState === "ERROR") { this.$apollo.queries.celeryProgressByTaskId.stopPolling(); this.$apollo.mutate({ - mutation: require("./celeryProgressFetched.graphql"), + mutation: gqlCeleryProgressFetched, variables: { taskId: this.$route.params.taskId, }, diff --git a/aleksis/core/assets/components/celery_progress/CeleryProgressBottom.vue b/aleksis/core/assets/components/celery_progress/CeleryProgressBottom.vue index 2bb95431cecd66bc68789bc1bfe7c5977b68cfb6..410e57fda0eca36d3acaca7fc0318174fd71d318 100644 --- a/aleksis/core/assets/components/celery_progress/CeleryProgressBottom.vue +++ b/aleksis/core/assets/components/celery_progress/CeleryProgressBottom.vue @@ -25,6 +25,7 @@ <script> import TaskListItem from "./TaskListItem.vue"; +import gqlCeleryProgressButton from "./celeryProgressBottom.graphql"; export default { name: "CeleryProgressBottom", @@ -45,7 +46,7 @@ export default { }, apollo: { celeryProgressByUser: { - query: require("./celeryProgressBottom.graphql"), + query: gqlCeleryProgressButton, pollInterval: 1000, }, }, diff --git a/aleksis/core/assets/components/notifications/NotificationItem.vue b/aleksis/core/assets/components/notifications/NotificationItem.vue index b1dea65c167df9417803a6e7c532309acce75325..6011765afe2ebf3df707d2dbfe7549e4f010d4a8 100644 --- a/aleksis/core/assets/components/notifications/NotificationItem.vue +++ b/aleksis/core/assets/components/notifications/NotificationItem.vue @@ -1,6 +1,6 @@ <template> <ApolloMutation - :mutation="require('./markNotificationRead.graphql')" + :mutation="gqlmarkNotificationRead" :variables="{ id: this.notification.id }" > <template #default="{ mutate, loading, error }"> @@ -80,6 +80,8 @@ </template> <script> +import gqlMarkNotificationRead from "./markNotificationRead.graphql"; + export default { props: { notification: { diff --git a/aleksis/core/assets/components/notifications/NotificationList.vue b/aleksis/core/assets/components/notifications/NotificationList.vue index 7e16ee197f93ff3c4f68e6d1275e2a175272c64f..9ec08daa5048672241808b01be16223d8f8aba07 100644 --- a/aleksis/core/assets/components/notifications/NotificationList.vue +++ b/aleksis/core/assets/components/notifications/NotificationList.vue @@ -1,6 +1,6 @@ <template> <ApolloQuery - :query="require('./myNotifications.graphql')" + :query="gqlMyNotifications" :poll-interval="1000" > <template #default="{ result: { error, data, loading } }"> @@ -73,6 +73,7 @@ <script> import NotificationItem from "./NotificationItem.vue"; +import gqlMyNotifications from "./myNotifications.graphql"; export default { components: { diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js index ea18ea4dbc7cd71e42a0062da2ab80a987b11c51..01f2ce2f2f8d314937536a8433c227d5633e5c51 100644 --- a/aleksis/core/assets/index.js +++ b/aleksis/core/assets/index.js @@ -3,9 +3,16 @@ import "@mdi/font/css/materialdesignicons.css"; import "./util"; import "./app"; +// This imports all known AlekSIS app entrypoints +// The list is generated by util/frontent_helpers.py and passed to Vite, +// which aliases the app package names into virtual JavaScript modules +// and generates importing code at bundle time. +import "aleksisAppImporter"; + import CeleryProgress from "./components/celery_progress/CeleryProgress.vue"; import About from "./components/about/About.vue"; + window.router.addRoute({ path: "/celery_progress/:taskId", component: CeleryProgress, diff --git a/aleksis/core/management/commands/vite.py b/aleksis/core/management/commands/vite.py new file mode 100644 index 0000000000000000000000000000000000000000..57370441b37a9db241a8a8a0bebbd8f8f00f5753 --- /dev/null +++ b/aleksis/core/management/commands/vite.py @@ -0,0 +1,29 @@ +import os + +from django.conf import settings + +from django_yarnpkg.management.base import BaseYarnCommand +from django_yarnpkg.yarn import yarn_adapter + +from ...util.frontend_helpers import run_vite, write_vite_values + + +class Command(BaseYarnCommand): + help = "Create Vite bundles for AlekSIS" # noqa + + def add_arguments(self, parser): + parser.add_argument("command", choices=["build", "serve"], nargs="?", default="build") + parser.add_argument("--no-install", action="store_true", default=False) + + def handle(self, *args, **options): + super(Command, self).handle(*args, **options) + + # Inject settings into Vite + write_vite_values(os.path.join(settings.NODE_MODULES_ROOT, "django-vite-values.json")) + + # Install Node dependencies + if not options["no_install"]: + yarn_adapter.install(settings.YARN_INSTALLED_APPS) + + # Run Vite build + run_vite([options["command"]]) diff --git a/aleksis/core/management/commands/webpack_bundle.py b/aleksis/core/management/commands/webpack_bundle.py index ee38566a8cda8587c41aacf036eca1d7da16cd04..1b324ae200e993dba15119fb16335091217d7200 100644 --- a/aleksis/core/management/commands/webpack_bundle.py +++ b/aleksis/core/management/commands/webpack_bundle.py @@ -1,35 +1,16 @@ -import json -import os -import shutil +import warnings -from django.conf import settings +from .vite import Command as ViteCommand -from django_yarnpkg.management.base import BaseYarnCommand -from django_yarnpkg.yarn import yarn_adapter -from ...util.frontend_helpers import get_apps_with_assets - - -class Command(BaseYarnCommand): - help = "Create webpack bundles for AlekSIS" # noqa +class Command(ViteCommand): + help = "Create Vite bundles for AlekSIS (legacy command alias)" # noqa def handle(self, *args, **options): - super(Command, self).handle(*args, **options) - - # Write webpack entrypoints for all apps - assets = { - app: {"dependOn": "core", "import": os.path.join(path, "index")} - for app, path in get_apps_with_assets().items() - } - assets["core"] = os.path.join(settings.BASE_DIR, "aleksis", "core", "assets", "index") - with open(os.path.join(settings.NODE_MODULES_ROOT, "webpack-entrypoints.json"), "w") as out: - json.dump(assets, out) - - # Install Node dependencies - yarn_adapter.install(settings.YARN_INSTALLED_APPS) + warnings.warn( + "webpack_bundle is deprecated and will be removed " + "in AlekSIS-Core 4.0. Use the new vite command instead.", + UserWarning, + ) - # Run webpack - config_path = os.path.join(settings.BASE_DIR, "aleksis", "core", "webpack.config.js") - shutil.copy(config_path, settings.NODE_MODULES_ROOT) - mode = "development" if settings.DEBUG else "production" - yarn_adapter.call_yarn(["run", "webpack", f"--mode={mode}"]) + super().handle(*args, **options) diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index daafab787c5112d393e5e547e7ba0638149efd64..ceec09c7b38c2e0809b62d12948f894d0d6ced2f 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -97,7 +97,7 @@ INSTALLED_APPS = [ "sass_processor", "django_any_js", "django_yarnpkg", - "webpack_loader", + "django_vite", "django_tables2", "maintenance_mode", "menu_generator", @@ -572,22 +572,17 @@ YARN_INSTALLED_APPS = [ "vue-apollo@^3.1.0", "vuetify@^2.6.7", "vue-router@^3.5.2", - "css-loader@^6.7.1", - "sass-loader@^13.0", - "vue-loader@^15.0.0", - "vue-style-loader@^4.1.3", - "vue-template-compiler@^2.7.7", - "webpack@^5.73.0", - "webpack-bundle-tracker@^1.6.0", - "webpack-cli@^4.10.0", + "vite@^4.0.1", + "@vitejs/plugin-vue2@^2.2.0", + "@rollup/plugin-node-resolve@^15.0.1", + "@rollup/plugin-graphql@^2.0.2", + "@rollup/plugin-virtual@^3.0.1", "vue-i18n@8", "eslint@^8.26.0", "eslint-plugin-vue@^9.7.0", - "eslint-webpack-plugin@^3.2.0", "eslint-config-prettier@^8.5.0", "stylelint@^14.14.0", "stylelint-config-standard@^29.0.0", - "stylelint-webpack-plugin@^3.3.0", "stylelint-config-prettier@^9.0.3", ] @@ -596,17 +591,12 @@ merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True) JS_URL = _settings.get("js_assets.url", STATIC_URL) JS_ROOT = _settings.get("js_assets.root", os.path.join(NODE_MODULES_ROOT, "node_modules")) -WEBPACK_LOADER = { - "DEFAULT": { - "CACHE": not DEBUG, - "STATS_FILE": os.path.join(NODE_MODULES_ROOT, "webpack-stats.json"), - "BUNDLE_DIR_NAME": "", - "POLL_INTERVAL": 0.1, - "IGNORE": [r".+\.hot-update.js", r".+\.map"], - } -} +DJANGO_VITE_ASSETS_PATH = os.path.join(NODE_MODULES_ROOT, "vite_bundles") +DJANGO_VITE_DEV_MODE = DEBUG +DJANGO_VITE_DEV_SERVER_PORT = 5173 + STATICFILES_DIRS = ( - os.path.join(NODE_MODULES_ROOT, "webpack_bundles"), + DJANGO_VITE_ASSETS_PATH, JS_ROOT, ) @@ -755,6 +745,7 @@ if _settings.get("dev.uwsgi.celery", DEBUG): UWSGI.setdefault("attach-daemon", []) UWSGI["attach-daemon"].append(f"celery -A aleksis.core worker --concurrency={concurrency}") UWSGI["attach-daemon"].append("celery -A aleksis.core beat") + UWSGI["attach-daemon"].append("aleksis-admin vite --no-install serve") DEFAULT_FAVICON_PATHS = { "pwa_icon": os.path.join(STATIC_ROOT, "img/aleksis-icon-maskable.png"), diff --git a/aleksis/core/templates/core/vue_base.html b/aleksis/core/templates/core/vue_base.html index f79c4a9bd7dfde9bcfcb5a2f822da5643054dca4..c1383da534105e30ccbb3ac09e67e35e1e80df15 100644 --- a/aleksis/core/templates/core/vue_base.html +++ b/aleksis/core/templates/core/vue_base.html @@ -1,7 +1,7 @@ {# -*- engine:django -*- #} {% load i18n menu_generator static sass_tags any_js rules html_helpers %} -{% load render_bundle from webpack_loader %} +{% load django_vite %} {% get_current_language as LANGUAGE_CODE %} {% get_available_languages as LANGUAGES %} @@ -53,6 +53,8 @@ <script type="text/javascript" src="{% url 'config.js' %}"></script> {% include_js "iconify" %} + {% vite_hmr_client %} + {% block extra_head %}{% endblock %} </head> <body {% if no_menu %}class="without-menu"{% endif %}> @@ -209,7 +211,7 @@ {{ request.site.preferences.theme__primary|json_script:"primary-color" }} {{ request.site.preferences.theme__secondary|json_script:"secondary-color" }} <script type="text/javascript" src="{% static 'js/search.js' %}"></script> -{% render_bundle 'core' %} +{% vite_asset 'aleksis/core/assets/index.js' %} {% block extra_body %}{% endblock %} </body> </html> diff --git a/aleksis/core/util/frontend_helpers.py b/aleksis/core/util/frontend_helpers.py index 664707500bda7ffde9d5d8456be39128cf55ff8f..4389cdd6d50b6651f12e0d20d904849195a94156 100644 --- a/aleksis/core/util/frontend_helpers.py +++ b/aleksis/core/util/frontend_helpers.py @@ -1,7 +1,12 @@ +import json import os +import shutil +from typing import Any, Optional, Sequence from django.conf import settings +from django_yarnpkg.yarn import yarn_adapter + from .core_helpers import get_app_module, get_app_packages @@ -17,6 +22,43 @@ def get_apps_with_assets(): return assets +def write_vite_values(out_path: str) -> dict[str, Any]: + vite_values = { + "static_url": settings.STATIC_URL, + } + # Write rollup entrypoints for all apps + vite_values["appEntrypoints"] = {} + for app, path in get_apps_with_assets().items(): + ep = os.path.join(path, "index.js") + if os.path.exists(ep): + vite_values["appEntrypoints"][app] = ep + # Add core entrypoint + vite_values["coreEntrypoint"] = os.path.join( + settings.BASE_DIR, "aleksis", "core", "assets", "index.js" + ) + + with open(out_path, "w") as out: + json.dump(vite_values, out) + + +def run_vite(args: Optional[Sequence[str]] = None) -> None: + args = list(args) if args else [] + + config_path = os.path.join(settings.BASE_DIR, "aleksis", "core", "vite.config.js") + shutil.copy(config_path, settings.NODE_MODULES_ROOT) + + mode = "development" if settings.DEBUG else "production" + args += ["-m", mode] + + log_level = settings.LOGGING["root"]["level"] + if settings.DEBUG or log_level == "DEBUG": + args.append("-d") + log_level = {"INFO": "info", "WARNING": "warn", "ERROR": "error"}.get(log_level, "silent") + args += ["-l", log_level] + + yarn_adapter.call_yarn(["run", "vite"] + args) + + def get_language_cookie(code: str) -> str: """Build a cookie string to set a new language.""" cookie_parts = [f"{settings.LANGUAGE_COOKIE_NAME}={code}"] diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js new file mode 100644 index 0000000000000000000000000000000000000000..12a7bcc5baeec4f19645bc16bbfba556ace7f9f8 --- /dev/null +++ b/aleksis/core/vite.config.js @@ -0,0 +1,76 @@ +const fs = require("fs"); +const path = require("path"); + +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue2"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; +import graphql from "@rollup/plugin-graphql"; +import virtual from "@rollup/plugin-virtual"; + +const django_values = JSON.parse(fs.readFileSync("./django-vite-values.json")); + +function generateAppImporter(entrypoints) { + let code = "let appObjects = {};"; + for (const appPackage of Object.keys(entrypoints)) { + let appName = appPackage.split(".").slice(-1)[0]; + appName = appName.charAt(0).toUpperCase() + appName.substring(1); + + code += `console.debug("Importing AlekSIS app entrypoint for ${appPackage}");\n`; + code += `import ${appName} from '${appPackage}';\n`; + code += `appObjects.push(${appName});\n`; + } + code += "export default appObjects;\n"; + return code; +} + +export default defineConfig({ + root: path.resolve(".."), + base: django_values.static_url, + build: { + outDir: path.resolve("./vite_bundles/"), + manifest: true, + rollupOptions: { + input: django_values.coreEntrypoint, + output: { + manualChunks(id) { + // Split big libraries into own chunks + if (id.includes("node_modules/vue")) { + return "vue"; + } else if (id.includes("node_modules/apollo")) { + return "apollo"; + } else if (id.includes("node_modules/graphql")) { + return "graphql"; + } else if (id.includes("node_modules")) { + // Fallback for all other libraries + return "vendor"; + } + + // Split each AlekSIS app in its own chunk + for (const [appPackage, ep] of Object.entries(django_values.appEntrypoints)) { + if (id.includes(ep)) { + return appPackage; + } + } + }, + }, + }, + }, + server: { + strictPort: true, + origin: "http://127.0.0.1:5173", + }, + plugins: [ + virtual({ + aleksisAppImporter: generateAppImporter(django_values.appEntrypoints), + }), + vue(), + nodeResolve({ modulePaths: [path.resolve("./node_modules")] }), + graphql(), + ], + resolve: { + alias: { + vue: "vue/dist/vue.esm.js", + ...django_values.appEntrypoints, + }, + }, +}); diff --git a/aleksis/core/webpack.config.js b/aleksis/core/webpack.config.js deleted file mode 100644 index 8f327cbe55a6dba6b7ae5f104a89f4da46ddf711..0000000000000000000000000000000000000000 --- a/aleksis/core/webpack.config.js +++ /dev/null @@ -1,97 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const webpack = require("webpack"); -const BundleTracker = require("webpack-bundle-tracker"); -const { VueLoaderPlugin } = require("vue-loader"); -const ESLintPlugin = require("eslint-webpack-plugin"); -const StyleLintPlugin = require("stylelint-webpack-plugin"); - -module.exports = { - context: __dirname, - entry: JSON.parse(fs.readFileSync("./webpack-entrypoints.json")), - output: { - path: path.resolve("./webpack_bundles/"), - filename: "[name]-[hash].js", - chunkFilename: "[id]-[chunkhash].js", - }, - plugins: [ - new BundleTracker({ filename: "./webpack-stats.json" }), - new VueLoaderPlugin(), - new ESLintPlugin({ - extensions: ["js", "vue"], - }), - new StyleLintPlugin({ - files: ["assets/**/*.{vue,htm,html,css,sss,less,scss,sass}"], - }), - ], - module: { - rules: [ - { - test: /\.vue$/, - use: { - loader: "vue-loader", - options: { - transpileOptions: { - transforms: { - dangerousTaggedTemplateString: true, - }, - }, - }, - }, - }, - { - test: /\.(css)$/, - use: ["vue-style-loader", "css-loader"], - }, - { - test: /\.scss$/, - use: [ - "vue-style-loader", - "css-loader", - { - loader: "sass-loader", - options: { - sassOptions: { - indentedSyntax: false, - }, - }, - }, - ], - }, - { - test: /\.(graphql|gql)$/, - exclude: /node_modules/, - loader: "graphql-tag/loader", - }, - ], - }, - optimization: { - runtimeChunk: "single", - splitChunks: { - chunks: "all", - maxInitialRequests: Infinity, - minSize: 0, - cacheGroups: { - vendor: { - test: /[\\/]node_modules[\\/]/, - name(module) { - // get the name. E.g. node_modules/packageName/not/this/part.js - // or node_modules/packageName - const packageName = module.context.match( - /[\\/]node_modules[\\/](.*?)([\\/]|$)/ - )[1]; - - // npm package names are URL-safe, but some servers don't like @ symbols - return `npm.${packageName.replace("@", "")}`; - }, - }, - }, - }, - }, - resolve: { - modules: [path.resolve("./node_modules")], - alias: { - vue$: "vue/dist/vue.esm.js", - }, - }, -}; diff --git a/docs/admin/10_install.rst b/docs/admin/10_install.rst index ee46193d93282c9e44016b3b0ebf8e1f1b9c4b36..322d39023183a171b29f2fffc4a55988441a89c7 100644 --- a/docs/admin/10_install.rst +++ b/docs/admin/10_install.rst @@ -144,7 +144,7 @@ After that, you can install the aleksis meta-package, or only `aleksis-core`: .. code-block:: shell pip3 install aleksis - aleksis-admin webpack_bundle + aleksis-admin vite build aleksis-admin collectstatic aleksis-admin migrate aleksis-admin createinitialrevisions diff --git a/pyproject.toml b/pyproject.toml index c9a5f25b70fabae18809434113aeed5cdef4a1d7..0ab9b866ddfe0cf79255cb41de68747973452c0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,8 +128,8 @@ django-ical = "^1.8.3" django-iconify = "^0.3" customidenticon = "^0.1.5" graphene-django = "^3.0.0" -django-webpack-loader = "^1.6.0" selenium = "^4.4.3" +django-vite = "^2.0.2" [tool.poetry.extras] ldap = ["django-auth-ldap"] diff --git a/tox.ini b/tox.ini index ff7a8c4aa3f0d97c2224ac9e4998fdaa246265c7..0c422a187839c40b718427898e2de2fab1c80758 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ skip_install = true envdir = {toxworkdir}/globalenv commands_pre = poetry install -E ldap - poetry run aleksis-admin webpack_bundle + poetry run aleksis-admin vite build poetry run aleksis-admin collectstatic --no-input commands = poetry run pytest --cov=. {posargs} aleksis/