Make Rspack output independent of the checkout path
What does this MR do and why?
Makes the production Rspack build give the same output wherever the checkout is. Today, building the same tree in two directories gives different Rspack file names and contents. The build is not reproducible, and the Nix build of the assets in !262215 can't match the file names of build-assets-image.
Two causes, one commit each:
- Keep checkout paths out of Vue 3 lane imports.
config/vue3migration/plugins/rspack_loader.jsrewrote each?vue3import to the target's absolute path. Rspack sizes a module by the loader's output, andsplitChunksplaces modules by size (minSize: 150000), so a longer checkout path moved modules between chunks. The loader now writes the path relative to the importing file. It resolves to the same file and query, so all importers still share one?vue3module. A new spec,spec/frontend/config/vue3migration/plugins/rspack_loader_spec.mjs, checks both. - Mangle Rspack output without char frequency. Rspack minifies the runtime while its chunk table still holds provisional hashes; the real content hashes replace them afterwards. SWC picks short variable names by how often each character appears, and it counts those hashes too. With source maps on, the provisional hashes depend on the absolute source paths, so the runtime's variable names, and with them its file name, change with the directory (https://github.com/web-infra-dev/rspack/issues/14089).
mangle.disableCharFreqmakes SWC pick names in a fixed order. The minimizer list is otherwise Rspack's default (SwcJsMinimizerRspackPlugin,LightningCssMinimizerRspackPlugin).
webpack is unchanged. The Rspack output changes once, to the path-independent layout.
Size, Rspack JS of tree 3c50039f built in /builds/gitlab-org/gitlab without source maps, gzip level 9:
| Build | Raw (B) | Gzip (B) | Gzip vs before |
|---|---|---|---|
| Before | 156,361,111 | 42,145,298 | |
| Commit 1 | 156,533,988 | 42,196,325 | +0.12% |
| Commits 1 and 2 | 156,534,008 | 42,299,520 | +0.37% |
Commit 1's change comes from the new chunk layout; commit 2's from the fixed name order.
References
- Nix build of the EE assets, which needs this: !262215
- The requirement is described in ADR-0045: gitlab-org/software-supply-chain-security/hardened-builds!234 (merged)
- SWC char-frequency mangling and provisional hashes in Rspack: https://github.com/web-infra-dev/rspack/issues/14089
How to set up and validate locally
- Run the loader spec:
yarn vitest:vue3migration --run. Steps 2 to 5 check the whole build. - Install and build the islands once:
NODE_ENV=production yarn install --frozen-lockfile, thenscripts/build_frontend_islands. - Build in the checkout and keep the output:
NODE_ENV=production FE_SOURCEMAPS=true SKIP_FRONTEND_ISLANDS_BUILD=true yarn run rspack-prod mv public/assets/webpack /tmp/rspack-a - Copy the checkout,
node_modulesincluded, to a path of a different length (cp -a), run the same build there, and move itspublic/assets/webpackto/tmp/rspack-b. diff -rq /tmp/rspack-a /tmp/rspack-bprints nothing. Onmasterthe names differ: 1079 of 5568 top-level files for a 57- vs a 107-character path with source maps.
Validation so far
- This MR's CI: pipeline 2954747770 passed, including
test-vue3migration-tooling(the new spec), the fourcompile-production-assetsjobs, Jest for Vue 2 and Vue 3, predictive RSpec ande2e:test-on-cng.bundle-size-review(allowed to fail) failed on a webpack chunk-name conflict thatmasterhas had sinceee4b06d1dc; webpack doesn't use this loader. - Nix comparison: pipeline 2952009663 of !262215 carries the same loader and Rspack config. Its Nix build, in a random
/nix/var/nix/builds/...directory, has the same 1696 Rspack file names and 940 entrypoints as a freshcompile-production-assets-rspackcompile in/builds/gitlab-org/gitlab(bothruntime.fa5b7cab.bundle.js). Before these commits, 234 names differed (2950755954). - Linux (
node:22.23.1-bookworm, arm64), tree3c50039fwith both commits: byte-identical output in/builds/gitlab-org/gitlab,/build/sourceand/nix/var/nix/builds/nix-12015-2632109856/source, with source maps (10,531 files) and without (7,859 files). - Before the change, the same container reproduces the mismatch name for name. In
/builds/gitlab-org/gitlabit gives the 1692 Rspack names ofgitlab-assets-ee:3c50039ff30f7a59985013e317bb78559e45740f(compile-production-assets-rspack), and in the Nix build directory the 1692 names ofgitlab-assets-ee-nix:3c50039ff30f7a59985013e317bb78559e45740f. 237 names differ between them. - Same module graph: 10,293 module identifiers before and after commit 1, 1705 of them
?vue3modules. - macOS arm64: byte-identical output at two paths (57 and 107 characters).
- Spec: the 15 tests of
rspack_loader_spec.mjspass, and 11 of them fail againstmaster's loader.yarn vitest:vue3migration: 193 tests pass. ESLint and Prettier are clean. - Not validated yet: loading a Vue 3 page from the Rspack build in a browser. The module graph is unchanged, but the chunks that carry it are not.
e2e:test-on-cngserves the webpack build, becauserspack_production_assetsis off by default.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.