Fail Vue 3 apps that miss a plugin install in dev and test
What does this MR do and why?
On Vue 3, a missing Vue.use(...) for VueApollo, VueRouter or Vuex breaks a page silently. This MR makes the page fail loudly in development and test builds, at the point where the app is created.
The problem
Vue.use(VueApollo), Vue.use(VueRouter) and Vue.use(Vuex) register a global mixin on the @vue/compat singleton. @vue/compat copies that mixin into an app only when the app is created. If an app gets one of these root options before the install ran, Vue 3 ignores the option:
apolloProvider:$apollois undefined.apollo:queries do not run, and code that reads$apollothrows.router:$routeand$routerare undefined, and<router-view>does not resolve.store:$storeis undefined, andmapState/mapGettersthrow in render.
On Vue 2 this does not happen, because any bundle on the page that installed the plugin covers every app. The Vue 3 failures show up far from the cause, for example as empty lists or failed form submits. !259575 (merged) fixed one case of this.
A static check over the import graph cannot find all cases reliably. The install often runs inside a function, behind a DOM check or after an awaited import. So whether it runs before the app is created depends on the page at runtime.
The change
- The compat adapters for VueApollo, VueRouter and Vuex mark the global mixin that their
install()registers, with a symbol key. Vue merges options withfor...in, which skips symbol keys, so the marker never reaches component options. - The checks read the mixins that the app really has, not a record of past installs. An install that runs after the app was created does not reach that app, so it does not count.
initVueApp(Vue 3 branch) checks theapolloProvider,routerandstoreoptions on the created app, before it mounts. The compatVueconstructor checks them before it creates the app, using a throwaway app that gets the same copy of global mixins. If a needed plugin is missing, they throw an error that names the missingVue.use(...)and the app.- The check runs only when
NODE_ENVis notproductionand not under Jest. This covers the local GDK dev server and CI feature specs, which build withNODE_ENV=testand enable thevue3_migrate_*flags. Jest is excluded because specs pass a provider and stub$apolloby hand. - Pinia is not checked.
~/pinia/instanceinstalls the plugin at module scope, andinitVueAppcallsapp.use(pinia)itself.
Component check
The root check sees only the options passed to the app. A second check covers components with their own plugin options. It is a global mixin, registered under the same condition, that runs in created. It throws when a component has one of these options and its app lacks the matching mixin:
apolloorapolloProvider: VueApollorouter: VueRouterstore: Vuex
It found a gap that the root check could not see: the milestone sidebar mounted IssuableTimeTracker, which has an apollo query, with no Vue.use(VueApollo) and no provider. Fixed in !260738 (merged).
A missing provider, router or store is not reported. An earlier version also reported components whose plugin was installed but had no provider. In a pipeline run, that found 3 components in 31 spec files. Only 1 of them was a real bug (fixed in !260658 (merged)). The other 2 are shared components whose apollo queries do nothing in some apps, for example BlobEditHeader on the file editor.
An error in a component hook does not stop the mount. Vue sends it to the development errorHandler, which logs it with console.error. Since !256639 (merged), every :js feature spec fails on unexpected SEVERE console output, so both checks fail the feature specs that reach them.
Limits
In feature specs, every vue3_migrate_* flag is enabled, including vue3_migrate_super_sidebar. The Vue 3 super sidebar bundle installs VueApollo on the compat Vue that all Vue 3 bundles on a page share. This hides a page that misses its own install. A local run with the super sidebar and performance bar flags disabled showed the Geo replicables pages failing, as expected.
Findings
- Geo replicables pages had no
Vue.use(VueApollo). Fixed in !260627 (merged). - Group approval settings passes no
apolloProvider, soPolicyOverrideWarningIconnever loads policies, also on Vue 2. With the current check this is no longer reported, but it is still a bug. Fixed in !260658 (merged). - The milestone sidebar mounted
IssuableTimeTrackerwith noVue.use(VueApollo)and no provider. Found by the component check in the stress run. Fixed in !260738 (merged). mountMarkdownEditorcreated an Apollo provider but never installed VueApollo, so on Vue 3 the editor could not mount on milestone forms. Found by the root check in the stress run. Fixed in !260747 (merged).
All four fixes are merged. The stress MR !255334 (closed) puts every page on Vue 3 with the super sidebar on Vue 2. Its run on master with this check, https://gitlab.com/gitlab-org/gitlab/-/pipelines/2929568930, has no [vue3-compat] failures.
References
- Same bug class as !259575 (merged)
- Geo replicables fix found by the same audit: !260627 (merged)
- Pipelines with findings: https://gitlab.com/gitlab-org/gitlab/-/pipelines/2925759169, https://gitlab.com/gitlab-org/gitlab/-/pipelines/2926065304
Screenshots or screen recordings
None. The change has no visible effect in production.
How to set up and validate locally
- Use a GDK with Vue 3 page bundles, so that migrated pages run on Vue 3.
- In a Rails console, enable a rollout flag, for example
Feature.enable(:vue3_migrate_work_item_settings). - Visit Admin > Settings > Work items.
- Confirm the console shows the error
AdminWorkItemSettings was given an option that needs Vue.use(VueRouter).
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.