# Tree-shaking
Tree-Shaking:删除没用到的代码。
webpack目前只支持ES6方式引入可以tree-shaking(ES 模块引入是静态引入,而用commonjs require这种引入则是动态引入);
//webpack.config.js
optimization:{
usedExports:true,
},
但是如果如 import 'xxx1'这样没有导出具体的内容tree-shaking会忽略这些。所以在package.json中配置'sideEffects:["xxx1"]'即可不影响改内容
在webpack打包的development环境,不会去除未被使用部分的内容,但是会标注信息在文件中。
在production环境中optimization默认配置好了,不需要自行配置了.
// 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);
打包后
!(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 * as util from "./util";
const name = util.funcA();
console.log(name);
发现最终结果同上:funcB 被 tree-shaking 掉了。
由此可见,import * as 的效果和 import {} 解构的效果是一样的:都是先取到 import 的对象,再基于对象上的属性是否被使用来进行标注。
- 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);
最终打包结果
!(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} 的写法。
import { cloneDeep } from 'lodash' // 打包体积大
import cloneDeep from 'lodash/cloneDeep' //体积小
// Lodash 为了能支持 Tree Shaking,同时发布了 lodash-es 版本模块
import { cloneDeep } from 'lodash-es'// 体积小
如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack 它可以安全地删除未用到的 export。
// package.json
{
"name": "your-project",
"sideEffects": false
}
如果代码确实有一些副作用,可以改为提供一个数组:此数组支持简单的 glob 模式匹配相关文件。
{
"name": "your-project",
"sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}
特别注意的是:
// index.js
import {fn1} from './use.js'
console.warn('my love')
fn1()
// use.js
const fn1 =function(){
console.log('xxxxxxx')
}
// 没被引入的fn2被tree-shaking树摇了
const fn2 =function(){
console.log('yyyyyyy')
}
// 如果fn2在这里执行了,虽然没有被index引用fn2方法,但是use.js和index有引用关系,fn2也会被打包 √
// 这个use.js的文件内容也被执行了
console.log('zzzzz')
export {fn1,fn2}
// dist打包
(() => {
"use strict";
console.log("zzzzz"), console.warn("my love"), console.log("xxxxxxx")
})();
tree-shaking (opens new window)
# rollup的tree-shaking
//input.js
import {foo} from './utils.js'
foo()
//utils
export function foo(obj){
obj&&obj.foo
}
export function bar(obj){
obj&&obj.bar
}
npx rollup input.js -f esm -o bundle.js
//bundle.js
function foo(obj){
obj&&obj.foo
}
foo()
此时,bar函数已被移除,但是foo只是读了obj.foo的值,似乎也没必要保存,为什么不去额外处理,因为害怕副作用。
如果一个函数调用产生了副作用,就不能将其移除,简单说就是对外部产生影响,例如修改了全局变量,或者obj.foo假如触发了getter的某种收集依赖,这个时候做了处理无疑是错误的。
//input.js
import {foo} from './utils.js'
/*#__PURE__*/foo()
/*#__PURE__*/告诉rollup这里是可以处理的,不必考虑副作用,那么此时打包后就会得到空文件,因为函数只是读取了obj.foo没做其他操作。
/*#__PURE__*/webpack也能识别