-
-
Notifications
You must be signed in to change notification settings - Fork 8.8k
Description
- Start Date: (fill me in with today's date, YYYY-MM-DD)
- Target Major Version: (2.x / 3.x)
- Reference Issues: (fill in existing related issues, if any)
- Implementation PR: (leave this empty)
Summary
Re-design app bootstrapping and global configuration API.
Basic example
Before
import Vue from 'vue'
import App from './App.vue'
Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
new Vue({
render: h => h(App)
}).$mount('#app')
After
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp()
app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
app.mount(App, '#app')
Motivation
Vue's current global API and configurations permanently mutate global state. This leads to a few problems:
-
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 (e.g.Vue.use
,Vue.mixin
) don't even have a way to revert their effects. This makes tests involving plugins particularly tricky.vue-test-utils
has to implement a special APIcreateLocalVue
to deal with this
-
This also 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' })
Detailed design
Technically, Vue 2 doesn't have the 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.
In this proposal we introduce a new global API, createApp
:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
Calling createApp
with a root component returns an app instance. An app instance provides an app context. The entire component tree formed by the root instance and its descendent components share the same app context, which provides the configurations that were previously "global" in Vue 2.x.
Global API Mapping
An app instance exposes a subset of the current global APIs. The rule of thumb is any APIs that globally mutate Vue's behavior are now moved to the app instance. These include:
- Global configuration
Vue.config
->app.config
- with the exception of
Vue.config.productionTip
- with the exception of
- Asset registration APIs
Vue.component
->app.component
Vue.directive
->app.directive
Vue.filter
->app.filter
- Behavior Extension APIs
Vue.mixin
->app.mixin
Vue.use
->app.use
Global APIs that are idempotent (i.e. do not globally mutate behavior) are now named exports as proposed in Global API Treeshaking.
Mounting App Instance
The app instance can be mounted with the mount
method. It works the same as the existing vm.$mount()
component instance method and returns the mounted root component instance:
const rootInstance = app.mount('#app')
rootInstance instanceof Vue // true
Provide / Inject
An app instance can also provide dependencies that can be injected by any component inside the app:
// in the entry
app.provide({
[ThemeSymbol]: theme
})
// in a child component
export default {
inject: {
theme: {
from: ThemeSymbol
}
},
template: `<div :style="{ color: theme.textColor }" />`
}
This is similar to using the provide
option in a 2.x root instance.
Drawbacks
-
Global APIs are now split between app instance methods and global named imports, instead of a single namespace. However the split makes sense because:
-
App instance methods are configuration APIs that globally mutate an app's behavior. They are also almost always used together only in the entry file of a project.
-
Global named imports are idempotent helper methods that are typically imported and used across the entire codebase.
-
Alternatives
N/A
Adoption strategy
- The transformation is straightforward (as seen in the basic example).
- A codemod can also be provided.
Unresolved questions
-
Vue.config.productionTip
is left out because it is indeed "global". Maybe it should be moved to a global method?import { suppressProductionTip } from 'vue' suppressProductionTip()