Draft: Replace the render-function bootstrap-vue implementation with Vue-3-portable templates
Replace the render-function bootstrap-vue implementation with Vue-3-portable templates
⚠️ 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 (markup/event parity fixtures rendered by
main, the unit suites, Storybook pixel comparison, the Cypress suite, and GitLab's full jest + rspec suites via the integration MRs), not on human review.This is one draft MR on purpose: it carries the whole series so it can be evaluated end to end. Every commit is one future small MR (see Splitting strategy). Status: complete. 87 commits on
mainbbb9a511ed; the vendoredsrc/vendor/bootstrap-vuedirectory is gone.
What this does
Removes the vendored bootstrap-vue (BSV) render-function / functional / mixin implementation from @gitlab/ui
and replaces it with Options-API SFCs with <template>s, written so that the library can move to Vue 3 without
@vue/compat with minimal changes.
- The vendored tree is deleted. No importer is left,
dist/vendoris gone andnpm packships no bootstrap-vue path. The derived SCSS stays insrc/scss/bootstrap_vue, with the BootstrapVue license notice next to it.vue-functional-data-mergeis dropped (popper.jsandportal-vuestay,srcuses them), and so are theui:bootstrap-vueCI jobs. - Where the code went. Dropdown and its items, tabs, form select, tooltip, popover and the tooltip directive
are inlined into their GlX SFC. Modal, table, table lite and form group keep an internal template SFC with the
BSV
name(BModal,BTable,BTableLite,BFormGroup), so consumer test contracts (stubs,findComponent({ name }),$children[0]) keep working. - Pure refactor. No prop, event or slot is removed. 45 changesets: 41 patch, 4 minor (new public methods and
the GlTable items provider type), no major. Vue 2 output is identical modulo comment placeholders and the
deviations under Known differences; under
@vue/compatbehaviour only moves towards Vue 2. - Size. 533 files, +49 434 / −51 984 against
main.
Companion MRs: the native Vue 3 package !6551 and the GitLab MRs under Consumer follow-ups.
How the code stays portable
- One version shim,
src/utils/vue_utils.js, holds every Vue-version-specific line: attrs and listeners (getAttrs,getListeners,getNonFallthroughListeners,getUndeclaredRootListeners,getRootClassStyleincl. compat.nativerouting,getComponentRootClassStyle), slots (getSlot,hasSlot,getNormalizedSlot,SlotOutlet, …), uid, reactive set/delete,ARRAY_WATCH_OPTIONS, scope id,isNativeVue3,defineDirective,mountComponent, transition class props, owner events, and thevModelDynamicbackfill from the vendoredvue.js. Branches select at runtime; under@vue/compatthe native Vue 3 branch runs, so the existing Vue 3 jest lane exercises it. - Root event bus (
bv::*, ~110 call sites in GitLab) isolated insrc/utils/root_events.js(Vue 2/compat: the$rootbus; pure Vue 3: a per-app emitter). src/utils/vue_teleport.js: Teleport shim. Its Vue 2 branch is the ported BVTransporter; its Vue 3 branch renders the built-inTeleportonce the target exists and, like portal-vue, nothing (and no warning) before.- Render functions left: only inside the shims, namely the Teleport shim and
SlotOutlet. The Vue 2 Teleport branch andSlotOutletare deleted at the Vue 3 switch (built-inTeleport, native<slot>). - Lint guard:
PORTABLE_VUE_FILESineslint.config.mjs(39 entries, every file that replaced vendored code) forbids render functions,functional, mixins,extendsand Vue-2-only instance APIs outside the shims. - Template rules, avoided explicitly: keyed
<template v-for>(the two compilers disagree),.native,.sync, falsy attribute bindings, accidental whitespace text nodes (strict-whitespace prettier override where markup is whitespace-sensitive), refs insidev-for(normalised: unpatched compat ignoresrefInFor), slot presence cached in computeds, template refs named like state.
Evidence (tip 9224b1a3bb)
| Layer | Result |
|---|---|
| Unit suite, Vue 2 | 6255 passed / 1 skipped |
Unit suite, Vue 3 @vue/compat |
6434 passed / 3 skipped |
Markup + event parity vs fixtures rendered by main (Vue 2), 1615 scenarios |
0 strict differences; 24 accepted (intended, see Known differences); 101 HTML-snapshot-only (comment placeholders) |
| Same, Vue 3 compat lane | 0 strict differences; 581 scenarios converge to Vue 2; 43 accepted; 814 snapshot-only |
| Hybrid lane (Vue 2-compiled templates on the unpatched compat runtime) | 0 new render errors; 38 errors that main has are gone |
| Storybook pixel comparison, 491 stories × vue2 / vue3 / hybrid, 1 px threshold | 491/491 ×3 |
| Cypress e2e (vue2 / vue3 / hybrid) | 77/77 ×3 |
| Build and dist | yarn build ok; 248 JS files, none under vendor/, every relative import resolves; compared with main's dist only dist/vendor (removed), 9 utility modules and 4 Storybook-only tailwind.css rules differ; yarn install --frozen-lockfile ok |
| Lint and messages | eslint, prettier, stylelint (0 errors), markdownlint clean; subjects and body lines ≤ 72; Danger emoji check clean |
| design.gitlab.com CI | green: https://gitlab.com/gitlab-org/gitlab-services/design.gitlab.com/-/pipelines/2977268471 (only the allow-failure code_quality infra job fails) |
| GitLab full jest + rspec | green, see GitLab |
Every commit passes jest (both lanes), parity, the hybrid lane and lint on its own.
GitLab
GitLab with this series' package (ui:build_package of this MR) and the consumer changes listed under Consumer
follow-ups, with pipeline:run-all-jest and pipeline:run-all-rspec:
gitlab-org/gitlab!264170, pipeline
https://gitlab.com/gitlab-org/gitlab/-/pipelines/2977009348.
| Suite | Result |
|---|---|
| jest (Vue 2 11/11, Vue 3 compat 11/11, with fixtures 2 + 2, integration 1 + 1) | all green |
| rspec (unit, integration, migration, background migration, frontend fixtures, clickhouse/es9 variants) | all green |
rspec system pg17 / rspec-ee system pg17 |
27/27, 15/15 |
| e2e child pipelines | green |
Red jobs, unrelated to this change and red on every integration MR: untamper-my-lockfile (cannot parse the package
tarball URL in yarn.lock), the start-as-if-foss bridge, and allow-failure bundle-size-review /
pajamas_adoption. Commit 87 is spec-only and does not change the package.
Native Vue 3
Package !6551
(packages/gitlab-ui-vue3): derived from this branch by a sync script plus codemods (lifecycle renames,
compatConfig pins, shim on native Vue 3), jest (vue3-jest + VTU 2) and rollup, no @vue/compat. Synced from this
series (no vendored code left):
| Check | Result |
|---|---|
| Native jest | 195/195 suites, 6098 passed / 0 failed; the 32 skips are the specs' own native gates (.native, Vue.extend, $root.$on) |
| Hand-edited specs | 1 (base_dropdown.spec.js); every other spec runs as written here |
| Parity vs the Vue 2 fixtures (140 modules) | 97 identical, 43 differ; every difference is what this series renders under @vue/compat or inherent to native Vue 3 (.native, attributes bound to false, legacy self-events) |
| Build and CI | 247 dist files, ES2018 check ok; ui-vue3:unit_tests and ui-vue3:build green |
GitLab Vue 3 MR gitlab-org/gitlab!264182, the analogue of
gitlab-org/gitlab!246445 (closed) on the new package (no test-runner migration): assets
compiled against the package, pipeline:run-rspec-vue3, a native jest lane. Pipeline
https://gitlab.com/gitlab-org/gitlab/-/pipelines/2977350195 is green, against 246445's latest
https://gitlab.com/gitlab-org/gitlab/-/pipelines/2688844024:
| Lane | gitlab MR 264182 (this rewrite) | gitlab MR 246445 (hand-ported package) |
|---|---|---|
compile-test-assets vue3 (+ production vue3 compiles) |
green | green |
rspec system pg17 vue3 |
27/27 shards | 29/32 |
rspec-ee system pg17 vue3 |
15/15 shards | 15/16 |
native jest (jest vue3 gitlab-ui-vue3 + fixtures) |
13/13 shards | not in CI |
| compat jest baseline | 13/13 shards | 13/13 |
GitLab-side changes in that MR (Vue 2 behaviour unchanged): RefSelector does not forward its own input/hide
listeners to the listbox (on Vue 3 they would fall through to the dropdown root), note_agent_actor_line and
tooltip_on_truncate call the directive hooks of the running Vue version, plus the spec changes G1–G7.
Splitting strategy
Each row is one future MR, and every commit is green on its own.
- Order. Rows follow the branch order (
git log --reverse main..), which is one valid merge order. After the foundation (rows 1–15, needed by every later row), the families can be reviewed in parallel. - ID. Family prefix and number (F foundation, FS/FG forms, TS tabs, DD dropdown, MD modal, TT tooltip, TB table, PX native Vue 3 portability, X removal).
- Depends on. What a row needs beyond the foundation: shim helpers another family added, or files an earlier commit changed.
- Ship together. MD4 + MD5 (MD4 alone leaves a compat-only portal divergence), TB7 + TB8 (shared changeset
wording), TS3 + TS4 (TS3 drops an empty nav-slot
<!---->that TS4 restores). - GitLab first. DD12 needs G2 merged in GitLab first.
- GitLab MR needed? G1–G7 are in gitlab-org/gitlab!264170.
Foundation: utilities out of the vendored tree, version shim, root events, mount helper, Teleport shim (rows 1–15)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 1 | F1 | Lint migrated components for Vue 3 portability 50a2100aa9 |
1, +98/−0 | — | no |
| 2 | F2 | Move bootstrap-vue spec helpers to tests/__helpers__ eec2eba4b9 |
26, +75/−68 | — | no |
| 3 | F3 | Remove dead bootstrap-vue config, plugin and locale utilities 27b22d8804 |
14, +15/−660 | — | no |
| 4 | F4 | Use existing src/utils equivalents inside vendored bootstrap-vue 56025a0a29 |
21, +21/−294 | — | no |
| 5 | F5 | Move bootstrap-vue DOM helpers to src/utils/dom_utils.js 07b1c224c4 |
31, +956/−753 | F3, F4 | no |
| 6 | F6 | Move BvEvent to src/utils 33b279d36a |
10, +262/−131 | F5 | no |
| 7 | F7 | Move shared toInteger and key-code helpers to src/utils 406823d1f4 |
24, +74/−119 | F4–F6 | no |
| 8 | F8 | Relocate bootstrap-vue SCSS into src/scss/bootstrap_vue 4b6daec7dc |
18, +27/−39 | — | no |
| 9 | F9 | Add Vue 2/3 version shim helpers to vue_utils 26d151886e |
8, +984/−46 | — | no |
| 10 | F10 | Add root event bus adapter 66d3aaa1fc |
11, +762/−13 | F9 | no |
| 11 | F11 | Add programmatic mount helper and use it for GlToast 249acebf14 |
6, +330/−18 | F9, F10 | no |
| 12 | F12 | Add portable transition class helpers and fix GlToast leave class 3419202a50 |
6, +350/−11 | F11 | no |
| 13 | F13 | Replace BVTransporter with a Teleport shim e756c8cda0 |
9, +650/−341 | F9, F11 | no |
| 14 | F14 | Move bootstrap-vue event listener helpers to src/utils 2b015e34ae |
32, +277/−216 | F5, F10, F13 | no |
| 15 | F15 | Route compat native listeners to the component root in the version shim c1fb3d6c95 |
2, +345/−16 | F9 | no |
Forms: GlFormSelect and BFormGroup (FS, FG)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 16 | FS3 | Make GlFormSelect portable 7bf0c2fcc3 |
4, +157/−13 | — | no |
| 17 | FS4 | Delete orphaned vendored form mixins b241ac54a8 |
8, +5/−212 | FS3 | no |
| 18 | FG1 | Inline BFormText and BForm(In)validFeedback into BFormGroup fc73ef576e |
16, +230/−830 | — | no |
| 19 | FG2 | Replace BCol and BFormRow with plain elements in BFormGroup 97642cb077 |
18, +226/−1354 | FS4, FG1 | no |
| 20 | FG3 | Rewrite BFormGroup as an internal template SFC f715661771 |
25, +2010/−2095 | FG2 | no |
| 53 | FG4 | Route GlForm and GlFormGroup listeners through the version shim f01687bdba |
8, +448/−8 | FG3, TB15 | no |
Tabs (TS)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 21 | TS1 | Add GlTabs and GlTab behaviour specs ported from vendored specs afa0152b3c |
2, +1264/−2 | — | no |
| 54 | TS2 | Make GlScrollableTabs listener and attr forwarding portable 5984426178 |
4, +140/−8 | TB15 | no |
| 55 | TS3 | Inline BTabs into GlTabs 518b6e1ed9 |
9, +1081/−89 | TS1, MD5 · ship with TS4 | yes: G5 |
| 56 | TS4 | Inline BTab into GlTab 5732eff246 |
11, +1072/−117 | TS3, TB15 | yes: G4, G6 |
| 57 | TS5 | Delete vendored bootstrap-vue tabs 34c9de8e83 |
15, +42/−2849 | TS4 | no |
Dropdown (DD)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 22 | DD1 | Drop dead Vue.extend wrapper from GlDropdown 40fd329038 |
2, +88/−20 | — | no |
| 23 | DD2 | Delete unused vendored BDropdownGroup 5eb8f29aa4 |
7, +2/−256 | — | no |
| 24 | DD3 | Inline BDropdownDivider into GlDropdownDivider 4662493c01 |
10, +250/−107 | DD2 | no |
| 25 | DD4 | Inline BDropdownHeader into GlDropdownSectionHeader 5398514f6f |
10, +330/−155 | DD3 | no |
| 26 | DD5 | Inline BDropdownText into GlDropdownText a35838fde9 |
10, +314/−136 | DD4 | no |
| 27 | DD6 | Inline BDropdownForm and BForm into GlDropdownForm cbf3ed943a |
16, +442/−708 | FG2, DD5 | no |
| 28 | DD7 | Remove .native listeners on GlDropdownItem in GlFormCombobox d4590294d8 |
3, +145/−7 | — | no |
| 29 | DD8 | Add public updatePopper method to GlDropdown bd6732968b |
3, +97/−0 | DD1 | adds the API G2 uses |
| 30 | DD9 | Inline BDropdownItemButton into GlDropdownItem f25d8b9f67 |
9, +581/−288 | DD6 | no |
| 58 | DD10 | Inline BDropdownItem link rendering into GlDropdownItem 39cb315ebe |
15, +1086/−343 | DD9 | no |
| 66 | DD11 | Port the vendored BDropdown spec onto GlDropdown a32ec12e66 |
2, +1353/−1178 | DD8, DD10 | no |
| 70 | DD12 | Inline BDropdown and its mixins into GlDropdown ac7a785913 |
17, +871/−1305 | DD11, TT6 | yes: G2 first |
| 71 | DD13 | Replace vendored BButton in GlDropdown with native toggle markup dcdd0dfd16 |
4, +826/−39 | DD12 | no (API narrowing, no usage) |
| 72 | DD14 | Remove leftover vendored dropdown files and move dropdown styles 5cb71d1aa6 |
21, +104/−832 | DD13 | no |
Modal (MD)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 31 | MD1 | Move the modal manager out of vendored bootstrap-vue 11f19e8e92 |
7, +477/−209 | — | no |
| 32 | MD2 | Route BModal root-bus traffic through the root event adapter f63a911ce4 |
3, +183/−12 | MD1 | no |
| 33 | MD3 | Rewrite v-gl-modal directive without bootstrap-vue 7cf4d3a8b0 |
9, +626/−363 | — | no |
| 34 | MD4 | Drop scoped-style, attrs-cache and id mixins from BModal a4fb865194 |
3, +196/−22 | MD2 · ship with MD5 | no |
| 35 | MD5 | Rewrite BModal as an internal template SFC 67c1b9753e |
23, +3837/−4139 | MD3, MD4 | yes: G1 (Vue 3 jest) |
| 59 | MD6 | Make GlModal's template portable fbbbb46a30 |
7, +442/−30 | MD5, TB15 | no |
| 60 | MD7 | Remove leftover vendored modal artifacts ea2e03f57b |
22, +326/−1328 | MD6 | no |
Tooltip and popover (TT)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 36 | TT1 | Add GlPopover and GlTooltip characterization specs d6059db982 |
3, +1880/−1 | — | no |
| 37 | TT2 | Replace tooltip_mixin with open, close, enable and disable methods 54abd7aaac |
10, +366/−59 | TT1 | optional (pure Vue 3) |
| 38 | TT3 | Extract popper positioning helpers into popper_utils b1827147ca |
5, +352/−89 | — | no |
| 39 | TT4 | Extract tooltip and popover DOM helpers into src/utils 527a559d6e |
5, +707/−122 | — | no |
| 40 | TT5 | Route tooltip root-bus traffic through the root event adapter 5a9e568a81 |
3, +254/−14 | TT2, TT4 | no |
| 41 | TT6 | Route tooltip target-owner shown auto-hide through the version shim bc0c786796 |
9, +404/−6 | TT5 | no |
| 61 | TT7 | Replace BVPopper and BVTooltipTemplate with a template SFC 0857b993e0 |
12, +924/−49 | TT6, MD5 | no |
| 65 | TT8 | Extract BVTooltip into a portable controller SFC 7bb92ec93e |
23, +1215/−956 | TT3, TT7 | no |
| 73 | TT9 | Inline BTooltip into GlTooltip 13ee620e3e |
12, +900/−70 | TT2, TT8 | no |
| 74 | TT10 | Inline VBTooltip into GlTooltipDirective df1023c497 |
15, +676/−1245 | MD3, TT9 | pure Vue 3 only (in gitlab MR 264182) |
| 77 | TT11 | Reimplement GlPopover on the shared tooltip controller 30b7989afc |
23, +744/−3672 | TT10 | no |
| 78 | TT12 | Remove vendored tooltip leftovers and move tooltip and popover styles a153a8af68 |
16, +84/−936 | TT11 | no |
Table (TB; TB10–TB14 are spec-only, written against the old implementation)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 42 | TB1 | Delete unused BTableSimple and the vendored table barrel fdb811dd43 |
8, +25/−658 | — | no |
| 43 | TB2 | Move table DOM helpers and field constants to table_utils 8b6c99fa1e |
12, +377/−88 | — | no |
| 44 | TB3 | Move table field and row helpers to table_utils f292aa76f7 |
24, +585/−337 | TB2 | no |
| 45 | TB4 | Move default sort compare to table_utils and use native stable sort 52eafc0301 |
11, +591/−200 | TB3 | no |
| 46 | TB5 | Extract table filtering logic to table_utils 3ad0210e9b |
9, +371/−143 | TB4 | no |
| 47 | TB6 | Extract table sorting logic to table_utils 5cd63611d4 |
5, +871/−151 | TB5 | no |
| 48 | TB7 | Extract table row selection logic to table_utils 215b8b45d6 |
7, +674/−82 | TB6 · ship with TB8 | no |
| 49 | TB8 | Expose row selection and refresh methods on GlTable 52460b338a |
5, +233/−1 | TB7 | optional (methods instead of $children[0]) |
| 50 | TB9 | Accept items provider functions in GlTable items prop 82bdb62c62 |
4, +211/−8 | TB8 | no |
| 51 | TB10 | Test GlTable pagination, primary key and item formatters directly 9027068160 |
3, +572/−0 | — | no |
| 52 | TB15 | Make GlTable and GlTableLite slot and listener forwarding portable 5b2a3ee82b |
8, +547/−31 | TB9 | no |
| 62 | TB11 | Test GlTable filtering directly f6201ec9e8 |
1, +783/−0 | — | no |
| 63 | TB12 | Test GlTable sorting directly 0c8d08ad58 |
1, +754/−0 | — | no |
| 64 | TB13 | Test GlTable row selection directly f9dd872a4f |
1, +1089/−0 | — | no |
| 67 | TB14 | Test GlTable busy state and items provider directly 39886bf17d |
1, +954/−0 | — | no |
| 68 | TB16 | Render table header and footer from a template SFC 9a4b5083b6 |
18, +1081/−590 | TB15, MD5 | yes: G3 |
| 75 | TB17 | Render table body rows from a template SFC f5941e7d70 |
27, +1551/−1233 | TB10, TB13, TB16 | yes: G7 |
| 76 | TB18 | Move busy, top, bottom and empty rows into the body template f877128f6d |
16, +499/−670 | TB17 | no |
| 79 | TB19 | Replace BTableLite with a template SFC core and shared table element 83bd39728c |
14, +1265/−76 | TB18 | no |
| 82 | TB20 | Replace BTable with a template SFC core ff4c14a5b7 |
27, +1849/−2045 | TB11–TB14, TB19 | no |
| 83 | TB21 | Relocate vendored table specs and remove remaining table vendor files 8eecf3b352 |
47, +3308/−11252 | TB20 | no |
Native Vue 3 portability (PX)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 69 | PX1 | Make specs portable to native Vue 3 4c95e0923b |
7, +29/−19 | — | no |
| 80 | PX2 | Forward GlButton listeners through the version shim 9b67bfb696 |
6, +294/−17 | — | no (events declared on native Vue 3 only via nativeEmits()) |
| 81 | PX3 | Declare emitted events that also fall through on Vue 3 6ad591cd29 |
20, +254/−2 | — | no (nativeEmits()) |
Removal (X)
| # | ID | Commit (future MR) | Files, +/− | Depends on | GitLab MR needed? |
|---|---|---|---|---|---|
| 84 | X1 | Delete vendored BButton, BButtonClose and BLink f480c77837 |
14, +8/−2010 | MD5, TS3, DD10, DD13 | no |
| 85 | X2 | Remove orphaned vendored utilities and the element-to-instance registry 5fbcb62365 |
71, +165/−3148 | X1, all families | no |
| 86 | X3 | Remove the vendored bootstrap-vue directory d0d02bf2c5 |
32, +47/−2525 | X2 | no |
| 87 | PX4 | Make the remaining specs portable to native Vue 3 9224b1a3bb |
18, +367/−275 | X3 | no (spec-only) |
Consumer follow-ups (GitLab / duo-ui)
gitlab-org/gitlab!264170 (Draft, pipeline:run-all-jest,
pipeline:run-all-rspec, pipeline::tier-3): the integration commit plus spec/code fixes. Each fix also passes with
the current @gitlab/ui and can land on master before the bump; the exception is the SLA snapshot line of G4.
| GitLab commit | For | Why | |
|---|---|---|---|
| G1 | Check hidden lazy modals by text instead of empty HTML | MD5 | Vue 3 jest lane: VTU 2 serializes the hidden modal's placeholder comment |
| G2 | Use GlDropdown public methods instead of its internal dropdown ref | DD12 | Blocking. 4 sites used the private $refs.dropdown. Without the fix, the alert sidebar "Edit" assignee button silently stops opening its menu (no error, no spec). Falls back on releases without updatePopper() |
| G3 | Find GlTable header cells by element instead of component | TB16 | header cells are elements now |
| G4 | Check lazy GlTab stubs independently of the prop declaration | TS4 | GlTab declares the BTab props, so stubs render lazy="true" / active="true" |
| G5 | Mount real tabs in the DevOps adoption app spec | TS3 | GlTabs' own logic now runs in the shallow-mounted spec |
| G6 | Use Vue 2 transition class attribute names in snapshots | TS4 | the serializer maps enterfromclass to enterclass, so one snapshot serves both jest lanes |
| G7 | Find GlTable body rows by element instead of component | TB17 | body rows are elements now (three AI settings tables, alert management table) |
gitlab-org/gitlab!264182 (Draft): the native Vue 3 lanes on the package
from !6551. It also carries the
GitLab change needed only for pure Vue 3: tooltip_on_truncate.js calls the GlTooltipDirective hooks of the
running Vue version (TT10), and the RefSelector and note_agent_actor_line changes above.
Not needed for the bump (before a pure Vue 3 switch):
- TT2. Use
open()/close()/enable()/disable()of GlTooltip/GlPopover instead of$refs.x.$emit('open' | …)(tooltips.vue,observability_feedback.vue, ee compliancefilters.vue) and add them to the jest mocks.$emitkeeps working on Vue 2 and compat. - TB8. Call
selectRow(), …,refresh()on GlTable instead of$children[0](which still works).
No change needed: GitLab's group_name_and_path_spec finds { name: 'BFormGroup' } and the core keeps that name;
duo-ui uses none of the narrowed or removed APIs (DD13 toggle-attrs keys, the bvEventRoot fallback).
design.gitlab.com follow-ups (separate MRs):
- Pre-existing. GlButton, GlFormInput, GlFormTextarea and GlLink pass BSV option names (
propagation,immediatePropagation) tostopEvent, which ignores them. GlCollapse binds Vue 2 transition class names. - Upstream. Tell the author of 39561c88 about its Vue 2 side effects (Known differences).
- After the Vue 3 switch. Delete the Vue 2 Teleport branch and
SlotOutlet(native<slot>; also removes the per-cell instance cost on Vue 3: +37% mount / +24% re-render on compat with all 8 GlTable columns slotted). Forwarddeactivated()of GlTooltip/GlPopover in<keep-alive>toforceHide(). GlPopover renders its slots twice per update on Vue 3. Decide whether GlFormGroup forwards non-native listeners (Vue 2 never delivered them). - Test infrastructure. Add a real-browser
@vue/compate2e check for nested tooltips and tab panels (jsdom cannot see DOM-anchor and visibility differences).
Known differences
Vue 2 deviations from main
- DD13: GlDropdown
toggle-attrs. Keys that named BButton props (href,to,pressed,rel,target) no longer change the toggle; onlytypeapplies.eventandrouter-tagrender as DOM attributes on link toggles, and a camelCase + kebab-case duplicate renders the kebab one. No GitLab or duo-ui usage. - TB18:
variantin a statictbodyTrAttrobject now applies to the busy, top, empty and bottom rows on every render. BSV deleted the key from the object at the first render, so the variant applied only once. - GlTooltip / GlPopover slot content (TT9, TT11).
main(since 39561c88) stops followingv-slotcontent while the tooltip is open and shows comments for comment-onlyv-slotcontent. Here, on Vue 2, both follow the slot while open and fall back to thetitleprop for comment-only content; the title-prop fallback of that commit is kept. A default container set withsetGlTooltipDefaultContainer()after mount applies on the next prop, title or content change (GitLab sets it once at boot).
Intended changes visible on Vue 2
The other 20 accepted Vue 2 parity entries:
- Slot presence is checked at render time, so slots added or removed after mount take effect: GlFormGroup label
description (FG4), GlModal
modal-ok/modal-cancel/modal-footer(MD6), GlTable head slots (TB15). - An items-provider function no longer logs a prop-type warning (TB9);
@input.onceon GlTabs runs once (TS3);buttonClassis a declared GlDropdownItem prop, so its junk attribute spelling on link items changes (DD9). - Other Vue 2 fixes: a
thAttrstyle no longer dropsthStyle(TB16); a non-static GlModal destroyed in the update that opens it no longer leaves a copy indocument.body(F13);selectAllRows()no longer throwsRangeErroron very large tables.
Test contracts and snapshots only
- Inlined components declare the BSV props they used to pass on (GlTab, GlTabs, the GlDropdown family, GlTooltip,
GlPopover): those keys leave
$attrs, and stubs render them as props. Components that now emit events themselves show them inwrapper.emitted()(GlTab, GlTabs, GlDropdownItem links). Components rewritten with declaredemits(GlDropdown, tabs, modal and table cores, tooltip, popover, form select) warn on Vue 3 / compat when code$emits an undeclared event on them (no GitLab usage); GlButton and the 14 PX components declare theirs on native Vue 3 only. shallowMountof the real GlTooltip renders a comment instead of a stubbedBTooltip; the BTr/BTd/BTh/BTbody components are gone (find rows and cells by element); a hidden lazy GlModal serializes as<!---->under VTU 2.- HTML only (101 snapshot-only Vue 2 parity scenarios): comment placeholders between table rows and in some slots,
stylestrings intdAttr/thAttr/tbodyTrAttrserialized like other styles, shifted__BVID__numbers.
Packaging
vue-functional-data-mergeis no longer a dependency. bootstrap-vue's license notice moved tosrc/scss/bootstrap_vue/LICENSE(that SCSS still derives from bootstrap-vue).dist/tailwind.cssloses 4 utilities only Storybook used.
Compat convergences
581 compat parity scenarios now match Vue 2 (details per changeset):
.nativelisteners (also withcapture,once, key modifiers) reach the root element ofinheritAttrs: falsecomponents; listeners added after mount are called; GlTabs shows itsemptyslot again.- Transitions use the class prop names of the running Vue, so
enterclass/leaveclassno longer leak as attributes; GlToast fades out fromshow; GlFormSelectchangecarries the value. - Toasts, tooltips and popovers mount into the calling app instead of separate
data-v-appwrappers; tooltips and popovers are removed right away on destroy and close when a GlDisclosureDropdown or GlCollapsibleListbox opens; the tooltip engine logs no compat deprecation warnings. - Table rows are focusable and keyboard-handled only with a
row-clickedlistener, and row listeners are fixed at the first render, as on Vue 2.$onlisteners on the internal table do not get row events.
The 43 accepted compat entries are the 20 shared intended changes plus 23 partial convergences: tooltip and popover
slot content while open, container forms, the tab link disabled attribute, tab pane transition attributes, an
empty foot slot of GlTableLite.