From b61345584b0d68ab8645b3b47ed80d5e6518e77a Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 22 Jun 2026 20:17:34 +0000 Subject: [PATCH] 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. --- frontend/README.md | 49 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index dbf5a0a33..162ca835b 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -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 `` 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