【webpack 从入门到使用】Tree Shaking

说明

  1. 这套笔记是对于 webpack 5.x 进行阐述的。(webpack <= 4 用法稍有不同)

什么是 Tree Shaking ?

tree shaking 是一个术语,通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块语法的 静态结构 特性,例如 importexport。这个术语和概念实际上是由 ES2015 模块打包工具 rollup 普及起来的。

  • webpack 2 正式版本内置支持 ES2015 模块(也叫做 harmony modules)和未使用模块检测能力。
  • 新的 webpack 4 正式版本扩展了此检测能力,通过 package.json 的 "sideEffects" 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 "pure(纯正 ES2015 模块)",由此可以安全地删除文件中未使用的部分。

注意

  • tree shaking 只能够在 ESModule 中使用,不能在其他的模块化(CommonJSAMDCMDUMD)使用!具体的原因是只有 ES6 的语法形式(静态 import 优先)才能让 tree-shaking 感知操作。
  • ESMoudle 的动态代码分割 (Dynamic import : () => import('xxx'))也是不支持 tree-shaking的

webpack 对于 tree shaking 的支持程度

在 webpack5 的生产环境中,默认支持 tree shaking ;在开发环境中,需要配置以下两个选项:usedExports 和 sideEffects

1. usedExports

mode = 'development' 时,配置 optimization.usedExports 可以告诉 webpack 在开发环境中需要开启 tree shaking 模式 (代码会分析出哪一部分是需要用到的,哪一部分不需要用到)

js 复制代码
const config = {
  // ...
  optimization: {
    usedExports: true
  },
  // ...
};

module.exports = config;

2. sideEffects

开启 tree shaking 之后,webpack 进行代码分析的时候可能会具有副作用;在 package.json中设置 sideEffects告诉 webpack ,哪一部分代码需要 / 不需要开启 tree shaking

  • 如果设置为 false,表示所有的代码都没有副作用,webpack 直接 tree shaking 即可
  • 如果设置为 *.xxx, 表示后缀名为 xxx 的文件具有副作用,webpack 不进行分析
  • 如果有多个规则,sideEffects使用 [] 连接起来

参考 tree-shaking 示例

参考 www.webpackjs.com/guides/tree...

  1. 在我们的项目中添加一个新的通用模块文件 src/math.js,并导出两个函数: project
lua 复制代码
webpack-demo
|- package.json
|- package-lock.json
|- webpack.config.js
|- /dist
  |- bundle.js
  |- index.html
|- /src
  |- index.js
+ |- math.js
|- /node_modules

src/math.js

js 复制代码
export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}
  1. 需要将 mode 配置设置成development,以确定 bundle 不会被压缩:

webpack.config.js

js 复制代码
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
+ mode: 'development',
+ optimization: {
+   usedExports: true,
+ },
};
  1. 配置完这些后,更新入口脚本,使用其中一个新方法,并且为了简化示例,我们先将 lodash 删除:

src/index.js

js 复制代码
- import _ from 'lodash';
+ import { cube } from './math.js';

  function component() {
-   const element = document.createElement('div');
+   const element = document.createElement('pre');

-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   element.innerHTML = [
+     'Hello webpack!',
+     '5 cubed is equal to ' + cube(5)
+   ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());

注意,我们没有从 src/math.js 模块中 import 另外一个 square 方法 。这个函数就是所谓的"未引用代码(dead code)",也就是说,应该删除掉未被引用的 export。现在运行 npm script npm run build,并查看输出的 bundle:

dist/bundle.js (around lines 90 - 100)

js 复制代码
/* 1 */
/***/ (function (module, __webpack_exports__, __webpack_require__) {
  'use strict';
  /* unused harmony export square */
  /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  function square(x) {
    return x * x;
  }

  function cube(x) {
    return x * x * x;
  }
});

注意,上面的 unused harmony export square 注释。如果你观察它下面的代码,你会注意到虽然我们没有引用 square,但它仍然被包含在 bundle 中。我们将在后面的章节解决这个问题。

将文件标记为 side-effect-free(无副作用)

在一个纯粹的 ESM 模块世界中,很容易识别出哪些文件有副作用。然而,我们的项目无法达到这种纯度,所以,此时有必要提示 webpack compiler 哪些代码是"纯粹部分"。

通过 package.json 的 "sideEffects" 属性,来实现这种方式。

json 复制代码
{
  "name": "your-project",
  "sideEffects": false
}

如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack 它可以安全地删除未用到的 export。

Tip

"side effect(副作用)" 的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

如果你的代码确实有一些副作用,可以改为提供一个数组:

json 复制代码
{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

此数组支持简单的 glob 模式匹配相关文件。其内部使用了 glob-to-regexp(支持:***{a,b}[a-z])。如果匹配模式为 *.css,且不包含 /,将被视为 **/*.css

Tip

注意,所有导入文件都会受到 tree shaking 的影响。这意味着,如果在项目中使用类似 css-loader 并 import 一个 CSS 文件,则需要将其添加到 side effect 列表中,以免在生产模式中无意中将它删除:

json 复制代码
{
  "name": "your-project",
  "sideEffects": ["./src/some-side-effectful-file.js", "*.css"]
}

最后,还可以在 module.rules 配置选项 中设置 "sideEffects"

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui