之前对tree shaking的认识仅仅停留在无用代码剔除上。今天来深究总结一下。
什么是tree shaking
Tree shaking is a term commonly used in the JavaScript context for dead-code elimination
Tree shaking是一个用在js中删除无用代码的术语。
在webpack 2版本中webpack内置支持了ES2015 modules,并且也支持了无用模块的导出检测。webpack 4版本在此功能上进行了扩展,并通过在package.json 中添加“ sideEffects” 属性向编译器提供提示,以标示项目中的哪些文件是“纯”的,从而可以安全的移除。
背后依据的原理
所以tree shaking背后依据的原理肯定和es module息息相关— 依赖的是ES6模块的静态分析能力,体现在:
- Export和import 只能出现在文件代码的顶层
- 使用 import 导入的变量是readonly的,类似const
demo实验
下面的例子都是用最新版的webpack
一个tree shaking可行的demo
// test.js:
export const one = function (value) {
console.log("this is one");
return value
};
export const two = function (value) {
console.log("this is two");
return value;
};
// index.js
import { one, two } from "./test";
console.log(two("test"));
在生产环境下打包(下同),发现函数one没有被打包进去:
一个tree shaking不可行的demo
// test-one.js
window.testOne = () => {
console.log("this is window test one")
}
export const anotherTest = () => {
console.log("another test")
}
// test.js
import { anotherTest } from "./test-one.js";
export const one = function (value) {
console.log("this is one");
return value;
};
export const two = function (value) {
console.log("this is two");
return value;
};
// index.js
import { one, two } from "./test";
console.log(two("test"))
打包后:
可以看到test-one.js 中的anotherTest虽然已经exported,因为没有被使用,所以被tree shaking了。但是,test-one.js中的window.testOne代码却被打包了。如果我想要的效果是:test-one.js export 的内容没有用到就不需要打包该怎么办呢?下面来介绍一下package.json中可以添加的一个属性sideEffects。
sideEffects
sideEffects 顾名思义是side effects,也就是副作用(函数式编程中的一个名词)。在webpack4中,通过在package.json 中添加"sideEffects": false , 可以向webpack指明整个项目是没用副作用的,可以安全的 tree-shaking 。sideEffects 也可以接受一个数组,数组的每一项是文件路径,用于保留这些文件的副作用:
"sideEffects":["./src/global.config.js"]
用sideEffects解决第二个demo的问题
在package.json中配置"sideEffects": false 后,我们再来打包看一下结果:
我们可以看到window.testOne确实没有被打包了
sideEffects 的注意点
有时候在项目中我们确实想“引入一些有副作用的文件”,比如我们想在window对象上定义一些js函数,供native端调用 (js bridge):
// global.js
window.NativeBridge = {
share: () => {
console.log("this is native share")
}
}
此时我们在index.js中调用global.js
import { one, two } from "./test";
import "./global.js"
console.log(two("test"));
打包后发现global.js根本没有被打包进文件。导致这种错误的原因是:我们是通过import "./global.js" 引入文件的,webpack 会把所有import "xxx" 看做是引入了文件,但是没有使用的。 如果此时在package.json中配置"sideEffects": false ”, 那么就global.js会被 tree-shaking 。
解决中类似import "xxx" 的方法是在`"sideEffects”中表明有副作用的文件:
"sideEffects":["./src/global.js"]
Tree-shaking 目前的局限
直接上代码:
// test.js
import { isDate } from "lodash-es"
export const one = function (value) {
console.log("this is one");
return isDate(value);
};
export const two = function (value) {
console.log("this is two");
return value;
};
// index.js
import { one, two } from "./test";
import "./global.js"
console.log(two("test"));
打包后:
可以看到虽然函数one没有没使用,但是lodash-es的部分代码还是被打包了。
解决方法:
- 把one函数修改为纯函数,用到的lodash的isDate方法作为依赖注入:
export const one = function (value, isDate ) {
console.log("this is one");
return isDate(value);
};
- 使用第三方webpack插件: webpack-deep-scope-analysis-plugin
关于babel
为了确保babel不会将代码编译为commonjs,配置 Babel preset @babel/preset-env 的modules属性为false
总结
想要在webpack 中更好的使用tree shaking,那么
- 需要使用ES6 模块
- 在使用babel的时候,不能编译转化为非es6模块
- 合理的加sideEffects。其实sideEffects就是来通知webpack可以安全的进行tree-shaking的,如果有些包真的是有副作用, 那么也可以在sideEffects中配置。
- 在写代码时尽量考虑到副作用的产生,合理避免。
参考: