# Global API
breaking

Vue 2.x has a number of global APIs and configurations that globally mutate Vue’s behavior. For instance, to register a global component, you would use the Vue.component API like this:

Vue.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
1
2
3
4
5
6

Similarly, this is how a global directive is declared:

Vue.directive('focus', {
  inserted: el => el.focus()
})
1
2
3

While this approach is convenient, it leads to a couple of problems. Technically, Vue 2 doesn't have a concept of an "app". What we define as an app is simply a root Vue instance created via new Vue(). Every root instance created from the same Vue constructor shares the same global configuration. As a result:

  • Global configuration makes it easy to accidentally pollute other test cases during testing. Users need to carefully store original global configuration and restore it after each test (e.g. resetting Vue.config.errorHandler). Some APIs like Vue.use and Vue.mixin don't even have a way to revert their effects. This makes tests involving plugins particularly tricky. In fact, vue-test-utils has to implement a special API createLocalVue to deal with this:

    import { createLocalVue, mount } from '@vue/test-utils'
    
    // create an extended `Vue` constructor
    const localVue = createLocalVue()
    
    // install a plugin “globally” on the “local” Vue constructor
    localVue.use(MyPlugin)
    
    // pass the `localVue` to the mount options
    mount(Component, { localVue })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
  • Global configuration makes it difficult to share the same copy of Vue between multiple "apps" on the same page, but with different global configurations.

    // this affects both root instances
    Vue.mixin({
      /* ... */
    })
    
    const app1 = new Vue({ el: '#app-1' })
    const app2 = new Vue({ el: '#app-2' })
    
    1
    2
    3
    4
    5
    6
    7

To avoid these problems, in Vue 3 we introduce…

# A New Global API: createApp

Calling createApp returns an app instance, a new concept in Vue 3.

import { createApp } from 'vue'

const app = createApp({})
1
2
3

If you're using a CDN build of Vue then createApp is exposed via the global Vue object:

const { createApp } = Vue

const app = createApp({})
1
2
3

An app instance exposes a subset of the Vue 2 global APIs. The rule of thumb is any APIs that globally mutate Vue's behavior are now moved to the app instance. Here is a table of the Vue 2 global APIs and their corresponding instance APIs:

2.x Global API 3.x Instance API (app)
Vue.config app.config
Vue.config.productionTip removed (see below)
Vue.config.ignoredElements app.config.compilerOptions.isCustomElement (see below)
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use (see below)
Vue.prototype app.config.globalProperties (see below)
Vue.extend removed (see below)

All other global APIs that do not globally mutate behavior are now named exports, as documented in Global API Treeshaking.

# config.productionTip Removed

In Vue 3.x, the "use production build" tip will only show up when using the "dev + full build" (the build that includes the runtime compiler and has warnings).

For ES modules builds, since they are used with bundlers, and in most cases a CLI or boilerplate would have configured the production env properly, this tip will no longer show up.

Migration build flag: CONFIG_PRODUCTION_TIP

# config.ignoredElements Is Now config.compilerOptions.isCustomElement

This config option was introduced with the intention to support native custom elements, so the renaming better conveys what it does. The new option also expects a function which provides more flexibility than the old string / RegExp approach:

// before
Vue.config.ignoredElements = ['my-el', /^ion-/]

// after
const app = createApp({})
app.config.compilerOptions.isCustomElement = tag => tag.startsWith('ion-')
1
2
3
4
5
6

Important

In Vue 3, the check of whether an element is a component or not has been moved to the template compilation phase, therefore this config option is only respected when using the runtime compiler. If you are using the runtime-only build, isCustomElement must be passed to @vue/compiler-dom in the build setup instead - for example, via the compilerOptions option in vue-loader (opens new window).

  • If config.compilerOptions.isCustomElement is assigned to when using a runtime-only build, a warning will be emitted instructing the user to pass the option in the build setup instead;
  • This will be a new top-level option in the Vue CLI config.

Migration build flag: CONFIG_IGNORED_ELEMENTS

# Vue.prototype Replaced by config.globalProperties

In Vue 2, Vue.prototype was commonly used to add properties that would be accessible in all components.

The equivalent in Vue 3 is config.globalProperties. These properties will be copied across as part of instantiating a component within the application:

// before - Vue 2
Vue.prototype.$http = () => {}
1
2
// after - Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
1
2
3

Using provide (discussed below) should also be considered as an alternative to globalProperties.

Migration build flag: GLOBAL_PROTOTYPE

# Vue.extend Removed

In Vue 2.x, Vue.extend was used to create a "subclass" of the base Vue constructor with the argument that should be an object containing component options. In Vue 3.x, we don't have the concept of component constructors anymore. Mounting a component should always use the createApp global API:

// before - Vue 2

// create constructor
const Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data() {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// create an instance of Profile and mount it on an element
new Profile().$mount('#mount-point')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// after - Vue 3
const Profile = {
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data() {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
}

Vue.createApp(Profile).mount('#mount-point')
1
2
3
4
5
6
7
8
9
10
11
12
13

# Type Inference

In Vue 2, Vue.extend was also used for providing TypeScript type inference for the component options. In Vue 3, the defineComponent global API can be used in place of Vue.extend for the same purpose.

Note that although the return type of defineComponent is a constructor-like type, it is only used for TSX inference. At runtime defineComponent is largely a noop and will return the options object as-is.

# Component Inheritance

In Vue 3, we strongly recommend favoring composition via Composition API over inheritance and mixins. If for some reason you still need component inheritance, you can use the extends option instead of Vue.extend.

Migration build flag: GLOBAL_EXTEND

# A Note for Plugin Authors

It is a common practice for plugin authors to install the plugins automatically in their UMD builds using Vue.use. For instance, this is how the official vue-router plugin installs itself in a browser environment:

var inBrowser = typeof window !== 'undefined'
/* … */
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
1
2
3
4
5

As the use global API is no longer available in Vue 3, this method will cease to work and calling Vue.use() will now trigger a warning. Instead, the end-user will now have to explicitly specify using the plugin on the app instance:

const app = createApp(MyApp)
app.use(VueRouter)
1
2

# Mounting App Instance

After being initialized with createApp(/* options */), the app instance app can be used to mount a root component instance with app.mount(domTarget):

import { createApp } from 'vue'
import MyApp from './MyApp.vue'

const app = createApp(MyApp)
app.mount('#app')
1
2
3
4
5

With all these changes, the component and directive we have at the beginning of the guide will be rewritten into something like this:

const app = createApp(MyApp)

app.component('button-counter', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

app.directive('focus', {
  mounted: el => el.focus()
})

// now every application instance mounted with app.mount(), along with its
// component tree, will have the same “button-counter” component
// and “focus” directive without polluting the global environment
app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Migration build flag: GLOBAL_MOUNT

# Provide / Inject

Similar to using the provide option in a 2.x root instance, a Vue 3 app instance can also provide dependencies that can be injected by any component inside the app:

// in the entry
app.provide('guide', 'Vue 3 Guide')

// in a child component
export default {
  inject: {
    book: {
      from: 'guide'
    }
  },
  template: `<div>{{ book }}</div>`
}
1
2
3
4
5
6
7
8
9
10
11
12

Using provide is especially useful when writing a plugin, as an alternative to globalProperties.

# Share Configurations Among Apps

One way to share configurations e.g. components or directives among apps is to create a factory function, like this:

import { createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'

const createMyApp = options => {
  const app = createApp(options)
  app.directive('focus' /* ... */)

  return app
}

createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
1
2
3
4
5
6
7
8
9
10
11
12
13

Now the focus directive will be available in both Foo and Bar instances and their descendants.

Deployed on Netlify.
Last updated: 2021-06-14, 10:05:43 UTC