# 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也能识别

最后更新: 11/18/2024, 12:28:36 PM