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 on Ctor.gitlabModifiedOptions and merges a copy. Ctor.extendOptions identity and the _Ctor cache are unchanged because @vue/test-utils v1 matches instances through both.
  • patches/@vue+compat+3.5.42+018+shared-component-render.patch: the wrapper lives in a WeakMap keyed by the original render function and is read by finishComponentSetup(). Generated from gitlab-org/frontend/vuejs-core with scripts/generate-gitlab-patches.js; source commit in xanf/shared-component-render-3.5.42 (same commit pushed as xanf/shared-component-render-3.5.43 for 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.js mounts import-free SFCs through both runtimes directly (test-utils hands compat a copy, which hides the wrapper). Without the patches: 4/4 fail with VUE_VERSION=3, 1/1 fails in Vue 2 mode. With them: all green.
  • spec/frontend/vue3migration and spec/frontend/super_sidebar (60 suites) pass in both Jest modes.
  • Real page A/B on !256620 (merged) with its GlCollapse workaround reverted, Vue 3 sidebar and a licensed Status page settings app on Settings → Monitor: unpatched, the GlCollapse export gains directives with Vue 2's show and the Monitor section stays hidden; patched, the export is untouched, the Alerts link is visible and collapses again, no console errors.

MR acceptance checklist

Merge request reports

Loading
Loading