From 8a28a4c15cef8823bf7ab28182f2512de5a817c4 Mon Sep 17 00:00:00 2001 From: Phan An Date: Sun, 12 Jul 2020 20:13:21 +0200 Subject: [PATCH 1/6] migration: Add Global API Treeshaking --- src/.vuepress/theme/styles/custom-blocks.styl | 4 - src/guide/migration/treeshaking.md | 159 +++++++++++++++++- 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/src/.vuepress/theme/styles/custom-blocks.styl b/src/.vuepress/theme/styles/custom-blocks.styl index 5b868166a4..086df7eb33 100644 --- a/src/.vuepress/theme/styles/custom-blocks.styl +++ b/src/.vuepress/theme/styles/custom-blocks.styl @@ -16,16 +16,12 @@ color darken(#ffe564, 70%) .custom-block-title color darken(#ffe564, 50%) - a - color $textColor &.danger background-color #ffe6e6 border-color darken(red, 20%) color darken(red, 70%) .custom-block-title color darken(red, 40%) - a - color $textColor &.details display block position relative diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index 5dcee99adc..9198d73b9b 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -1,3 +1,160 @@ # Global API Treeshaking - +If you’ve ever had to manually manipulate DOM in Vue, you might have come across this pattern: + +```js +import Vue from 'vue' + +Vue.nextTick(() => { + // something something DOM-related +}) +``` + +Or, if you’ve been unit-testing an application involving [async components](/guide/component-dynamic-async.html), chances are you’ve written something like this: + +```js +import { shallowMount } from '@vue/test-utils' +import { MyComponent } from './MyComponent.vue' + +test('an async feature', async () => { + const vm = shallowMount(MyComponent) + + // execute some DOM-related tasks + + await vm.$nextTick() + +// run your assertions +}) +``` + +`Vue.nextTick()` is a global API exposed directly on a single Vue object – in fact, the instance method `$nextTick()` is just a handy wrapper around `Vue.nextTick()` with the callback’s `this` context automatically bound to the current Vue instance for convenience. + +But what if you’ve never had to deal with manual DOM manipulation, nor are you using or testing async components in our app? Or, what if, for whatever reason, you prefer to use the good old `window.setTimeout()` instead? In such a case, the code for `nextTick()` will become dead code – that is, code that’s written but never used. And dead code is hardly a good thing, especially in our client-side context where every kilobyte matters. + +Module bundlers like [webpack](https://webpack.js.org/) support [tree-shaking](https://webpack.js.org/guides/tree-shaking/), which is a fancy term for “dead code elimination.” Unfortunately, due to how the code is written in previous Vue versions, global APIs like `Vue.nextTick()` are not tree-shakeable and will be included in the final bundle regardless of where they are actually used or not. + +In Vue 3, the global and internal APIs have been restructured with tree-shaking support in mind. As a result, the global APIs can now only be accessed as named exports for the ES Modules build. For example, our previous snippets should now look like this: + +```js +import { nextTick } from 'vue' + +nextTick(() => { + // something something DOM-related +}) +``` + +and + +```js +import { shallowMount } from '@vue/test-utils' +import { MyComponent } from './MyComponent.vue' +import { nextTick } from 'vue' + +test('an async feature', async () => { + const vm = shallowMount(MyComponent) + + // execute some DOM-related tasks + + await nextTick() + + // run your assertions +}) +``` + +Calling `Vue.nextTick()` directly will now result in the infamous `undefined is not a function` error. + +With this change, provided the module bundler supports tree-shaking, global APIs that are not used in a Vue application will be eliminated from the final bundle, resulting in an optimal file size. + +## Affected APIs + +These global APIs in Vue 2.x are affected by this change: + +* `Vue.nextTick` +* `Vue.observable` +* `Vue.version` +* `Vue.compile` (only in full builds) +* `Vue.set` (only in compat builds) +* `Vue.delete` (only in compat builds) + + +## Internal Helpers + +In addition to public APIs, many of the internal components/helpers are now exported as named exports as well. This allows the compiler to output code that only imports features when they are used. For example the following template: + +```html + +
hello
+
+``` + +is compiled into something similar to the following: + +```js +import { h, Transition, applyDirectives, vShow } from 'vue' + +export function render() { + return h(Transition, [ + applyDirectives(h('div', 'hello'), this, [vShow, this.ok]) + ]) +} +``` + +This essentially means the `Transition` component only gets imported when the application actually makes use of it. In other words, if the application doesn’t have any `` component, the code supporting this feature will not be present in the final bundle. + +With global tree-shaking, the user only “pay” for the features they actually use. Even better, knowing that optional features won't increase the bundle size for applications not using them, framework size has become much less a concern for additional core features in the future, if at all. + +::: warning Important +The above only applies to the [ES Modules builds](http://localhost:8080/guide/installation.html#explanation-of-different-builds) for use with tree-shaking capable bundlers - the UMD build still includes all features and exposes everything on the Vue global variable (and the compiler will produce appropriate output to use APIs off the global instead of importing). +::: + +## Usage in Plugins + +If your plugin relies on an affected Vue 2.x global API, for instance: + +```js +const plugin = { + install: Vue => { + Vue.nextTick(() => { + // ... + }) + } +} +``` + +In Vue 3, you’ll have to import it explicitly: + +```js +import { nextTick } from 'vue' + +const plugin = { + install: app => { + nextTick(() => { + // ... + }) + } +} +``` + +If you use a module bundle like webpack, this may cause Vue’s source code to be bundled into the plugin, and more often than not that’s not what you'd expect. A common practice to prevent this from happening is to configure the module bundler to exclude Vue from the final bundle. In webpack's case, you can use the [`externals`](https://webpack.js.org/configuration/externals/) configuration option: + +```js +// webpack.config.js +module.exports = { + /*...*/ + externals: { + vue: 'Vue' + } +} +``` + +This will tell webpack “Hey, treat this Vue module as an external library and don’t bother bundling it.” + +If your module bundler of choice happens to be [Rollup](https://rollupjs.org/), you basically get the same effect for free, as by default Rollup will treat absolute module IDs (`'vue'` in our case) as external dependencies and not include them in the final bundle. During bundling though, it might emit a [“Treating vue as external dependency”](https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency) warning, which can be suppressed with the `external` option: + +```js +// rollup.config.js +export default { + /*...*/ + external: ['vue'] +} +``` From 90c2913f7a7054503616cc3aa77e67d95c68023c Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 13 Jul 2020 13:45:54 +0200 Subject: [PATCH 2/6] CR --- src/guide/migration/treeshaking.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index 9198d73b9b..05ba62bd80 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -1,5 +1,7 @@ # Global API Treeshaking +### Vue 2.x Syntax + If you’ve ever had to manually manipulate DOM in Vue, you might have come across this pattern: ```js @@ -17,11 +19,11 @@ import { shallowMount } from '@vue/test-utils' import { MyComponent } from './MyComponent.vue' test('an async feature', async () => { - const vm = shallowMount(MyComponent) + const wrapper = shallowMount(MyComponent) // execute some DOM-related tasks - await vm.$nextTick() + await wrapper.vm.$nextTick() // run your assertions }) @@ -33,6 +35,8 @@ But what if you’ve never had to deal with manual DOM manipulation, nor are you Module bundlers like [webpack](https://webpack.js.org/) support [tree-shaking](https://webpack.js.org/guides/tree-shaking/), which is a fancy term for “dead code elimination.” Unfortunately, due to how the code is written in previous Vue versions, global APIs like `Vue.nextTick()` are not tree-shakeable and will be included in the final bundle regardless of where they are actually used or not. +### Vue 3 Syntax + In Vue 3, the global and internal APIs have been restructured with tree-shaking support in mind. As a result, the global APIs can now only be accessed as named exports for the ES Modules build. For example, our previous snippets should now look like this: ```js @@ -51,7 +55,7 @@ import { MyComponent } from './MyComponent.vue' import { nextTick } from 'vue' test('an async feature', async () => { - const vm = shallowMount(MyComponent) + const wrapper = shallowMount(MyComponent) // execute some DOM-related tasks @@ -147,7 +151,7 @@ module.exports = { } ``` -This will tell webpack “Hey, treat this Vue module as an external library and don’t bother bundling it.” +This will tell webpack to treat the Vue module as an external library and not bundle it. If your module bundler of choice happens to be [Rollup](https://rollupjs.org/), you basically get the same effect for free, as by default Rollup will treat absolute module IDs (`'vue'` in our case) as external dependencies and not include them in the final bundle. During bundling though, it might emit a [“Treating vue as external dependency”](https://rollupjs.org/guide/en/#warning-treating-module-as-external-dependency) warning, which can be suppressed with the `external` option: From 815370d8db8c3ef50b3cd6810515fdc2265967f9 Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 13 Jul 2020 13:46:34 +0200 Subject: [PATCH 3/6] Fix heading levels --- src/guide/migration/treeshaking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index 05ba62bd80..136d5f0fb2 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -1,6 +1,6 @@ # Global API Treeshaking -### Vue 2.x Syntax +## Vue 2.x Syntax If you’ve ever had to manually manipulate DOM in Vue, you might have come across this pattern: @@ -35,7 +35,7 @@ But what if you’ve never had to deal with manual DOM manipulation, nor are you Module bundlers like [webpack](https://webpack.js.org/) support [tree-shaking](https://webpack.js.org/guides/tree-shaking/), which is a fancy term for “dead code elimination.” Unfortunately, due to how the code is written in previous Vue versions, global APIs like `Vue.nextTick()` are not tree-shakeable and will be included in the final bundle regardless of where they are actually used or not. -### Vue 3 Syntax +## Vue 3 Syntax In Vue 3, the global and internal APIs have been restructured with tree-shaking support in mind. As a result, the global APIs can now only be accessed as named exports for the ES Modules build. For example, our previous snippets should now look like this: From 9de190183f0d33178453fd6ab3b1bff52487c362 Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 13 Jul 2020 13:48:07 +0200 Subject: [PATCH 4/6] Use "previous/current syntax" for consistency --- src/guide/migration/treeshaking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index 136d5f0fb2..edc7865c2a 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -1,6 +1,6 @@ # Global API Treeshaking -## Vue 2.x Syntax +## Previous Syntax If you’ve ever had to manually manipulate DOM in Vue, you might have come across this pattern: @@ -35,7 +35,7 @@ But what if you’ve never had to deal with manual DOM manipulation, nor are you Module bundlers like [webpack](https://webpack.js.org/) support [tree-shaking](https://webpack.js.org/guides/tree-shaking/), which is a fancy term for “dead code elimination.” Unfortunately, due to how the code is written in previous Vue versions, global APIs like `Vue.nextTick()` are not tree-shakeable and will be included in the final bundle regardless of where they are actually used or not. -## Vue 3 Syntax +## Current Syntax In Vue 3, the global and internal APIs have been restructured with tree-shaking support in mind. As a result, the global APIs can now only be accessed as named exports for the ES Modules build. For example, our previous snippets should now look like this: From 25a20acb9a66a39314a9b6fb5e875970781fe0fd Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 13 Jul 2020 15:43:02 +0200 Subject: [PATCH 5/6] Update src/guide/migration/treeshaking.md Co-authored-by: Natalia Tepluhina --- src/guide/migration/treeshaking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index edc7865c2a..1955d46570 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -108,7 +108,7 @@ This essentially means the `Transition` component only gets imported when the ap With global tree-shaking, the user only “pay” for the features they actually use. Even better, knowing that optional features won't increase the bundle size for applications not using them, framework size has become much less a concern for additional core features in the future, if at all. ::: warning Important -The above only applies to the [ES Modules builds](http://localhost:8080/guide/installation.html#explanation-of-different-builds) for use with tree-shaking capable bundlers - the UMD build still includes all features and exposes everything on the Vue global variable (and the compiler will produce appropriate output to use APIs off the global instead of importing). +The above only applies to the [ES Modules builds](/guide/installation.html#explanation-of-different-builds) for use with tree-shaking capable bundlers - the UMD build still includes all features and exposes everything on the Vue global variable (and the compiler will produce appropriate output to use APIs off the global instead of importing). ::: ## Usage in Plugins From 19fbfc34fe7a0ce636c4f451afb5a08dc91bd2b4 Mon Sep 17 00:00:00 2001 From: Phan An Date: Mon, 13 Jul 2020 15:45:53 +0200 Subject: [PATCH 6/6] chore: mention that Vue.observable is replaced by Vue.reactive --- src/guide/migration/treeshaking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/guide/migration/treeshaking.md b/src/guide/migration/treeshaking.md index edc7865c2a..695ef33b41 100644 --- a/src/guide/migration/treeshaking.md +++ b/src/guide/migration/treeshaking.md @@ -74,7 +74,7 @@ With this change, provided the module bundler supports tree-shaking, global APIs These global APIs in Vue 2.x are affected by this change: * `Vue.nextTick` -* `Vue.observable` +* `Vue.observable` (replaced by `Vue.reactive`) * `Vue.version` * `Vue.compile` (only in full builds) * `Vue.set` (only in compat builds)