webpack tree-shaking解析
生产环境、开发环境 treeShaking
初始化工作
npm init -y 初始化 package.json 文件,然后安装 webpack 包
npm install -D webpack webpack-cli
根目录下,创建 webpack.config.js
// ./webpack.config.js
const path = require("path");
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "../dist"),
filename: "bundle.js",
},
mode: 'production',
}
然后在 src/treeShaking 文件夹下创建 a.js
// ./src/treeShaking/a.js
export const a = () => {
console.log('a');
}
export const b = () => {
console.log('b');
}
然后在 src 文件夹下创建入口文件 index.js
import { a, b } from "./treeShaking/a";
a();
const unused = '死代码1'
if (false) {
console.log('死代码2')
}
index.js 文件中,b是引入了但没有使用的模块变量,所以它是死代码,unused 是定义了但没有用到的变量,也是死代码,false判断条件里面的 console.log也不会被执行,所以也是死代码
生产环境直接打包
上面我们已经配置了 webpack 的 mode 是 production,直接打包看效果
在 package.json 里面配置命令
"scripts": {
"build": "webpack --config ./webpack/webpack.treeShaking.js",
},
然后 npm run build 打包后的 dist/bundle.js 文件:

我们发现只有 a 被打包了,其他死代码都被 treeshaking 掉了,也就是说,在 production 模式下,默认开启了 treeshaking。那 treeshaking 是怎么工作的呢?
开发环境观察treeShaking
修改一下 webpack 的配置
const path = require("path");
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "../dist"),
filename: "bundle.js",
},
mode: 'development',
devtool: 'source-map',
optimization: {
//开发环境观察 tree-shaking 配置,打包后会标注 export、import 的变量是否用到了
usedExports: true,
}
}
这个时候我们再打包观察 bundle.js 文件


我们发现导出的 exports 对象上,只挂载了 a 函数,b函数用注释 /* unused harmony export b */标记了,if判断里面的逻辑被删除了,unused虽然没有使用但还是保存了下来。注意,此时只做了标记操作,删除死代码的逻辑是在 Taser-webpack-plugin 里面做的
然后我们再修改一下 webpack 的配置,开启 minimize: true手动 tree-shaking
const path = require("path");
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, "../dist"),
filename: "bundle.js",
},
// mode: 'production',
mode: 'development',
devtool: 'source-map',
optimization: {
//开发环境观察 tree-shaking 配置,打包后会标注 export、import 的变量是否用到了
usedExports: true,
//删除被标记,也就是没有用到的死代码
minimize: true
}
}
再打包

发现只有 a 函数被挂载到 exports 对象上,b、unused、if 都被删除了
treeShaking流程
可以概括为三个步骤:
- 收集模块导出变量,并将其记录到
ModuleGraph变量中 - 遍历
ModuleGraph变量,标记当前模块导出变量是否被使用上,没被使用就通过/* unused harmony export b */标记 - 打包时,通过
Taser-webpack-plugin删除被/* unused harmony export b */标记的模块导出变量
CommonJS 可以 treeShaking 吗
如果 index.js 里面是这样的代码
// ./src/index.js
if (Math.random() > 0.5) {
require('a').then((res) => console.log(res))
} else {
require('b').then((res) => console.log(res))
}
那现在我们就不能判断到底是 a 用到了还是 b 用到了,因为 require 是动态导入,对于 CommonJs, 在运行时才能知道到底哪些模块变量用到了哪些没用到
而对于 ESModule,它通过 export、import 静态导入导出的语法,在 编译 时,就知道了哪些模块变量用到了哪些没用到,所以 treeShaking 只对 ES module 形式导入导出的模块生效
sideEffects 副作用
某些情况下, treeShaking 会失效,比如下面的代码
// ./src/treeShaking/a.js
window.a = '全局a'
// ./src/index.js
import './treeShaking/a'
console.log('bbb');
然后打包,观察 bundle.js 文件

按理说,我在 window 对象上挂了一个属性 a,但是我并没有使用 window.a,应该会被 treeShaking 掉,但是打包后的结果并没有删除掉
原因是 Webpack 的 Tree Shaking 逻辑停留在代码 静态分析 层面,只是浅显地判断:
- 模块导出变量是否被其它模块引用
- 引用模块的主体代码中有没有出现这个变量
更深层次的原因则是产生意料之外的副作用
这个时候,可以通过配置 package.json 的 sideEffects 来消除副作用
它的配置选项如下:
true:所有文件都有副作用,不可以 tree-shakingfalse:所有文件没有副作用,可以 tree-shaking数组:只有数组中的文件有副作用不可以被 tree-shaking,其它的都可以 tree-shaking
然后我们配置了 sideEffects: false 之后,标识所有文件都没有副作用,都要做 treeShaking,然后重新打包

结语
以上就是本篇 treeShaking 的内容,如果对源码有兴趣,可以去看这篇文章 # Webpack 原理系列九:Tree-Shaking 实现原理
转载自:https://juejin.cn/post/7246219936594821180