tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 import
和 export
。
webpack 4 正式版本扩展了此检测能力,通过 package.json
的 "sideEffects"
属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯正 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。
webpack 基础配置
npm i -D webpack webpack-cli html-webpack-plugin webpack-dev-server
Tip:以下演示为
webpack 5
先在开发环境下看一下
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin(),
],
};
src/math.js
export const add = (x, y) => x + y;
export const subtract = (x, y) => x - y;
src/index.js
import { add } from './math';
console.log(add(1, 2));
从上面看到,我们引用并使用了math.add
函数,没有使用 math.subtract
函数
执行 npx webpack
可以看到,打包结果中 math
模块的两个函数都被打包了
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin(),
],
optimization: {
// 使用 ES module 方式引用的模块将被 tree shaking 优化
usedExports: true,
},
};
执行 npx webpack
可以看到,只有 已经使用的 add
函数被暴露出去
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
plugins: [
new HtmlWebpackPlugin(),
],
};
执行 npx webpack
可以看到,只有已经使用的 add
函数的执行结果, subtract
函数就是所谓的“未引用代码(dead code)”,也就是说,应该删除掉未被引用的 export
。并且代码已经被 webpack 优化精简了
可以得出结论,tree shaking 会将通过使用 ES module 方式引用的模块中未使用的代码删除掉
tree shaking
两个关键词:1. 使用 ES module 模块方案; 2. 未使用的代码
继续验证
src/index.js
import { add, subtract } from './math';
console.log(add(1, 2));
从上面看到,我们引用了 add, subtract
但只使用了math.add
函数,没有使用 math.subtract
函数
执行 npx webpack
可以看到,打包结果中依旧只有 add
函数被打包了,未使用过的 subtract
函数被删除了
注意 Webpack 不能百分百安全地进行 tree-shaking。有些模块导入,只要被引入,就会对应用程序产生重要的影响。一个很好的例子就是 全局样式
文件,或者 全局JS
文件。
src/style.css
body {
background-color: chocolate;
}
src/todo.global.js
console.log('TODO');
src/index.js
import _ from 'lodash';
import { add, subtract } from './math';
import './todo.global';
import './style.css';
console.log(add(1, 2));
webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
plugins: [
new HtmlWebpackPlugin(),
],
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
],
},
optimization: {
usedExports: true,
},
};
执行 npx webpack serve
,你会发现 style.css
、 todo.global.js
都生效了,这是因为这两个文件不是使用的 ESmodules
方式将模块导出(export)的。
Webpack
认为这样的文件有“副作用”。具有副作用的文件不应该做 tree-shaking
,因为这将破坏整个应用程序。
如何告诉 Webpack 你的代码无副作用,可以通过 package.json
有一个特殊的属性 sideEffects
,就是为此而存在的。
sideEffects
有三个可能的值:
true:(默认值)这意味着所有的文件都有副作用,也就是没有一个文件可以
tree-shaking
。false:告诉 Webpack 没有文件有副作用,所有文件都可以
tree-shaking
。数组:是文件路径数组。它告诉 webpack,除了数组中包含的文件外,你的任何文件都没有副作用。因此,除了指定的文件之外,其他文件都可以安全地进行
tree-shaking
。
Tip
“side effect(副作用)” 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
package.json
{
"sideEffects": true, // 所有的文件都有副作用,也就是没有一个文件可以 `tree-shaking`。
}
执行 npx webpack serve
可以发现并无变化,因为这是默认的
package.json
{
"sideEffects": false, // 告诉 Webpack 没有文件有副作用,所有文件都可以 `tree-shaking`。
}
执行 npx webpack serve
可以发现上面示例中的 style.css
、 todo.global.js
都被 tree-shaking
了
显然 sideEffects
设置为 true 或则 false 显得有些鲁莽极端,你可以使用数组的方式配置文件
package.json
{
"sideEffects": ['*.css', '*.global.js'], // 告诉 Webpack 扩展名是 .css 或者 .global.js 文件视为有副作用,不要 `tree-shaking`
}
执行 npx webpack serve
可以发现上面示例中的 style.css
、 todo.global.js
都被 tree-shaking
了
webpack 4
曾经不进行对 CommonJs
导出和 require()
调用时的导出使用分析。
webpack 5
增加了对一些 CommonJs
构造的支持,允许消除未使用的 CommonJs
导出,并从 require()
调用中跟踪引用的导出名称。
sideEffects
sideEffects
和 usedExports
(更多被认为是 tree shaking)是两种不同的优化方式。
sideEffects
更为有效 是因为它允许跳过整个模块/文件和整个文件子树。
usedExports
依赖于 terser 去检测语句中的副作用。它是一个 JavaScript 任务而且没有像 sideEffects
一样简单直接。而且它不能跳转子树/依赖由于细则中说副作用需要被评估。
我们学到为了利用 tree shaking 的优势, 你必须…
import
和 export
)。package.json
文件中,添加 "sideEffects"
属性。mode
为 "production"
的配置项以启用更多优化项,包括压缩代码与 tree shaking。你可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
继续加油!ヾ(◍°∇°◍)ノ゙