Stop Vue 2 and @vue/compat from mutating shared SFC exports
What does this MR do and why?
Related to the GlCollapse investigation in !256620 (merged) (see this note). It fixes the underlying mechanism instead of forcing a separate Vue 3 copy of one component.
Problem
A .vue file with no imports is not duplicated by the Vue 3 infection mechanism, so a single options object is extended by both the Vue 2 realm and the @vue/compat realm. Both runtimes write onto that object, and each write leaks into the other realm. Enumerating the write sites in vue@2.7.16 and @vue/compat@3.5.42 leaves two that change behaviour:
| Runtime | Write site | Trigger | Effect in the other realm |
|---|---|---|---|
| Vue 2 | resolveConstructorOptions() copies late-modified options into Ctor.extendOptions |
the second global Vue.mixin() after the component was extended (Vue.directive/Vue.component never trigger it; sealedOptions is never refreshed, so every object-valued option differs on the second pass). Plugins such as vue-apollo and Vuex install via mixin. |
The written-back directives object inherits Vue 2's globals. Compat resolves show from it and applies Vue 2's v-show to GlCollapse: Monitor toggles aria-expanded but the content stays display: none. |
@vue/compat |
convertLegacyRenderFn() replaces Component.render with a wrapper that substitutes compat's h |
any hand-written render(h) rendered by compat first |
Vue 2 instances of the same component render nothing. |
Everything else that writes onto the export (_Ctor cache, in-place normalisation of props/inject/directives, definition.name, async factory state) is benign for the other realm.
Fix
patches/vue+2.7.16.patch:resolveConstructorOptions()keeps the modified options onCtor.gitlabModifiedOptionsand merges a copy.Ctor.extendOptionsidentity and the_Ctorcache are unchanged because@vue/test-utilsv1 matches instances through both.patches/@vue+compat+3.5.42+018+shared-component-render.patch: the wrapper lives in aWeakMapkeyed by the original render function and is read byfinishComponentSetup(). Generated from gitlab-org/frontend/vuejs-core withscripts/generate-gitlab-patches.js; source commit inxanf/shared-component-render-3.5.42(same commit pushed asxanf/shared-component-render-3.5.43for the pending bump).
The alternative, treating every .vue file as exposed to Vue in the infection scanner, would duplicate 278 currently shared SFCs and everything they pull in.
Verification
spec/frontend/vue3migration/shared_sfc_mutation_spec.jsmounts import-free SFCs through both runtimes directly (test-utils hands compat a copy, which hides the wrapper). Without the patches: 4/4 fail withVUE_VERSION=3, 1/1 fails in Vue 2 mode. With them: all green.spec/frontend/vue3migrationandspec/frontend/super_sidebar(60 suites) pass in both Jest modes.- Real page A/B on !256620 (merged) with its
GlCollapseworkaround reverted, Vue 3 sidebar and a licensed Status page settings app on Settings → Monitor: unpatched, theGlCollapseexport gainsdirectiveswith Vue 2'sshowand the Monitor section stays hidden; patched, the export is untouched, the Alerts link is visible and collapses again, no console errors.
MR acceptance checklist
- I have evaluated the MR acceptance checklist for this MR.