|
| 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