Draft: Run GitLab on Vue 3 with a Vue-3-only @gitlab/ui (do not merge)

⚠️ Disclaimer: 100% AI-generated

Every 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/ui that 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/ui needs on top of it.
  • @gitlab/ui side: no Vue 2. @gitlab/ui is the tarball that ui:build_package builds for the design.gitlab.com Draft MR gitlab-org/gitlab-services/design.gitlab.com!6556 (branch xanf/gitlab-ui-vue3-in-place). That MR makes packages/gitlab-ui itself 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-vue3 alias.

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 vue peer dependency is ^3.5;
  • its directives use the Vue 3 hook names (beforeMount, unmounted, ...);
  • it declares the events its components emit;
  • GlSprintf, GlIntersperse, GlFriendlyWrap and GlSkeletonLoader are stateful components;
  • it no longer depends on portal-vue, vue-functional-data-merge or vue-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:

  • RefSelector forwarded the listeners of the events it emits itself to GlCollapsibleListbox.
  • tooltip_on_truncate and NoteAgentActorLine called directive hooks by their Vue 2 names.
  • createAlert, renderMath and renderVueComponentForLegacyJS passed 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-vue3 and their vue3 duplicates;
  • all asset compiles: test, production, Rspack and vue3;
  • every RSpec job, including rspec system pg17 (27/27) and rspec-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

🤖 Generated with Claude Code

Edited by Illya Klymov

Merge request reports

Loading
Loading