一、先说V4
// src/math.js
export function square(x) {return x * x;}
export function cube(x) {return x * x * x;}
// src/index 只引用了cube
import { cube } from './math.js';
function component() {
var element = document.createElement('pre');
element.innerHTML = [
'Hello webpack!',
'5 cubed is equal to ' + cube(5)
].join('\n\n');
return element;
}
document.body.appendChild(component());
V4 生产模式下,build之后 tree-shaking的结果是只有cube,而没有square。
二、再说V5
1、解决了模块嵌套的 tree-shaking 问题
// inner.js
export const a = 1;
export const b = 2;
// module.js
export * as inner from './inner';
// 或 import * as inner from './inner';
export { inner };
// user.js
import * as module from './module';
console.log(module.inner.a);
在上面这个例子中,可以在生产模式下删除导出的b。
2、内部模块 tree-shaking ——只导出但没有用到的也会被丢弃
Webpack 4 没有分析模块的导出和引用之间的依赖关系。webpack 5 有一个新的选项 optimization.innerGraph,在生产模式下是默认启用的,它可以对模块中的标志进行分析,找出导出和引用之间的依赖关系。
在这样的模块中:
import { something } from './something';
function usingSomething() { return something;}
export function test() { return usingSomething();}
内部依赖图算法会找出 something 只有在使用 test 导出时才会使用。这允许将更多的出口标记为未使用,并从代码包中省略更多的代码。也就说以上代码如果使用webpack4编译,something由于被引用了,它就一定不会被保留,不管之后的使用是否有效。
当配置里设置"sideEffects": false时,可以省略更多的模块。在这个例子中,当 test 导出未被使用时,./something 将被省略。(PS:这个属性得酌情小心使用,置为false,意味着生产环境下,项目没有被引用到的代码都会被丢弃,比如pollify,这个通常是没有导出的)
3、CommonJs Tree Shaking
在v4版本的时候官网明确说过 :
确保没有 compiler 将 ES2015 模块语法转换为 CommonJS 模块
而在v5增加了一些支持,允许消除未使用的 CommonJs 导出,并从 require() **调用中跟踪引用的导出名称。注意:**不是百分百支持,以下是支持的:
- exports|this|module.exports.xxx = ...
- exports|this|module.exports = require("...") (reexport)
- exports|this|module.exports.xxx = require("...").xxx (reexport)
- Object.defineProperty(exports|this|module.exports, "xxx", ...)
- require("abc").xxx
- require("abc").xxx()
- 从 ESM 导入
- require() 一个 ESM 模块
- 被标记的导出类型 (对非严格 ESM 导入做特殊处理):
- Object.defineProperty(exports|this|module.exports, "__esModule", { value: true|!0 })
- exports|this|module.exports.__esModule = true|!0
PS:对于检测到的不可分析的代码时,webpack 会放弃,并且完全不跟踪这些模块的导出信息(出于性能考虑)。