From c72f4dd85f003193e909aaa4b566a9b60cf1bd29 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 9 Apr 2019 10:38:09 +0800 Subject: [PATCH 1/5] global api change --- active-rfcs/0000-global-api-change.md | 164 ++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 active-rfcs/0000-global-api-change.md diff --git a/active-rfcs/0000-global-api-change.md b/active-rfcs/0000-global-api-change.md new file mode 100644 index 00000000..bfa5dd5d --- /dev/null +++ b/active-rfcs/0000-global-api-change.md @@ -0,0 +1,164 @@ +- Start Date: 2019-04-08 +- Target Major Version: 3.x +- Reference Issues: N/A +- Implementation PR: N/A + +# Summary + +Re-design app bootstrapping and global API. + +- 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. + +- 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). + +# Basic example + +## Before + +``` js +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 + +``` js +import { createApp } from 'vue' +import App from './App.vue' + +const app = createApp(App) + +app.config.ignoredElements = [/^app-/] +app.use(/* ... */) +app.mixin(/* ... */) +app.component(/* ... */) +app.directive(/* ... */) + +app.mount('#app') +``` + +# Motivation + +Some of 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 API `createLocalVue` 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: + + ``` js + // 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`: + +``` js +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` +- 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` + +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). + +## 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: + +``` js +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: + +``` js +// in the entry +app.provide({ + [ThemeSymbol]: theme +}) + +// in a child component +export default { + inject: { + theme: { + from: ThemeSymbol + } + }, + template: `
` +} +``` + +This is similar to using the `provide` option in a 2.x root instance. + +# Drawbacks + +## Plugin auto installation + +Many Vue 2.x libraries and plugins offer auto installation in their UMD builds, for example `vue-router`: + +``` html + + +``` + +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. + +# Alternatives + +N/A + +# Adoption strategy + +- The transformation is straightforward (as seen in the basic example). +- Moved methods can be replaced with stubs that emit warnings to guide migration. +- 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? + + ``` js + import { suppressProductionTip } from 'vue' + + suppressProductionTip() + ``` From 3f0efe3bc2349d24d1622b5b3f3a1c32e2f0a46a Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 30 May 2019 15:25:55 +0800 Subject: [PATCH 2/5] Update 0000-global-api-change.md --- active-rfcs/0000-global-api-change.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/active-rfcs/0000-global-api-change.md b/active-rfcs/0000-global-api-change.md index bfa5dd5d..d05d8da5 100644 --- a/active-rfcs/0000-global-api-change.md +++ b/active-rfcs/0000-global-api-change.md @@ -107,6 +107,14 @@ const rootInstance = app.mount('#app') rootInstance instanceof Vue // true ``` +The `mount` method can also accept props to be passed to the root component via the second argument: + +``` js +app.mount('#app', { + // props to be passed to root component +}) +``` + ## Provide / Inject An app instance can also provide dependencies that can be injected by any component inside the app: From ccb4cb09a67738d205e698a75d701e167764258f Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 5 Nov 2019 21:47:47 -0500 Subject: [PATCH 3/5] Update 0000-global-api-change.md --- active-rfcs/0000-global-api-change.md | 32 ++++++++------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/active-rfcs/0000-global-api-change.md b/active-rfcs/0000-global-api-change.md index d05d8da5..fcab0422 100644 --- a/active-rfcs/0000-global-api-change.md +++ b/active-rfcs/0000-global-api-change.md @@ -36,7 +36,7 @@ new Vue({ import { createApp } from 'vue' import App from './App.vue' -const app = createApp(App) +const app = createApp() app.config.ignoredElements = [/^app-/] app.use(/* ... */) @@ -44,7 +44,7 @@ app.mixin(/* ... */) app.component(/* ... */) app.directive(/* ... */) -app.mount('#app') +app.mount(App, '#app') ``` # Motivation @@ -73,12 +73,11 @@ In this proposal we introduce a new global API, `createApp`: ``` js import { createApp } from 'vue' -import App from './App.vue' -const app = createApp(App) +const app = createApp() ``` -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. +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. ## Global API Mapping @@ -86,11 +85,10 @@ An app instance exposes a subset of the current global APIs. The rule of thumb i - Global configuration - `Vue.config` -> `app.config` - - with the exception of `Vue.config.productionTip` + - some config option changes will be discussed in a separate RFC. - 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` @@ -99,18 +97,16 @@ All other global APIs that do not globally mutate behavior are now named exports ## 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: +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: ``` js -const rootInstance = app.mount('#app') - -rootInstance instanceof Vue // true +const rootInstance = app.mount(App, '#app') ``` -The `mount` method can also accept props to be passed to the root component via the second argument: +The `mount` method can also accept props to be passed to the root component via the third argument: ``` js -app.mount('#app', { +app.mount(App, '#app', { // props to be passed to root component }) ``` @@ -160,13 +156,3 @@ N/A - The transformation is straightforward (as seen in the basic example). - Moved methods can be replaced with stubs that emit warnings to guide migration. - 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? - - ``` js - import { suppressProductionTip } from 'vue' - - suppressProductionTip() - ``` From f75ce940ce95a97500c33aae0d763d93c457e2fd Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 8 Nov 2019 18:25:53 -0500 Subject: [PATCH 4/5] update: specify config option changes - remove config.productionTip - replace config.ignoredElements with config.isCustomElement --- active-rfcs/0000-global-api-change.md | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/active-rfcs/0000-global-api-change.md b/active-rfcs/0000-global-api-change.md index fcab0422..7a29e921 100644 --- a/active-rfcs/0000-global-api-change.md +++ b/active-rfcs/0000-global-api-change.md @@ -85,7 +85,8 @@ An app instance exposes a subset of the current global APIs. The rule of thumb i - Global configuration - `Vue.config` -> `app.config` - - some config option changes will be discussed in a separate RFC. + - `config.productionTip`: removed. ([details](#remove-config-productiontip)) + - `config.ignoredElements` -> `config.isCustomElement`. ([details](#config-ignoredelements-config-iscustomelement)) - Asset registration APIs - `Vue.component` -> `app.component` - `Vue.directive` -> `app.directive` @@ -134,6 +135,31 @@ export default { This is similar to using the `provide` option in a 2.x root instance. +## Remove `config.productionTip` + +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). + +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. + +## `config.ignoredElements` -> `config.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 version: + +``` js +// before +Vue.config.ignoredElements = ['my-el', /^ion-/] + +// after +const app = Vue.createApp() +app.config.isCustomElement = tag => tag.startsWith('ion-') +``` + +**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). + +- 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; + +- This will be a new top-level option in the Vue CLI config. + # Drawbacks ## Plugin auto installation @@ -156,3 +182,4 @@ N/A - The transformation is straightforward (as seen in the basic example). - Moved methods can be replaced with stubs that emit warnings to guide migration. - A codemod can also be provided. +- For `config.ingoredElements`, a compat shim can be easily provided. From 6510e688cd89c1a96e594adce71b33c5ec1b8636 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 12 Nov 2019 11:33:26 -0500 Subject: [PATCH 5/5] Rename 0000-global-api-change.md to 0009-global-api-change.md --- .../{0000-global-api-change.md => 0009-global-api-change.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename active-rfcs/{0000-global-api-change.md => 0009-global-api-change.md} (100%) diff --git a/active-rfcs/0000-global-api-change.md b/active-rfcs/0009-global-api-change.md similarity index 100% rename from active-rfcs/0000-global-api-change.md rename to active-rfcs/0009-global-api-change.md