持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
简介
webpack官方有标准的说法:Tree-shaking的本质是消除无用的js代码。无用代码消除在广泛存在于传统的编程语言编译器中,编译器可以判断出某些代码根本不影响输出,然后消除这些代码,这个称之为DCE(dead code elimination
webpack5中有一项的重大变更就是构建优化
,其中就有针对tree-shaking做出的调整
webpack5以前的tree-shaking
webpack5 以前,tree-shaking 比较简单,主要是找import
进来的变量是否在这个模块内出现过,出现过则不剔除,不出现过则剔除。并且用于 esModule 中
webpack5 更新tree-shaking
嵌套的 tree-shaking
Webpack 现在能够跟踪对导出的嵌套属性的访问。这可以改善重新导出命名空间对象时的 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
。
内部模块 tree-shaking
Webpack 4 没有分析模块的导出和引用之间的依赖关系。webpack 5 有一个新的选项 optimization.innerGraph
,在生产模式下是默认启用的,它可以对模块中的标志进行分析,找出导出和引用之间的依赖关系。
在这样的模块中:
import { something } from './something';
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
内部依赖图算法会找出 something
只有在使用 test
导出时才会使用。这允许将更多的出口标记为未使用,并从代码包中省略更多的代码。
当设置"sideEffects": false
时,可以省略更多的模块。在这个例子中,当 test
导出未被使用时,./something
将被省略。
要获得未使用的导出信息,需要使用 optimization.unusedExports
。要删除无副作用的模块,需要使用optimization.sideEffects
。
可以分析以下标记。
-
函数声明
-
类声明
-
默认导出export default
或定义变量以下的:- 函数表达式
- 类表达式
- 顺序表达式
/*#__PURE__*/
表达式- 局部变量
- 引入的捆绑(bindings)
使用 eval()
将为一个模块放弃这个优化,因为经过 eval 的代码可以引用范围内的任何标记。
这种优化也被称为深度范围分析。 webpack5,可以进行根据作用域之间的关系进行优化。比如: a.js 中到处了两个方法 a 和 b,在 index.js 中引入了 a.js 到处的 a 方法,没有引用 b 方法。那么 webpack4 打包出来的结果包含了 index.js 和 a.js 的内容,包含了没有用到的 b 方法。但是 webpack5 的 treeshaking,会进行作用域分析,打包结果只有 index 和 a 文件中的 a 方法,没有用到的 b 方法是不会被打包进来的。
CommonJs Tree Shaking
Webpack 曾经不进行对 CommonJs 导出和 require()
调用时的导出使用分析。
Webpack 5 增加了对一些 CommonJs 构造的支持,允许消除未使用的 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
-
未来计划支持更多的构造
当检测到不可分析的代码时,webpack 会放弃,并且完全不跟踪这些模块的导出信息(出于性能考虑)。