Tree Shaking 是一种优化技术,用于移除模块之间的无效代码。与代码压缩不同,代码压缩主要是移除模块内部的无效代码,而 Tree Shaking 则是移除模块之间未使用的导出。这可以显著减少最终打包文件的体积,提高加载速度。
1. 背景
某些模块导出的代码并不一定会被用到。例如:
// myMath.jsexport function add(a, b) { console.log("add"); return a + b;}export function sub(a, b) { console.log("sub"); return a - b;}
// index.jsimport { add } from "./myMath";console.log(add(1, 2));
在这个例子中,sub
函数并没有被使用,Tree Shaking 可以移除掉未使用的 sub
函数。
2. 使用
从 Webpack 2 开始,就支持了 Tree Shaking。只要在生产环境中,Tree Shaking 会自动开启。
3. 原理
Webpack 会从入口模块出发,寻找依赖关系。当解析一个模块时,Webpack 会根据 ES6 的模块导入语句来判断,该模块依赖了另一个模块的哪个导出。Webpack 选择 ES6 的模块导入语句,是因为 ES6 模块有以下特点:
导入导出语句只能是顶层语句:不能放在条件语句或循环中。import 的模块名只能是字符串常量:不能是变量。import 绑定的变量是不可变的:一旦导入,不能重新赋值。这些特征非常适合进行静态分析,从而确定哪些代码是未使用的。
在具体分析依赖时,Webpack 坚持的原则是:保证代码正常运行,然后再尽量 Tree Shaking。因此,如果你依赖的是一个导出的对象,由于 JavaScript 语言的动态特性,以及 Webpack 的智能限制,为了保证代码正常运行,它不会移除对象中的任何信息。
因此,我们在编写代码时,尽量:
使用export xxx
导出,而不使用 export default {xxx}
导出使用 import {xxx} from "xxx"
导入,而不使用 import xxx from "xxx"
导入 依赖分析完毕后,Webpack 会根据每个模块每个导出是否被使用,标记其他导出为 dead code
,然后交给代码压缩工具处理。代码压缩工具最终移除掉那些 dead code
代码。
4. 使用第三方库
某些第三方库可能使用的是 CommonJS 方式导出,比如 lodash
,或者没有提供普通的 ES6 方式导出。对于这些库,Tree Shaking 是无法发挥作用的。因此,要寻找这些库的 ES6 版本。好在很多流行但没有使用 ES6 的第三方库,都发布了它们的 ES6 版本,比如 lodash-es
。
5. 作用域分析
Tree Shaking 本身并没有完善的作用域分析,可能导致在一些 dead code
函数中的依赖仍然被视为依赖。插件 webpack-deep-scope-plugin
提供了作用域分析,可以解决这些问题。
6. 副作用问题
Webpack 在 Tree Shaking 的使用中,有一个原则:一定要保证代码正确运行。在满足该原则的基础上,再来决定如何 Tree Shaking。因此,当 Webpack 无法确定某个模块是否有副作用时,它往往将其视为有副作用。这可能导致某些情况并不是我们所想要的。
例如:
// common.jsvar n = Math.random();// index.jsimport "./common.js";
虽然我们根本没有使用 common.js
的导出,但 Webpack 担心 common.js
有副作用,如果去掉会影响某些功能。如果要解决该问题,就需要标记该文件是没有副作用的。
副作用:函数运行过程中,可能会对外部环境造成影响的功能
在 package.json
中加入 sideEffects
:
{ "sideEffects": false}
有两种配置方式:
false:当前工程中,所有模块都没有副作用。注意,这种写法会影响到某些 CSS 文件的导入。数组:设置哪些文件拥有副作用,例如:["!src/common.js"]
,表示只要不是 src/common.js
的文件,都有副作用。 这种方式通常由第三方库在其 package.json
中标注。
7. CSS Tree Shaking
Webpack 无法对 CSS 完成 Tree Shaking,因为 CSS 与 ES6 模块没有直接关系。因此,对 CSS 的 Tree Shaking 需要其他插件完成。例如,purgecss-webpack-plugin
可以实现 CSS 的 Tree Shaking。
const PurgecssPlugin = require('purgecss-webpack-plugin');const path = require('path');module.exports = { plugins: [ new PurgecssPlugin({ paths: [ path.resolve(__dirname, './src/**/*.html'), path.resolve(__dirname, './src/**/*.js'), path.resolve(__dirname, './src/**/*.vue') ], safelist: { standard: ['body', 'html'], deep: [/^btn-/], greedy: [/^nav-/] } }) ]};
注意:purgecss-webpack-plugin
对 CSS Modules 无能为力。
8. 总结
通过配置 Tree Shaking,可以显著减少最终打包文件的体积,提高加载速度。