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: $apollo is undefined. apollo: queries do not run, and code that reads $apollo throws.
  • router: $route and $router are undefined, and <router-view> does not resolve.
  • store: $store is undefined, and mapState/mapGetters throw 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 with for...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 the apolloProvider, router and store options on the created app, before it mounts. The compat Vue constructor 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 missing Vue.use(...) and the app.
  • The check runs only when NODE_ENV is not production and not under Jest. This covers the local GDK dev server and CI feature specs, which build with NODE_ENV=test and enable the vue3_migrate_* flags. Jest is excluded because specs pass a provider and stub $apollo by hand.
  • Pinia is not checked. ~/pinia/instance installs the plugin at module scope, and initVueApp calls app.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:

  • apollo or apolloProvider: VueApollo
  • router: VueRouter
  • store: 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, so PolicyOverrideWarningIcon never 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 IssuableTimeTracker with no Vue.use(VueApollo) and no provider. Found by the component check in the stress run. Fixed in !260738 (merged).
  • mountMarkdownEditor created 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

Screenshots or screen recordings

None. The change has no visible effect in production.

How to set up and validate locally

  1. Use a GDK with Vue 3 page bundles, so that migrated pages run on Vue 3.
  2. In a Rails console, enable a rollout flag, for example Feature.enable(:vue3_migrate_work_item_settings).
  3. Visit Admin > Settings > Work items.
  4. 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.

Edited by Miguel Rincon

Merge request reports

Loading
Loading