Frontend: Document webpack to Vite migration plan #5679

Add a README section mapping the current webpack build to its Vite
equivalents, and record the vue3-gettext 4.x ESM-only blocker (webpack
cannot resolve `fs` from pofile) that motivates the migration.
This commit is contained in:
Michael Mayer 2026-06-22 20:17:34 +00:00
parent 8b0cf318b0
commit b61345584b

View file

@ -48,14 +48,49 @@ When an upstream advisory is fully resolved, retire the override and rerun `make
## Major-Version Upgrades — Known Blockers
Some major upgrades are blocked by config-file module style. The configs referenced below are CommonJS today; an ESM migration is required before bumping these. Track each as its own change:
Some major upgrades are blocked by config-file module style (the configs referenced below are CommonJS today and need an ESM migration first) or by a bundler-level incompatibility in the package itself. Track each as its own change:
| Package | Latest | Blocker |
|-------------------------------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `postcss-preset-env` 11.x | ESM | `frontend/postcss.config.js` is CommonJS (`module.exports = { plugins: [require("postcss-preset-env"), ...] }`). |
| `webpack-manifest-plugin` 6.x | ESM | `frontend/webpack.config.js` is CommonJS (`require("webpack-manifest-plugin")`). Webpack accepts ESM configs, but the migration is non-trivial. |
| `vuetify` 4.x | — | See the `vuetify` row in [Currently Pinned Packages](#currently-pinned-packages); also a separate v3 → v4 migration project. |
| `vue-router` 5.x | — | Major release with breaking changes across `frontend/src/app/routes.js` and dynamic imports. Needs its own evaluation pass with TestCafe verification. |
| Package | Latest | Blocker |
|-------------------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `postcss-preset-env` 11.x | ESM | `frontend/postcss.config.js` is CommonJS (`module.exports = { plugins: [require("postcss-preset-env"), ...] }`). |
| `webpack-manifest-plugin` 6.x | ESM | `frontend/webpack.config.js` is CommonJS (`require("webpack-manifest-plugin")`). Webpack accepts ESM configs, but the migration is non-trivial. |
| `vue3-gettext` 4.x | ESM | v4 is ESM-only and exports its extraction tooling from the same runtime entry: `dist/index.js` does an unconditional `import PO from "pofile"`, and `pofile` calls `require("fs")`. The `exports` map has no runtime-only subpath and the package sets no `sideEffects: false`, so webpack cannot tree-shake it — `make build-js` fails with `Can't resolve 'fs'` in `pofile/lib/po.js`. The runtime API itself (`createGettext({ translations, silent, defaultLanguage })`, the `$gettext`/`$ngettext`/`$pgettext`/`$npgettext` globals, `%{}` interpolation) is compatible and the removed `<translate>` component / `v-translate` directive are unused here, so the only fix needed is a bundler workaround (e.g. `resolve.fallback: { fs: false }`, which ships dead extraction code), an upstream split of runtime vs. tooling exports, or a [migration to Vite](#migrating-the-production-build-from-webpack-to-vite) (Rollup externalizes `fs` instead of failing). |
| `vuetify` 4.x | — | See the `vuetify` row in [Currently Pinned Packages](#currently-pinned-packages); also a separate v3 → v4 migration project. |
| `vue-router` 5.x | — | Major release with breaking changes across `frontend/src/app/routes.js` and dynamic imports. Needs its own evaluation pass with TestCafe verification. |
## Migrating the Production Build From webpack to Vite
**Status: not started — tracked in #5679.** The production bundle is built with webpack (`frontend/webpack.config.js`, run via `make build-js`); Vitest already runs on Vite (`frontend/vitest.config.js`), so a production migration would converge the two toolchains. This is its own project (related: #5659 test-side Vite v8, and the historical #838). It is *not* a prerequisite for any single dependency bump; treat the ESM-only unblock (e.g. `vue3-gettext` 4.x) as a side benefit, not the reason.
**Why it helps the ESM-only blockers.** Vite/Rollup externalize Node built-ins for the browser — a `fs` import resolves to a stub that only throws if actually called at runtime — instead of failing the build the way webpack 5 does. Verified locally: a minimal Vite build of `vue3-gettext` 4's `createGettext` succeeds with a `Module "fs" has been externalized` warning. Caveat: Vite does **not** tree-shake the dead extraction tooling either (the package sets no `sideEffects: false`), so the runtime bundle still carries the `pofile`/parser code — migrate for the modernization, not as a bundle slimmer.
**The contract that must not break.** The Go server reads a **flat** asset manifest: `internal/config/client_assets.go` (`ClientAssets`) unmarshals `assets/static/build/assets.json` keyed by logical name — `app.js`, `app.css`, `share.js`, `share.css`, `splash.js`, `splash.css`, plus named font/image entries (`MaterialIcons-Regular.*`, `default-skin.svg`, `preloader.gif`, …). That shape is `webpack-manifest-plugin`'s output. Vite's default `.vite/manifest.json` is keyed by **source path** with nested objects and is not interchangeable, so the migration must either emit the flat `assets.json` from a small post-build step or teach `ClientAssets.Load` the Vite shape. All editions (`plus`/`pro`/`portal`) read the same struct, so whatever shape is chosen must hold for every edition build.
**Feature-by-feature mapping** (from `webpack.config.js`):
| webpack today | Vite equivalent |
|-------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------|
| 3 entries `app`/`share`/`splash` | `build.rollupOptions.input` (3 inputs) |
| `output``../assets/static/build`, `[name].[contenthash].js`, `clean` | `build.outDir` + `emptyOutDir` + `entryFileNames`/`chunkFileNames`/`assetFileNames` with `[name].[hash]` |
| flat `assets.json` via `webpack-manifest-plugin` | `build.manifest` + post-build transform to the flat shape (see contract above) |
| `resolve.modules: [src]` + `preferRelative` | reuse the `resolve.alias` map already in `vitest.config.js` |
| `vue-loader`, `vue$` runtime alias, `whitespace: "preserve"` | `@vitejs/plugin-vue` (already a dep) + alias + `template.compilerOptions` |
| `webpack-plugin-vuetify` (auto-import) | `vite-plugin-vuetify` *(new dep)* |
| `babel-loader` + `@babel/preset-env` + `core-js` (browserslist Safari 13 / iOS 13) | `@vitejs/plugin-legacy` *(new dep; reads browserslist)* — esbuild alone won't downlevel to the iOS 13 baseline |
| `workbox-webpack-plugin` `GenerateSW` (`sw.js`, `importScripts` `sw-scope-cleanup.js`, `modifyURLPrefix: static/build/`, exclusions, 5 MiB cap) | `vite-plugin-pwa` *(new dep; workbox under the hood)* with the same options |
| CSS/PostCSS/Sass loader stack (`mini-css-extract-plugin`, `css-loader`, `postcss-loader`, `resolve-url-loader`, `sass-loader`) | Vite native CSS (reads `postcss.config.js`; keep the `sass` dep) |
| `DefinePlugin` Vue flags (`__VUE_OPTIONS_API__`, `__VUE_PROD_DEVTOOLS__`, `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__`) | Vite `define` |
| `EmitStaticFilePlugin` (copies `sw-scope-cleanup.js`) | `publicDir` or a small emit plugin |
| `hls.js``hls.light.min.js` alias | same `resolve.alias` entry |
| png/jpg/svg/font `asset/resource` | Vite asset pipeline — confirm the named manifest entries still land in `assets.json` |
| `CUSTOM_SRC`/`CUSTOM_NAME` edition overlay | env-driven `resolve.alias`/input |
| `webpack-bundle-analyzer` (`BUILD_ENV=analyze`) | `rollup-plugin-visualizer` |
| `make watch-js` (`webpack --watch`) | `vite build --watch`**not** the dev server; Go serves the embedded build output, so HMR isn't wired into the templates |
| `postbuild` `scripts/precompress.js` | unchanged; it operates on the output dir, not the bundler |
**Dependency churn.** Retire `webpack`, `webpack-cli`, `webpack-manifest-plugin`, `webpack-plugin-vuetify`, `workbox-webpack-plugin`, `eslint-webpack-plugin`, `webpack-bundle-analyzer`, `vue-loader`, and the loader stack (`babel-loader`, `css-loader`, `postcss-loader`, `resolve-url-loader`, `mini-css-extract-plugin`, `vue-style-loader`, `file-loader`, `url-loader`, `svg-url-loader`). Add `vite-plugin-vuetify`, `@vitejs/plugin-legacy`, `vite-plugin-pwa`, and `rollup-plugin-visualizer` (`vite` and `@vitejs/plugin-vue` are already deps). Then `make audit` and `make notice`.
**Validation.** `make build-js` emits `assets/static/build/assets.json` in the flat shape and the Go server renders the app/share/splash entrypoints without `cannot read assets.json`; `sw.js` is generated and the precompressed `.gz`/`.zst` siblings appear; smoke-test the Safari 13 / iOS 13 baseline; the `plus`/`pro`/`portal` edition builds still produce valid manifests; finally `make test-js` and the acceptance suite.
## Auditing for Orphaned Dependencies