Tree Shaking 在计算机中表示消除死代码。
Tree Shaking 最早源于 LISP,用于消除未调用的代码,后来也被应用于其他的语言,例如 Javacript、Dart 等。
Javacript 中的 Tree Shaking 依赖于 ESModule 的静态语法分析,不执行任何代码,就可以明确地知道模块的依赖关系。最早源自打包工具 rollup,rollup 主要用于对 ESModule 相关的代码进行打包。Webpack2 开始内置支持 ESModule 和检测未使用模块的能力;Webpack4 正式扩展了这个能力,并且通过 pacjage.json
中的 sideEffects 属性作为标记,告知 Webpack 在编译时哪些文件可以安全地被删除掉;在 Webpack5 中,也提供了对部分 CommonJS 语句的 Tree Shaking 支持。
在 Webpack 中实现 Tree Shaking 可以采用两种不同的方案:usedExports 或者 sideEffects。这两种方案可以独立使用,也可以结合使用。
usedExports:
usedExports:对未使用的函数进行标记,然后就可以通过 Terser 将其删除掉。
production 模式下默认会开启。
- 新建
src/math.js
和src/index.js
,并编写代码。// src/math.js export const increase = (num1, num2) => { return num1 + num2 } export const decrease = (num1, num2) => { return num1 - num2 }
// src/index.js import {increase} from './math' console.log(increase(10, 20))
- 在
webpack.config.js
配置文件中进行配置。// webpack.config.js module.exports = { // 由于 production 模式下默认会做很多的优化,此处为了能够更好地查看到 Tree Shaking 的效果,使用 development 模式 mode: 'development', // 此处配置 source-map 是为了能更清晰地查看打包后文件的内容 devtool: 'source-map', }
- 运行
webpack
命令进行打包,会发现,即使src/math.js
中的 decrease 函数并没有被导入使用,也还是被导出了。
- 在
webpack.config.js
配置文件中进行配置。module.exports = { // 由于 production 模式下默认会做很多的优化,此处为了能够更好地查看到 Tree Shaking 的效果,使用 development 模式 mode: 'development', // 此处配置 source-map 是为了能更清晰地查看打包后文件的内容 devtool: 'source-map', optimization: { // 对未使用的函数进行标记 usedExports: true, } }
- 运行
webpack
命令进行打包,会发现,src/math.js
中的 decrease 函数没有再被导出了,并且被标记多了一行注释/* unused harmony export decrease */
。如果配置了 Terser,这行注释将会被 Terser 解析,然后 Terser 就会根据这行注释将 decrease 函数删除掉。
- 在
webpack.config.js
配置文件中进行配置。const TerserPlugin = require('terser-webpack-plugin') module.exports = { // 由于 production 模式下默认会做很多的优化,此处为了能够更好地查看到 Tree Shaking 的效果,使用 development 模式 mode: 'development', // 此处配置 source-map 是为了能更清晰地查看打包后文件的内容 devtool: 'source-map', optimization: { // 对未使用的函数进行标记 usedExports: true, // 使用 Terser 将未使用的函数删除掉 minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { keep_fnames: true, } }) ] } }
- 运行
webpack
命令进行打包,会发现,src/math.js
中的 decrease 函数没有再被打包到输出文件中了。
sideEffects:
sideEffects:告知 Webpack Compiler 哪些模块有副作用。如果模块有副作用的话,导入了该模块但是并没有使用,打包时该模块将不会被删除掉;如果模块没有副作用的话,导入了该模块但是并没有使用,打包时该模块将会直接被删除掉。
模块有副作用的意思是:模块中的代码有执行一些有副作用的操作。
通过配置 usedExports 实现对整个模块的 Tree Shaking 存在的问题:
- 新建
src/format.js
和src/index.js
文件,并编写代码。// src/format.js export const dateFormat = () => { return '2023-10-28' }
src/index.js import './format' console.log('index')
- 在
webpack.config.js
配置文件中进行配置。const TerserPlugin = require('terser-webpack-plugin') module.exports = { // 由于 production 模式下默认会做很多的优化,此处为了能够更好地查看到 Tree Shaking 的效果,使用 development 模式 mode: 'development', // 此处配置 source-map 是为了能更清晰地查看打包后文件的内容 devtool: 'source-map', optimization: { // 对未使用的函数进行标记 usedExports: true, // 使用 Terser 将未使用的函数删除掉 minimize: true, minimizer: [ new TerserPlugin({ terserOptions: { keep_fnames: true, } }) ] } }
- 运行
webpack
命令进行打包,会发现,配置 usedExports 后,dateForamt 函数确实没有被打包到输出文件中,但是对src/format.js
文件的引入还存在。
通过配置 sideEffects 实现对整个模块的 Tree Shaking:
- 新建
src/format.js
、src/format.js
和src/index.js
文件,并编写代码。// src/format.js export const dateFormat = () => { return '2023-10-28' } // 没有副作用
// src/math.js export const sum = () => { return 30 } // 有副作用 window.message = 'Hello World'
src/index.js import './format' import './math' console.log('index')
- 在
webpack.config.js
配置文件中进行配置。module.exports = { // 由于 production 模式下默认会做很多的优化,此处为了能够更好地查看到 Tree Shaking 的效果,使用 development 模式 mode: 'development', // 此处配置 source-map 是为了能更清晰地查看打包后文件的内容 devtool: 'source-map', }
- 在
package.json
中配置 sideEffects 来告知 Webpack Compiler 哪些模块有副作用。// package.json "sideEffects": [ "./src/math.js", // 如果属性值是一个数组,需要为数组添加一个项 `**.css` 来CSS 文件进行配置,否则的话,项目中引入的 CSS 文件会被作为没有副作用的模块,在打包时被删除掉。 "**.css" ],
sideEffects 的属性值可以是数组,来表示哪些模块有副作用;也可以是布尔值,为 true 表示所有模块都有副作用,为 false 表示所有模块都没有副作用。
在开发中,更推荐编写纯模块。因此,更推荐配置"sideEffects": false
来将所有模块都作为没有副作用的模块,此时对 CSS 的配置可以放到webpack.config.js
配置文件中。// webpack.config.js module: { rules: [ { test: /\.css$/, use: ["style-loader", "css-loader"], // 将匹配到的所有 CSS 文件都作为有副作用的文件 sideEffects: true, } ] }
- 运行
webpack
命令进行打包,会发现,没有副作用的src/format.js
整个文件都没有被打包到输出文件中,有副作用的src/math.js
被保留了下来。