Skip to content

Commit bed289b

Browse files
authored
Global mounting/configuration API change (#29)
1 parent 5fb76aa commit bed289b

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

active-rfcs/0009-global-api-change.md

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
- Start Date: 2019-04-08
2+
- Target Major Version: 3.x
3+
- Reference Issues: N/A
4+
- Implementation PR: N/A
5+
6+
# Summary
7+
8+
Re-design app bootstrapping and global API.
9+
10+
- Global APIs that globally mutate Vue's behavior are now moved to **app instances** created the new `createApp` method, and their effects are now scoped to that app instance only.
11+
12+
- Global APIs that are do not mutate Vue's behavior (e.g. `nextTick` and the APIs proposed in [Advanced Reactivity API](https://github.com/vuejs/rfcs/pull/22)) are now named exports as specified in [the Global API Treeshaking RFC](https://github.com/vuejs/rfcs/blob/treeshaking/active-rfcs/0000-global-api-treeshaking.md).
13+
14+
# Basic example
15+
16+
## Before
17+
18+
``` js
19+
import Vue from 'vue'
20+
import App from './App.vue'
21+
22+
Vue.config.ignoredElements = [/^app-/]
23+
Vue.use(/* ... */)
24+
Vue.mixin(/* ... */)
25+
Vue.component(/* ... */)
26+
Vue.directive(/* ... */)
27+
28+
new Vue({
29+
render: h => h(App)
30+
}).$mount('#app')
31+
```
32+
33+
## After
34+
35+
``` js
36+
import { createApp } from 'vue'
37+
import App from './App.vue'
38+
39+
const app = createApp()
40+
41+
app.config.ignoredElements = [/^app-/]
42+
app.use(/* ... */)
43+
app.mixin(/* ... */)
44+
app.component(/* ... */)
45+
app.directive(/* ... */)
46+
47+
app.mount(App, '#app')
48+
```
49+
50+
# Motivation
51+
52+
Some of Vue's current global API and configurations permanently mutate global state. This leads to a few problems:
53+
54+
- 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.
55+
56+
- `vue-test-utils` has to implement a special API `createLocalVue` to deal with this
57+
58+
- This also makes it difficult to share the same copy of `Vue` between multiple "apps" on the same page, but with different global configurations:
59+
60+
``` js
61+
// this affects both root instances
62+
Vue.mixin({ /* ... */ })
63+
64+
const app1 = new Vue({ el: '#app-1' })
65+
const app2 = new Vue({ el: '#app-2' })
66+
```
67+
68+
# Detailed design
69+
70+
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.
71+
72+
In this proposal we introduce a new global API, `createApp`:
73+
74+
``` js
75+
import { createApp } from 'vue'
76+
77+
const app = createApp()
78+
```
79+
80+
Calling `createApp` returns an **app instance**. An app instance provides an **app context**. The entire component tree mounted by the app instance share the same app context, which provides the configurations that were previously "global" in Vue 2.x.
81+
82+
## Global API Mapping
83+
84+
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:
85+
86+
- Global configuration
87+
- `Vue.config` -> `app.config`
88+
- `config.productionTip`: removed. ([details](#remove-config-productiontip))
89+
- `config.ignoredElements` -> `config.isCustomElement`. ([details](#config-ignoredelements-config-iscustomelement))
90+
- Asset registration APIs
91+
- `Vue.component` -> `app.component`
92+
- `Vue.directive` -> `app.directive`
93+
- Behavior Extension APIs
94+
- `Vue.mixin` -> `app.mixin`
95+
- `Vue.use` -> `app.use`
96+
97+
All other global APIs that do not globally mutate behavior are now named exports as proposed in [Global API Treeshaking](https://github.com/vuejs/rfcs/pull/19).
98+
99+
## Mounting App Instance
100+
101+
The app instance can mount a root component with the `mount` method. It works similarly to the 2.x `vm.$mount()` method and returns the mounted root component instance:
102+
103+
``` js
104+
const rootInstance = app.mount(App, '#app')
105+
```
106+
107+
The `mount` method can also accept props to be passed to the root component via the third argument:
108+
109+
``` js
110+
app.mount(App, '#app', {
111+
// props to be passed to root component
112+
})
113+
```
114+
115+
## Provide / Inject
116+
117+
An app instance can also provide dependencies that can be injected by any component inside the app:
118+
119+
``` js
120+
// in the entry
121+
app.provide({
122+
[ThemeSymbol]: theme
123+
})
124+
125+
// in a child component
126+
export default {
127+
inject: {
128+
theme: {
129+
from: ThemeSymbol
130+
}
131+
},
132+
template: `<div :style="{ color: theme.textColor }" />`
133+
}
134+
```
135+
136+
This is similar to using the `provide` option in a 2.x root instance.
137+
138+
## Remove `config.productionTip`
139+
140+
In 3.0, 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).
141+
142+
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.
143+
144+
## `config.ignoredElements` -> `config.isCustomElement`
145+
146+
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 version:
147+
148+
``` js
149+
// before
150+
Vue.config.ignoredElements = ['my-el', /^ion-/]
151+
152+
// after
153+
const app = Vue.createApp()
154+
app.config.isCustomElement = tag => tag.startsWith('ion-')
155+
```
156+
157+
**Important:** in 3.0, 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`](https://vue-loader.vuejs.org/options.html#compileroptions).
158+
159+
- If `config.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;
160+
161+
- This will be a new top-level option in the Vue CLI config.
162+
163+
# Drawbacks
164+
165+
## Plugin auto installation
166+
167+
Many Vue 2.x libraries and plugins offer auto installation in their UMD builds, for example `vue-router`:
168+
169+
``` html
170+
<script src="https://unpkg.com/vue"></script>
171+
<script src="https://unpkg.com/vue-router"></script>
172+
```
173+
174+
Auto installation relies on calling `Vue.use` which is no longer available. This should be a relatively easy migration, and we can expose a stub for `Vue.use` that emits a warning instead.
175+
176+
# Alternatives
177+
178+
N/A
179+
180+
# Adoption strategy
181+
182+
- The transformation is straightforward (as seen in the basic example).
183+
- Moved methods can be replaced with stubs that emit warnings to guide migration.
184+
- A codemod can also be provided.
185+
- For `config.ingoredElements`, a compat shim can be easily provided.

0 commit comments

Comments
 (0)