生产环境、开发环境 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 实现原理