Force infection for pass-through modules above app roots
What does this MR do and why?
The infection scanner computes one infected flag, using an app-root barrier. A module
whose only Vue-touching import is an app root is therefore marked clean, which is the
correct answer to the upward question: the module holds no Vue itself.
createIsInfectable reuses that flag for the downward question, where the answer
differs. When a Vue 3 module imports such a pass-through, the copy must be Vue 3.
Because it is not, the importer resolves the clean copy, everything below it also
resolves clean, and the subtree runs Vue 2 inside a page meant to run Vue 3. Every
module-scope singleton down there becomes two objects, so the two halves of the page
read different state.
On ee/app/assets/javascripts/pages/projects/issues/show:
V3 ee/app/assets/javascripts/pages/projects/issues/show/index.js
V3 app/assets/javascripts/issues/index.js
V2 app/assets/javascripts/issuable/index.js <- clean sink
V2 app/assets/javascripts/sidebar/mount_sidebar.js
V2 app/assets/javascripts/batch_comments/store/index.jsEverything in that chain is already under one feature flag, so grouping apps under a shared flag does not fix it.
What this MR changes
- Adds
INFECTION_FORCELISTtoconfig/helpers/context_aliases_shared.js: the four pass-through modules that a Vue 3 importer must resolve a Vue 3 copy of, so that infection continues past them rather than stopping. - Resolves and validates that list in
config/helpers/vue3_infection_shared.js, next tocreateIsInfectable, its only caller. The constants module stays data only, because every webpack, vite, rspack and jest invocation requires it and so it must not touch the filesystem or throw. createIsInfectableconsults the list after the blocklist and before the graph. The blocklist wins, and a path on both lists fails at load rather than resolving by check order.- Entries are matched as exact repo-relative paths, not substrings. A path that does not exist fails at load, so a moved entry cannot become silently dead.
- Entries for an edition the checkout lacks are dropped rather than validated:
ee/is absent from the FOSS mirror,jh/from CE and EE checkouts.
mr_notes/mount_app.js is listed for a page that has no vue3_migration.yml yet. It is
what unblocks that migration, so it is here rather than in the migration MR.
Measured effect
Simulated through the real createIsInfectable, against a scanner graph regenerated on
this branch, on both CE (FOSS_ONLY=true) and EE resolution:
| page | without the list | with the list |
|---|---|---|
pages.projects.merge_requests.rapid_diffs |
11 split singletons | 0 |
pages.projects.merge_requests.show |
11 | 0 |
ee/.../pages/projects/issues/show |
6 | 0 |
No other migrated page's Vue 3 graph changes size, except issues/show, which grows from
790 to 863 modules.
How to verify
node scripts/frontend/infection_scanner/infection_scanner.mjs
FOSS_ONLY=true node scripts/frontend/infection_scanner/infection_scanner.mjs
yarn vitest:infection-scanner!252671 (closed) runs the blocked migration
on top of this branch, so its ~"pipeline::tier-3" result is the end-to-end check.
No changelog entry: build tooling, with no user-facing change until a page migration flag is enabled.
References
- Fixes #625296 (closed)
- Analysis: #625296 (comment 3760799545)
- Unblocks !252389 (merged)
- Blocked migration issue: #611465
- End-to-end verification: !252671 (closed)
- Detects this class of bug at build time: !252666 (merged)
- Supersedes this list: !252667 (merged)
- Runs this MR's specs in CI: !252664 (closed)