Draft: Drop Vue 2 support from @gitlab/ui
⚠️ Disclaimer: 100% AI-generatedEvery line of code, test, and this description was written by Claude (Opus 5.5) in Claude Code, directed and supervised by @xanf, with no hand-written code. Correctness claims rest on machine-checked evidence (the native Vue 3 jest run, pixel comparison against Vue 2 references, the Cypress suite, dist checks), not on human review. This MR targets the bootstrap-vue template rewrite !6550, so the diff only shows the Vue 3 switch.
What this does
packages/gitlab-ui itself becomes Vue-3-only: the published @gitlab/ui is built from it for Vue 3 and runs on
native Vue 3 and on GitLab's @vue/compat runtime. There is no second package and no second package name. It
replaces the separate-package approach of
!6551 with the same code, applied in
place.
Every component sets compatConfig: COMPAT_CONFIG (MODE: 3, plus the compat features that change what the
component itself observes turned off), so it behaves on @vue/compat as on native Vue 3. v-model keeps working for
@vue/compat consumers through the components' Vue 2 model option (COMPONENT_V_MODEL).
Commits
- Run packages/gitlab-ui on Vue 3 only (tooling)
- Vue 3 is installed as the
@gitlab/vue3alias (npm:vue@3.5.32):vuein this monorepo is pinned to Vue 2 by the rootresolutionsfor the Nuxt 2 documentation site. Jest mapsvueto it, Storybook resolves itsvue/dist/vue.esm-bundler.jsto it, the build keepsvueexternal. - Vue Test Utils 2 (
@vue/test-utils),@vue/vue3-jest,rollup-plugin-vue@6. Thevuepeer dependency is^3.5.0. - Removed: Vue 2,
vue-template-compiler,@vue/compatand the patched@gitlab/hybrid-vue(with its 19 patches and the patch update script),portal-vue,vue-runtime-helpers, Vue Test Utils 1,@vue/vue2-jest,vue-router3, the Vue 2 Storybook framework,vue-loader15,merge-cobertura,use_vue3.js. - Jest: one native lane. SFCs are compiled like the build (
whitespace: 'preserve', no comments).vue-test-utils-compatkeeps the Vue Test Utils 1 API the specs use, and runtime-compiled spec templates getv-modelmapped to themodeloption, as@vue/compatdoes for GitLab. - Build: same per-file ESM dist, same entry points, same CSS output.
yarn check:dist(new) checks the syntax level, leftover.vue/style imports, and that every name read fromvueis a Vue 3 export (webpack and Rspack check export presence). - Storybook on the Vue 3 framework. Story templates are compiled with preserved whitespace, like the library's.
- ESLint: the Vue 3 portability guard is removed.
- The documentation site (Nuxt 2, Vue 2) depends on
npm:@gitlab/ui@138.7.1from the registry: it keeps showing the last Vue 2 release until it moves off Nuxt 2. - A
majorchangeset and the FAQ entry describe the consumer changes below.
- Vue 3 is installed as the
- Collapse the Vue version shim to its Vue 3 branch:
vue_utils.js,root_events.jsandvue_teleport.jskeep their helper API, so components need no change. Only the Vue 3 branches stay, including the handling of@vue/compatconsumers (.nativelisteners, the$rootevent bus, the own-event bridge, thevModelDynamicbackfill). The Vue 2Teleportand the Vue 2SlotOutletare deleted.SlotOutletstays as the Vue 3 component: each remaining use renders a slot function of another instance, which a template cannot do without it. - Apply the Vue 3 codemods to the gitlab-ui sources: lifecycle renames,
$scopedSlots→$slots, Vue 3 directive hooks, componentv-modeldesugaring in the library templates, functional → stateful,portal-vue→Teleport,compatConfigpins (bin/apply_compat_config.mjs, checked byyarn lint:compat-config). - Make the gitlab-ui components and stories native Vue 3: listener, attribute, class and style forwarding through
the shim helpers,
nullinstead offalsebindings,ARRAY_WATCH_OPTIONS,setReactive, the Vue 3 transition prop names inGlCollapse,base_dropdown.spec.jsonTeleport. Stories: componentv-modeldesugared, global registrations through Storybook'ssetup(), plain elements as portal targets. - Run the gitlab-ui CI jobs on Vue 3 only: one lane for unit tests, the test Storybook, visual and integration
tests (the vue2/vue3/hybrid duplicates and the coverage merge job are gone).
ui:build_packagealso runsyarn check:dist. - Filter fallthrough listeners by key in GlButton and GlForm:
getNonFallthroughListenersand GlForm'slisteners()leave a listener to the fallthrough when its key is in$attrs, not when the handler is identical. With consumer templates compiled by the Vue 3 compiler, a skipped child update replaces the vnode with a new handler array for the same event, and the identity check bound the listener a second time (Enter ran the handler twice).
Evidence
Local, on the branch head:
| Check | Result |
|---|---|
| Jest (native Vue 3, the only lane) | 202/202 suites, 6254 passed, 32 skipped (the isNativeVue3 gates of @vue/compat-only cases) |
yarn build |
249 dist files: the 248 files of the Vue 2 build of the same commit plus utils/compat_config.js; index.css and tailwind.css byte-identical |
| Exported names | identical in all 248 common dist files (export * resolved) |
yarn check:dist |
249 files parse as ES2019 modules (the level the Vue 2 dist already had), read only Vue 3 exports, no .vue/style imports |
| Visual (test-storybook, Firefox) | 491/491 stories match the Vue 2 references rendered in the same environment |
| Cypress (Chrome) | 77/77 |
ESLint, Prettier, markdownlint, yarn-deduplicate |
clean |
| Site | unit tests 199/199, yarn build OK (on the 138.7.1 release) |
CI: pipeline 2978864170 (merged results) passed:
| Job | Result |
|---|---|
| ui:unit_tests | 202/202 suites, 6254 passed, 32 skipped |
| ui:build_package | check:dist OK, tarball with the same 780 files as the Vue 2 package plus src/dist utils/compat_config.js |
| ui:visual 1/2, 2/2 | 491 stories match the committed references (no reference regenerated) |
ui:integration_tests (cy:run, cy:edge) |
passed |
| lint, ui:lint, untamper-my-lockfile, site:build, site:unit_tests, site:e2e_dev, site:e2e_prod | passed |
| ui:duo_job | failed, allowed (duo-ui-allowed-to-fail label): duo-ui still runs Vue 2 and its own compat harness, which a Vue-3-only @gitlab/ui does not support |
Tarball for consumers:
https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/jobs/17400220445/artifacts/raw/packages/gitlab-ui/gitlab-ui.xanf-gitlab-ui-vue3-in-place.tgz
What consumers must change
- Vue 2 apps stay on the 138 major version.
- Native Vue 3: bind the component's model prop and event instead of
v-model(:value+@inputunless the component'smodeloption says otherwise, for example:selected+@selectonGlCollapsibleListbox).@vue/compatkeepsv-modelworking. - Code that reads
$listenersor$scopedSlotsof a library component, or emitsopen/closeonGlTooltipandGlPopover, only works on@vue/compatwith those features enabled for the caller: use$attrs,$slotsand the methods..nativelisteners only work on@vue/compat. - Directives define the Vue 3 hooks only. Code that calls a hook directly (
GlResizeObserverDirective.bind(...)) callsbeforeMount/unmountedinstead.GlTooltipDirectivealso keepsbind,componentUpdatedandunbind. - Specs that stubbed
MountingPortal/PortalTargetstubteleport. The filtered search token segment still registers its teleport asPortal. - duo-ui has to move to Vue 3 before it can take this package.