最近从小被子那里学了不少 Tree-shaking 的知识,Tree-shaking 译作“摇树优化”,是 DCE(Dead Code Elimination)优化的一种实现。
Webpack 在编译阶段,通过分析模块依赖,对代码中未使用到的对象和 function 进行标注,再通过压缩工具“摇”掉这些多余的代码。
首先我们使用以下命令,来搭建一套实验环境,加深对 Tree-shaking 的理解。
# 初始化项目目录与 package.json
mkdir demo && cd demo
npm init -y
# 安装 webpack,当前版本 4.35.0
npm i -D webpack webpack-cli
# 创建源码目录和文件
mkdir src
touch src/index.js src/util.js
其中 util.js
文件的内容如下:
function funcA() {
return "funcA";
}
function funcB() {
return "funcB";
}
export { funcA, funcB };
index.js
文件的内容如下:
import { funcA } from "./util";
const name = funcA();
console.log(name);
然后我们在项目根目录中,执行 npx webpack
来编译项目代码,得到编译后的代码 dist/main.js
。我们对它进行格式化,能看到以下内容:
!(function(e) {
// ... 省略
})([
function(e, t, r) {
"use strict";
r.r(t);
console.log("funcA");
}
]);
从结果上来看,因为util.js
中的funcB
没有被 index.js
import,所以最终编译的代码main.js
中没有 funcB
相关的代码。funcB 被 tree-shaking 掉了。
接下来,我们改变 index.js
中 import 语句的写法:
import * as util from "./util";
const name = util.funcA();
console.log(name);
发现最终结果同上:funcB 被 tree-shaking 掉了。
由此可见,import * as 的效果和 import {} 解构的效果是一样的:都是先取到 import 的对象,再基于对象上的属性是否被使用来进行标注。
接下来,我们修改下 util.js
的内容,在 export 后面加上一个 default
关键字试试:
function funcA() {
return "funcA";
}
function funcB() {
return "funcB";
}
export default { funcA, funcB };
由于加上了 default,所以 index.js
文件中对 util 的调用需要加多一级 default 属性的引用:
import * as util from "./util";
const name = util.default.funcA();
console.log(name);
同时,又因为我们的 util.js
中只有一条默认的 export default,所以可以直接写成以下同时去掉 * as 和 default 的写法:
import util from "./util";
const name = util.funcA();
console.log(name);
我们格式化下最终编译的 dist/main.js
发现,虽然 funcB 没有被调用,但是依然被打包进了源码:
!(function(e) {
// ... 省略
})([
function(e, n, t) {
"use strict";
t.r(n);
const r = {
funcA: function() {
return "funcA";
},
funcB: function() {
return "funcB";
}
}.funcA();
console.log(r);
}
]);
由此可见,export default 对象被 import 后,挂在 default 上的属性和方法,即使没有被调用,也无法被 tree-shaking。
所以我们在组织模块文件时,应当尽可能避免 export default {A, B, C} 的写法。
讲完上面的 demo,我们以实际的 lodash 库为例,看看不同的 import 方式对打包后的文件大小的影响。
首先,我们继续在上面的 demo 项目中安装 lodash 模块(当前版本 4.17.15)
npm i -S lodash
将 index.js
修改为如下内容:
import { cloneDeep } from 'lodash'
const name = cloneDeep({id:'猫哥学前班'})
console.log(name)
执行 npx webpack
后,可以看到 dist/main.js
文件的大小为 70.9KB
。
如果我们将第一行函数引入方式修改为按需引用:
import cloneDeep from 'lodash/cloneDeep'
const name = cloneDeep({id:'猫哥学前班'})
console.log(name)
编译后文件为 17.8KB
,大小减少了 75%。
Webpack 官网提到,要开启模块的 Tree-shaking,需要满足以下四个条件:
"sideEffects"
属性的定义(false
表示所有文件无副作用,可启用 Tree Shaking)Lodash 为了能支持 Tree Shaking,同时发布了 lodash-es 版本模块,我们将 index.js
修改一下,看看是否有效果:
import { cloneDeep } from 'lodash-es'
const name = cloneDeep({id:'猫哥学前班'})
console.log(name)
可以看到,开启 Tree-shaking 后,编译大小减少至 14.6KB
,比上述按需引用的方式更小。
Tree-shaking 的使用场景,主要是为了将第三方 npm 模块中未用到的代码剔除出打包后的文件。如果是项目本身的 src 文件,Tree-shaking 的意义并不大。
因为项目本身的业务代码不应该存在能被 shake 掉的 dead code,如果有,可以通过代码覆盖率工具找到它们,并从源码中优化掉。
业务代码可以按照 Vendor 和 Router 的维度进行 Code Splitting,而模块中的代码则需要做好 sideEffects 的声明。
贴张前端技术群里的讨论,仅供参考。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。