Skip to content

Commit f3ad69a

Browse files
authored
Add Global API Changes migration page (#125)
1 parent 69ce338 commit f3ad69a

File tree

4 files changed

+216
-8
lines changed

4 files changed

+216
-8
lines changed

src/.vuepress/config.js

+9-7
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ const sidebar = {
5555
{
5656
title: 'Tooling',
5757
collapsable: false,
58-
children: ['/guide/single-file-component']
58+
children: [
59+
'/guide/single-file-component',
60+
'/guide/testing'
61+
]
5962
},
6063
{
6164
title: 'Scaling Up',
@@ -67,15 +70,14 @@ const sidebar = {
6770
'/guide/accessibility'
6871
]
6972
},
70-
{
71-
title: 'Tooling',
72-
collapsable: false,
73-
children: ['/guide/testing']
74-
},
7573
{
7674
title: 'Migration to Vue 3',
7775
collapsable: true,
78-
children: ['migration', 'migration/functional-components']
76+
children: [
77+
'migration/global-api',
78+
'migration/treeshaking',
79+
'migration/functional-components'
80+
]
7981
},
8082
{
8183
title: 'Contribute to the Docs',

src/.vuepress/styles/index.styl

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
position: relative;
1818
border-bottom-right-radius: 2px;
1919
border-top-right-radius: 2px;
20-
max-width: 80%;
2120
margin: 1rem 0;
2221

2322
&::before {

src/guide/migration/global-api.md

+204
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Global API Changes
2+
3+
Vue 2.x has a number of global APIs and configurations that globally mutate Vue’s behavior. For instance, to create a global component, you would use the `Vue.component` API like this:
4+
5+
``` js
6+
Vue.component('button-counter', {
7+
data: () => ({
8+
count: 0
9+
}),
10+
template: '<button @click="count++">Clicked {{ count }} times.</button>'
11+
})
12+
```
13+
14+
Similarly, this is how a global directive is declared:
15+
16+
``` js
17+
Vue.directive('focus', {
18+
inserted: el => el.focus()
19+
})
20+
```
21+
22+
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:
23+
24+
* 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:
25+
26+
``` js
27+
import { createLocalVue, mount } from '@vue/test-utils'
28+
29+
// create an extended `Vue` constructor
30+
const localVue = createLocalVue()
31+
32+
// install a plugin “globally” on the “local” Vue constructor
33+
localVue.use(MyPlugin)
34+
35+
// pass the `localVue` to the mount options
36+
mount(Component, { localVue })
37+
```
38+
39+
* Global configuration makes it difficult to share the same copy of Vue between multiple "apps" on the same page, but with different global configurations.
40+
41+
```js
42+
// this affects both root instances
43+
Vue.mixin({ /* ... */ })
44+
45+
const app1 = new Vue({ el: '#app-1' })
46+
const app2 = new Vue({ el: '#app-2' })
47+
```
48+
49+
To avoid these problems, in Vue 3 we introduce…
50+
51+
## A New Global API: `createApp`
52+
53+
Calling `createApp` returns an _app instance_, a new concept in Vue 3.
54+
55+
```js
56+
import { createApp } from 'vue'
57+
58+
const app = createApp()
59+
```
60+
61+
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_. Here is a table of the current global APIs and their corresponding instance APIs:
62+
63+
| 2.x Global API | 3.x Instance API (`app`) |
64+
|----------------|--------------------------|
65+
| Vue.config | app.config |
66+
| Vue.config.productionTip | _removed_ ([see below](config-productiontip-removed)) |
67+
| Vue.config.ignoredElements | app.config.isCustomElement ([see below](#config-ignoredelements-is-now-config-iscustomelement)) |
68+
| Vue.component | app.component |
69+
| Vue.directive | app.directive |
70+
| Vue.mixin | app.mixin |
71+
| Vue.use | app.use ([see below](#a-note-for-plugin-authors)) |
72+
73+
All other global APIs that do not globally mutate behavior are now named exports, as documented in [Global API Treeshaking](./treeshaking.html).
74+
75+
### `config.productionTip` Removed
76+
77+
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).
78+
79+
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.
80+
81+
### `config.ignoredElements` Is Now `config.isCustomElement`
82+
83+
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:
84+
85+
``` js
86+
// before
87+
Vue.config.ignoredElements = ['my-el', /^ion-/]
88+
89+
// after
90+
const app = Vue.createApp()
91+
app.config.isCustomElement = tag => tag.startsWith('ion-')
92+
```
93+
94+
::: tip Important
95+
96+
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).
97+
98+
* 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;
99+
* This will be a new top-level option in the Vue CLI config.
100+
:::
101+
102+
### A Note for Plugin Authors
103+
104+
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:
105+
106+
```js
107+
var inBrowser = typeof window !== 'undefined';
108+
/**/
109+
if (inBrowser && window.Vue) {
110+
window.Vue.use(VueRouter);
111+
}
112+
```
113+
114+
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:
115+
116+
```js
117+
const app = createApp(MyApp)
118+
app.use(VueRouter)
119+
```
120+
121+
## Mounting App Instance
122+
123+
After being initialized with `createApp()`, the app instance `app` can be used to mount a Vue root instance with `app.mount(VueInstance, domTarget)`:
124+
125+
```js
126+
import { createApp } from 'vue'
127+
import MyApp from './MyApp.vue'
128+
129+
const app = createApp()
130+
app.mount(MyApp, ‘#app’)
131+
```
132+
133+
The `mount` method can also accept props to be passed to the root component via a third argument:
134+
135+
```js
136+
app.mount(MyApp, '#app', {
137+
// props to be passed to root component
138+
})
139+
```
140+
141+
With all these changes, the component and directive we have at the beginning of the guide will be rewritten into something like this:
142+
143+
``` js
144+
const app = createApp()
145+
146+
app.component('button-counter', {
147+
data: () => ({
148+
count: 0
149+
}),
150+
template: '<button @click="count++">Clicked {{ count }} times.</button>'
151+
})
152+
153+
app.directive('focus', {
154+
inserted: el => el.focus()
155+
})
156+
157+
// now every Vue instance mounted with app.mount(), along with its
158+
// component tree, will have the same “button-counter” component
159+
// and “focus” directive without polluting the global environment
160+
app.mount(MyApp, '#app')
161+
```
162+
163+
## Provide / Inject
164+
165+
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:
166+
167+
```js
168+
// in the entry
169+
app.provide({
170+
[ThemeSymbol]: theme
171+
})
172+
173+
// in a child component
174+
export default {
175+
inject: {
176+
theme: {
177+
from: ThemeSymbol
178+
}
179+
},
180+
template: `<div :style="{ color: theme.textColor }" />`
181+
}
182+
```
183+
184+
## Share Configurations Among Apps
185+
186+
One way to share configurations e.g. components or directives among apps is to create a factory function, like this:
187+
188+
```js
189+
import { createApp } from 'vue'
190+
import Foo from './Foo.vue'
191+
import Bar from './Bar.vue'
192+
193+
const createMyApp = () => {
194+
const app = createApp()
195+
app.directive('focus', /* ... */)
196+
197+
return app
198+
}
199+
200+
createApp().mount(Foo, '#foo')
201+
createApp().mount(Bar, '#bar')
202+
```
203+
204+
Now the `focus` directive will be available in both Foo and Bar instances and their descendants.

src/guide/migration/treeshaking.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Global API Treeshaking
2+
3+
<!--Tmp. placeholder for linking purpose-->

0 commit comments

Comments
 (0)