Draft: Run GitLab on Vue 3 with a Vue-3-only @gitlab/ui (do not merge)
⚠️ Disclaimer: 100% AI-generatedEvery line of this MR was written by Claude (Opus 5.5) in Claude Code, directed and supervised by @xanf. Do not merge. It shows what GitLab needs to run on a
@gitlab/uithat supports Vue 3 only.
What this is
GitLab on Vue 3, with a @gitlab/ui that dropped Vue 2.
- GitLab side: no Vue 2. This MR is stacked on !263207
("[POC] Remove Vue 2", by @mrincon). That POC builds and tests GitLab with Vue 3 and the Vue 3 template compiler
by default, with the Vue 2 packages removed. This MR targets its branch
634491-remove-vue2-poc, so the diff shows only what a Vue-3-only@gitlab/uineeds on top of it. @gitlab/uiside: no Vue 2.@gitlab/uiis the tarball thatui:build_packagebuilds for the design.gitlab.com Draft MR gitlab-org/gitlab-services/design.gitlab.com!6556 (branchxanf/gitlab-ui-vue3-in-place). That MR makespackages/gitlab-uiitself Vue 3 only. It builds on the template rewrite gitlab-org/gitlab-services/design.gitlab.com!6550, which replaced the vendored bootstrap-vue render functions with templates. There is no second package and no@gitlab/ui-vue3alias.
GitLab still compiles @gitlab/ui from source (@gitlab/ui → @gitlab/ui/src) with the POC's Vue 3 template
compiler. Every @gitlab/ui component pins compatConfig: { MODE: 3, ... }, so on GitLab's @vue/compat runtime
it behaves as on native Vue 3. Consumer-facing changes of the package:
- the
vuepeer dependency is^3.5; - its directives use the Vue 3 hook names (
beforeMount,unmounted, ...); - it declares the events its components emit;
GlSprintf,GlIntersperse,GlFriendlyWrapandGlSkeletonLoaderare stateful components;- it no longer depends on
portal-vue,vue-functional-data-mergeorvue-runtime-helpers.
Changes on top of the POC
| Commit | Why |
|---|---|
Use the Vue-3-only @gitlab/ui build of the template rewrite |
package.json and yarn.lock point at the tarball, the same way the GitLab UI integration MRs do. |
| Pass component children as function slots | createAlert, renderMath and renderVueComponentForLegacyJS passed arrays and strings as children. Components that opt out of the RENDER_FUNCTION compat warn about them. |
Call the Vue 3 hooks of the @gitlab/ui directives |
tooltip_on_truncate and NoteAgentActorLine called GlTooltipDirective.bind and GlResizeObserverDirective.bind directly, which now throws. The global Jest tooltip mock and the mock directive helper follow the Vue 3 hook names. |
| Stop forwarding RefSelector's own listeners to the listbox | Its input listener fell through to the listbox root, and typing in the search box navigated to a broken URL (find file, network graph, commits, ...). |
| Six spec fixes | The specs relied on bootstrap-vue internals: the HTML of a hidden lazy modal, the $refs.dropdown of GlDropdown, table rows and header cells as components, the GlTab prop declaration, and a DevOps adoption spec that needed real tabs. The same fixes are in !264170. |
| Trigger DOM events instead of faking component events in specs | 13 specs ran vm.$emit with events the component never emits: focus, blur, mouseover, mouseout, transitionend or invalid on a GlFormInput or GlButton; click or show on GlCollapsibleListbox; and selected and input where the listbox emits select and search. With declared emits, Vue warns. |
Keep the stubs of former functional @gitlab/ui utilities |
The stub factory renders only the default slot of a functional component. Snapshots and text assertions around GlSprintf encode that. |
| Update a GlTabs snapshot for the Vue 3 transition class props | The transition stub inside GlTabs has enterfromclass and leavefromclass now. This is the only snapshot affected. |
Nothing else changes. The POC keeps the portal-vue alias, because GitLab's own code and @gitlab/duo-ui still
import portal-vue.
GitLab code that was not Vue-3-ready
Fixed here:
RefSelectorforwarded the listeners of the events it emits itself toGlCollapsibleListbox.tooltip_on_truncateandNoteAgentActorLinecalled directive hooks by their Vue 2 names.createAlert,renderMathandrenderVueComponentForLegacyJSpassed non-function children to components.- Specs that depended on the internals listed above, or on the functional stubs.
Still on Vue 2 APIs: @gitlab/duo-ui is still a Vue 2 library that runs on @vue/compat. Its dependencies include
portal-vue 2. The POC's task list covers the other remaining Vue 2 libraries.
Evidence
Local (this branch, @gitlab/ui from the tarball, yarn install --frozen-lockfile):
| Check | Result |
|---|---|
Rspack compile (rspack build, Vue 3 and its compiler by default) |
0 errors, 2 warnings (asset and entrypoint size limits) |
Jest sample: every spec that imports a component the template rewrite changed (GlModal, GlFormGroup, GlTable, GlTableLite, GlTabs, GlTab, GlTooltip, GlPopover, GlFormSelect, GlDropdown*), plus the specs this MR changes: 1004 files, --maxWorkers=4 |
1020/1020 suites, 21588 passed, 42 skipped, 0 failed, 81/81 snapshots |
Full Jest suite (jest.config.js, --maxWorkers=4) |
6694/6695 suites, 112008 passed, 5 failed, 525/525 snapshots. The 5 failures are all in draggable_compat_spec. They fail the same way on the POC with the released @gitlab/ui 138.7.0 locally, and they pass in CI. |
System specs did not run locally, because no GDK is set up for this worktree. They run in CI with pipeline:run-all-rspec.
CI: pipeline https://gitlab.com/gitlab-org/gitlab/-/pipelines/2978905418, run on the rebuilt package (ui:build_package job 17400220445). 349 jobs passed. They cover:
- all Jest jobs:
jest,jest-with-fixtures,jest-integration,jest-snapshot-vue3and theirvue3duplicates; - all asset compiles: test, production, Rspack and
vue3; - every RSpec job, including
rspec system pg17(27/27) andrspec-ee system pg17(15/15).
Two system examples failed once and passed on retry, and their pass on retry marks them as flaky: user_activates_issue_tracker_spec.rb:40 and user_avatar_spec.rb:23.
The only failed job is untamper-my-lockfile. It fails on every GitLab UI integration MR, because it cannot parse the tarball URL in yarn.lock.
An earlier pipeline found a @gitlab/ui bug, which the rebuilt package fixes. After the parent of a GlButton re-rendered without changing its props, GlButton bound an array of keydown listeners a second time, and Enter pinned and then unpinned a sidebar item (pinned_nav_items_spec.rb:163). The package now filters the fallthrough listeners by key.
Labels
pipelinetier-3, pipeline:run-all-jest and pipeline:run-all-rspec, as on the POC.
References
- POC this MR is stacked on: !263207
- Vue-3-only
@gitlab/ui: gitlab-org/gitlab-services/design.gitlab.com!6556 - Template rewrite: gitlab-org/gitlab-services/design.gitlab.com!6550
- Integration run of the template rewrite on Vue 2: !264170