skip to content

Upgrading a Vue 2 entry file that uses `Vue.use`, `Vue.component` and `Vue.prototype.$http`, what replaces each in Vue 3, and why?

level: middleimportance: must knowfreq 62%

answer

  1. no more shared global constructor
  2. an app instance per createApp call
  3. globally mutating APIs move to app
  4. prototype becomes config.globalProperties

basics

~20 s

Vue 3 moves the APIs that globally mutated Vue onto the app returned by createApp: app.use, app.component, app.directive, app.mixin, and app.config.globalProperties for Vue.prototype. Vue 2's shared global config leaked plugins and settings across apps and tests.

solid answer

~40 s

In Vue 2 there was no real app: every root created with `new Vue()` shared one global configuration, so `Vue.use`, `Vue.mixin` or `Vue.component` affected every instance on the page and leaked between tests, which is why test tooling needed a `createLocalVue` workaround. Vue 3 introduces an **app instance**: `createApp(App)` returns an object that owns its own config. The mapping is `Vue.use` -> `app.use`, `Vue.component` -> `app.component`, `Vue.directive` -> `app.directive`, `Vue.mixin` -> `app.mixin`, `Vue.config` -> `app.config`, and `Vue.prototype.$http` -> `app.config.globalProperties.$http` (or `app.provide` plus `inject`). `Vue.extend` is removed, `new Vue({ render }).$mount('#app')` becomes `app.mount('#app')`, and non-mutating helpers like `Vue.nextTick` become named imports such as `nextTick`, which bundlers can tree-shake.

code

ts · 17 lines
ts
// main.ts, Vue 3
// was: Vue.use(MyPlugin); Vue.component('BaseButton', BaseButton);
//      Vue.prototype.$http = http; new Vue({ render: h => h(App) }).$mount('#app')
import { createApp } from 'vue'
import App from './App.vue'
import BaseButton from './components/BaseButton.vue'
import { MyPlugin } from './plugins/my-plugin'
import { http } from './http'

const app = createApp(App)

app.use(MyPlugin)
app.component('BaseButton', BaseButton)
app.config.globalProperties.$http = http // this.$http in Options API components
app.provide('http', http)                 // inject('http') in <script setup>

app.mount('#app')

go deeper

for a junior

Recall the mapping: Vue.use, Vue.component and Vue.mixin become app.use, app.component and app.mixin on the object createApp returns.

for a middle

Explain why: Vue 2 roots shared one global config that leaked across apps and tests, and Vue 3 scopes config, components and plugins to each app instance.

for a senior

Cover the edges a real upgrade hits: Vue.prototype to globalProperties or provide, self-installing UMD plugins, named-export helpers and mount now rendering inside the element.

for a principal

Relate the change to isolation: per-app config enables several apps per page and clean tests, and nudges teams from implicit globals toward provide/inject dependencies.

## The problem with Vue 2's global API Vue 2 had no concept of an application. What people called "the app" was just a root instance created with `new Vue()`, and every root created from the same `Vue` constructor **shared one global configuration**. That caused two concrete problems: - **Test pollution.** A plugin installed with `Vue.use` or a mixin added with `Vue.mixin` stayed installed for every later test, and some of those calls could not be undone. Vue 2's test utilities had to offer `createLocalVue`, an extended constructor, just to isolate plugins per test. - **Several apps on one page.** Two roots on the same page could not have different global components, mixins or config, because `Vue.mixin(...)` affected both. Vue 3's answer is the **app instance**: `createApp(rootComponent)` returns an object with its own `config`, component registry, directives and plugins. The rule of thumb from the migration guide is that *any API that globally mutates Vue's behaviour moves onto the app instance*. ## The mapping | Vue 2 global API | Vue 3 replacement | |---|---| | `Vue.use(Plugin)` | `app.use(Plugin)` | | `Vue.component('Name', def)` | `app.component('Name', def)` | | `Vue.directive('focus', def)` | `app.directive('focus', def)` | | `Vue.mixin(obj)` | `app.mixin(obj)` | | `Vue.config` | `app.config` | | `Vue.prototype.$http = http` | `app.config.globalProperties.$http = http` | | `Vue.config.ignoredElements` | `app.config.compilerOptions.isCustomElement` (a function) | | `Vue.config.productionTip` | Removed | | `Vue.extend(options)` | Removed: plain options objects, `defineComponent` for type inference, `extends` for inheritance | ## Mounting changed too `new Vue({ el: '#app' })` or `new Vue({ render: h => h(App) }).$mount('#app')` becomes: ```ts import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app') ``` One visible difference: Vue 2 **replaced** the mount element with the rendered root, while Vue 3 renders **inside** it, replacing its `innerHTML`. CSS or scripts that targeted the original element's attributes can notice the change. ## Non-mutating helpers became named exports APIs that never mutated global state, such as `Vue.nextTick` or `Vue.observable`, were not moved to the app. They became **named exports** (`import { nextTick, reactive } from 'vue'`) so a bundler can drop the ones you never import. Bundled code that called `Vue.nextTick` on a default-imported `Vue` has to switch to the named import, because Vue 3's ES module build has no default `Vue` export. ## Plugins and globalProperties - **Auto-installing plugins stop installing.** Some Vue 2 UMD builds called `window.Vue.use(Plugin)` on load. With no global constructor to install into, that no longer works; call `app.use(Plugin)` yourself. - **`globalProperties` is copied onto every component instance** in that app, so `this.$http` keeps working in the Options API. In `<script setup>` there is no `this`, so shared services are usually exposed with `app.provide(key, value)` and read with `inject(key)`; the migration guide suggests `provide` as the alternative. - **Order matters.** Vue's docs say `mount()` should be called after all app configuration and asset registration is done, because mounting renders the root right away. ## Traps interviewers probe - **Several apps, several registries.** `createApp` can be called more than once on a page, and each app has its own components, directives and config. A component registered on one app is unknown to the other, which is exactly the isolation Vue 2 lacked. - **Chaining versus return values.** `app.use`, `app.component(name, def)`, `app.directive(name, def)`, `app.mixin` and `app.provide` return the app, so they chain. `app.mount` does not: it returns the **root component instance**, so `createApp(App).use(P).mount('#app')` must come last. - **Config moved with everything else.** Hooks such as `Vue.config.errorHandler` become `app.config.errorHandler`, set per app. - **`Vue.extend` has no drop-in.** Components are plain options objects; `defineComponent` exists for type inference and `extends` for the rare inheritance case. ## A migration checklist 1. Find every `Vue.` call in the entry file and in plugin files, and route each through the single `app` object. 2. Replace `Vue.prototype.$x` with `app.config.globalProperties.$x`, or with `provide`/`inject` if `<script setup>` code needs it. 3. Replace `Vue.nextTick` and similar helpers with named imports. 4. In tests, create a fresh app or mount with per-test plugins instead of relying on `createLocalVue`.

  • Why does Vue 3 recommend `provide` over `globalProperties` for shared services in `<script setup>` code?
    `globalProperties` are copied onto component instances, so they are reached through `this` or the template, and `<script setup>` has no `this`. `app.provide(key, value)` makes the service available to any component through `inject(key)`, which works in `setup`, keeps the dependency explicit at the call site, and can be overridden for a subtree by a nearer `provide`.
  • What happens to a Vue 2 plugin that installed itself with `window.Vue.use(Plugin)` in its UMD build?
    It stops installing under Vue 3, because there is no global constructor whose `use` it can call. The consuming app must call `app.use(Plugin)` explicitly, and the plugin's `install(app)` must itself register on the app it receives rather than on a global `Vue`.

saying these in an interview costs you the question

  • Vue 3 still has a global Vue.component that every app shares
  • Vue.prototype still works in Vue 3 for adding this.$http
  • Plugins install once globally in Vue 3, just like Vue.use
  • createApp only renamed new Vue; nothing about isolation changed
  • app.mount replaces the target element exactly as Vue 2 did