pocket-manual
pocket-manual copied to clipboard
关于 Tree Shaking
对于前端开发来说,tree shaking 这个名词多多少少都有所了解。
tree shaking 是一个术语,通常用于描述移除 JS 上下文中没用的代码,这样可以有效地缩减打包体积。
Webpack 官方文档有一段很形象的描述:
你可以将应用程序想象成一棵树。绿色表示实际用到的源码和 library,是树上活的树叶。灰色表示无用的代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。
tree shaking 这个概念最早由 ES2015 模块打包工具 rollup 提出。
关于 tree shaking 的实现原理可以阅读一下这篇文章:tree shaking 性能优化实践-原理篇。
这里主要从 Webpack 的角度来谈 tree shaking。
Webpack2 增加了 tree shaking 的功能。tree shaking 之所以能够被 Webpack 支持得益于 ES6 modules 静态解析的特性。ES6 的模块声明保证了依赖关系是提前确定的,使得静态分析成为可能。
Webpack 的 tree shaking 大致流程是先通过 import 和 export 语法找出那些需要删除的无用的代码,再借助工具(如 uglifyjs-webpack-plugin)进行删除。
但是在实际使用过程中 tree shaking 效果并不明显,由于副作用影响,无用的代码并没有完全消除。
副作用的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
关于副作用,可以了解一下这篇文章:你的 tree shaking 并没什么卵用。
举一个例子,在 Webpack3 中:
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
}
}
// index.js
import { cube } from './math.js'
console.log(cube(6))
// math.js
export function square(x) {
return x * x
}
export function cube(x) {
return x * x * x
}
查看输出结果 bundle.js:

可以看出,由于 index.js 并未从 math.js 模块中 import 导入 square 方法。所以,在 bundle.js 中,square 没有被导入。但是,它仍然被包含在 bundle.js 中。这时候可以通过配置 plugins 启用 UglifyJSPlugin 插件删除无用的代码(也可以通过 webpack -p 命令):
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.optimize.UglifyJsPlugin() // 启用 UglifyJsPlugin
]
}
从输出文件 bundle.js 可以看到 square 被删除了(压缩代码就不贴截图了)。
但是,如果是有副作用的代码,即使未被 import 导入,也不会被删除:
// webpack.config.js
const path = require('path')
const webpack = require('webpack')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
}
}
// index.js
import { cube } from './math.js'
console.log(cube(6))
// math.js
export function square(x) {
return x.a
}
square({ a: 666 }) // 产生 side effects
export function cube(x) {
return x * x * x
}
从输出文件 bundle.js 可以看到副作用代码被保留下来了(还是贴一下截图吧 XD):

以上基于 webpack v3.6.0
Webpack4 对 tree shaking 进行了扩展优化,可以通过配置 package.json 的配置项 sideEffects,向 compiler 提供提示,表明项目中的哪些文件是纯的 ES2015 模块,由此可以安全地删除文件中未使用的部分。
还是上面的例子,这里简单地将 sideEffects 标记为 false,以告知 Webpack 可以安全地删除未用到的 export 导出:
// package.json
{
// ...
"sideEffects": false
// ...
}
为了删除无用的代码,在 Webpack4 中可以通过设置 mode 为 production 来启用 UglifyJSPlugin 插件(也可以通过 webpack -p 命令)。
// webpack.config.js
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
mode: 'production' // 启用 UglifyJsPlugin
}
// index.js
import { cube } from './math.js'
console.log(cube(6))
// math.js
export function square(x) {
return x * x
}
export function cube(x) {
return x * x * x
}
从输出文件 bundle.js 可以看到副作用代码被删除了(压缩代码就不贴截图了)。
当然,如果代码确实有一些副作用,那么可以将 sideEffects 改为提供一个数组:
// package.json
{
// ...
"sideEffects": [
"./src/math.js"
]
// ...
}
以上基于 webpack v4.28.3 和 webpack-cli v3.1.2
参考:
怎么没有更新
怎么没有更新
本周整理完更新一波