举一个 tree shaking 的栗子:

boy.js

1
2
3
4
// boy.js
import {logger} from '@ies/eden-util';
export const name = 'name';
export const age = 15;

index.js

1
2
3
// index.js
import {name} from './boy.js';
export function getName (x) { return name; };

构建结果:

bundle.js

1
2
const name = 'name';
export function getName (x) { return name; };

可以看到,age 因为没有被使用,所以在最终的构建结果中被自动删除了,这就是 tree shaking,帮助我们删除未使用过的代码。

相对于 dce 的成熟,tree shaking 是一个新提出的概念。因为它的实现,依赖于 es module 的规范定义,可以使我们有机会对 js 进行依赖的静态分析。

如何在 webpack 中使用

webpack 2 后的版本会自动 开启 tree shaking,但是它的实际生效是有条件的。

  • 代码必须是 es module 格式。
  • 若引用 external 模块,它的 package.json 中必须标明该模块不含副作用( sideEffects 为 false)。可以参考:https://github.com/webpack/webpack/tree/master/examples/side-effects

    这就要求我们必须使用 es module 来写代码,也要保证代码传给 webpack 时,代码格式没被 babel 自动转为 commonjs。因为 babel-preset-env 会自动开启 commonjs 模块转换,会把 es module 转为 commonjs,我们需要把 babel-preset 配置为 module: false 。

webpack 在 tree shaking 的 bug

还是上面的例子,我们把 boy.js 增加一个方法:

1
2
3
4
5
6
7
// boy.js
import {logger} from '@ies/eden-util';
export const name = 'name';
export const age = 15;
export function getAge () {
logger.log('Age: ', age);
}

index.js 维持不变

1
2
const name = 'name';
export function getName (x) { return name; };

可以发现构建产物发生了变化:

bundle.js

1
2
3
4
5
6
7
8
const logger = {
log: function(msg) {
console.log(msg);
}
};

const name = 'name';
export function getName (x) { return name; };

因为 index.js 中没有引用 getAge 方法,我们希望 getAge 方法和它所依赖的 logger 都不要打入最终的 bundle 中,结果可以看到 logger 被打了进来,webpack 不能进行更深一层的副作用依赖分析。

可以使用 https://github.com/vincentdchan/webpack-deep-scope-analysis-plugin 这个插件来修复这个问题。