likes
comments
collection
share

Webpack Tree Shaking原理与实践Webpack 的 Tree Shaking 是一种优化技术,用于删除

作者站长头像
站长
· 阅读数 33

Webpack 的 Tree Shaking 是一种优化技术,用于删除未使用的代码,特别是在 ES6 模块系统中。Webpack 通过静态分析模块的导入和导出来识别和移除那些没有被引用的代码。

原理:

  1. ES6 模块语法:Tree Shaking 需要 ES6 模块语法(import 和 export),因为 CommonJS(require 和 module.exports)语法不支持静态分析。

  2. 静态分析:Webpack 通过分析模块的静态结构来决定哪些部分是可摇动的。这意味着导入和导出必须是静态的,不能在运行时动态改变。

  3. UglifyJS 或 terser:Webpack 通常会配合压缩工具(如 UglifyJS 或 terser)来删除经过标记的未使用代码。

实践:

  1. 配置文件:在 Webpack 配置文件中,开启生产模式 (mode: 'production') 会自动启用 Tree Shaking。
// webpack.config.js
module.exports = {
  mode: 'production',
  // ...其他配置
};
  1. ES6 导入:使用默认导出和命名导出,而不是 CommonJS 导入。
// library.js
export function unusedFunction() {}
export default function usedFunction() {}

// consumer.js
import usedFunction from './library'; // 默认导出,Tree Shaking 可能
import { unusedFunction } from './library'; // 命名导出,Tree Shaking 不会移除
  1. Pure Comments:对于那些有副作用的函数,可以通过注释告诉编译器函数是纯净的(无副作用)。
// library.js
/*#__PURE__*/ export function pureFunction() {} // 标记为纯净,Tree Shaking 可能
  1. Side Effects:在 package.json 文件中,使用 "sideEffects": false 声明模块没有副作用,这样 Webpack 就可以安全地摇掉未被引用的导出。
// package.json
{
  "sideEffects": false
}
  1. terser 配置:在生产构建中,配置 terser 插件以优化 Tree Shaking 效果。
// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 删除console语句
            drop_debugger: true, // 删除debugger语句
            pure_funcs: ['console.log'] // 移除指定的无副作用函数
          }
        }
      })
    ]
  }
};
  1. 避免动态导入:Tree Shaking 无法处理动态导入,因为它们在编译时无法确定。
// 不好的例子,动态导入无法Tree Shaking
if (someCondition) {
  import('./dynamicModule').then(module => {
    module.default();
  });
}

// 好的例子,静态导入可以Tree Shaking
import('./staticModule').then(module => {
  module.default();
});

Tree Shaking进阶分析

Scope Hoisting:

Scope Hoisting 是 Webpack 的另一个优化特性,它可以改善代码的结构,使模块间的调用更高效,进而提升 Tree Shaking 的效果。当开启 Scope Hoisting 后,Webpack 会尝试将模块合并到它们的父模块中,减少代码中的作用域链长度。

在 Webpack 4 及以上版本中,Scope Hoisting 是默认开启的,你不需要做额外配置。但在一些特殊情况下,你可能需要手动关闭它来调试或对比性能差异。

模块分析:

为了更好地理解哪些代码被 Tree Shaking 掉了,哪些没有,你可以使用 Webpack 的分析工具来生成报告。

// webpack.config.js
module.exports = {
  // ...
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

安装 webpack-bundle-analyzer 包并配置到你的 Webpack 中,这将生成一个交互式的报告,展示每个模块的大小和它们之间的依赖关系,帮助你识别哪些部分可以进一步优化。

按需加载库:

对于大型库,如 lodash,使用 Lodash 的按需加载功能可以显著减小打包体积。通过直接导入你所需的函数而不是整个库,Webpack 可以更容易地 Tree Shake 未使用的代码。

// 错误示例:引入整个lodash库
import _ from 'lodash';

// 正确示例:按需导入所需函数
import uniq from 'lodash/uniq';

外部化库:

对于不常变动的大型依赖(如 React、Vue 等),可以考虑通过 externals 配置将其作为外部依赖,这样它们就不会被打包进最终的 bundle 中,而是作为单独的脚本文件加载。这样做不仅减少了打包时间,也使得浏览器可以缓存这些库,提高加载速度。

// webpack.config.js
module.exports = {
  // ...
  externals: {
    react: 'React',
    'react-dom': 'ReactDOM'
  }
};

然后,在 HTML 页面中直接通过 <script> 标签引入这些库。

利用 Tree Shaking 友好的库:

选择那些为 Tree Shaking 设计的库,这些库通常会使用 Rollup 或其他工具进行打包,确保导出的模块是 Tree Shaking 兼容的。在选用第三方库时,查看其文档或 GitHub 仓库,了解其是否支持 Tree Shaking。

使用 sideEffects 与 exports 语法:

在某些情况下,库可能会包含一些副作用,即使它们没有被直接导入。为了帮助 Webpack 更准确地识别这些副作用,库开发者可以在 package.json 文件中声明 sideEffects 属性。

// package.json
{
  "name": "my-library",
  "sideEffects": ["*.css", "*.scss"],
  // ...
}

这告诉 Webpack 只有匹配这些模式的文件才可能包含副作用,其他文件可以安全地 Tree Shaking。

另外,对于那些使用 CommonJS 或 UMD 格式的库,可以使用 exports 字段来指示 Tree Shaking 应该如何处理它们。这需要在 package.json 中添加:

// package.json
{
  "name": "my-old-library",
  "exports": {
    ".": "./index.js",
    "./legacy": "./legacy.js"
  },
  // ...
}

这样,即使库使用了 CommonJS,Webpack 也能识别哪些导出是可以 Tree Shaking 的。

使用 import() 动态导入:

虽然动态导入不能被 Tree Shaking,但它们可以用来按需加载代码,减少初始加载时间。这种方式适用于那些非关键路径的代码,如路由切换或用户触发的功能。

import('./optionalFeature').then(feature => {
  feature.activate();
});

优化模块打包:

有时,模块打包方式会影响 Tree Shaking 的效果。例如,使用 rollup-plugin-node-resolve 和 rollup-plugin-commonjs 插件时,确保它们的配置支持 Tree Shaking:

// rollup.config.js
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';

export default {
  // ...
  plugins: [
    resolve(),
    commonjs({
      include: /node_modules/, // 只转换 node_modules 目录下的 CommonJS 模块
      namedExports: {
        // 具体库的 Tree Shaking 支持
        'react': ['Component', 'createElement'],
        'lodash': ['debounce', 'throttle']
      }
    })
  ]
};

避免使用 IIFE:

立即调用的函数表达式 (IIFE) 会阻止 Tree Shaking,因为它们在运行时执行,无法进行静态分析。如果库使用了 IIFE,尝试找到一个不使用它的替代品,或者联系库作者请求支持 Tree Shaking。

测试 Tree Shaking:

在开发过程中,确保 Tree Shaking 正常工作的一个好方法是检查打包后的代码。查看是否有未使用的函数或变量被移除,或者使用 source-map-explorer 等工具可视化打包结果,以确保 Tree Shaking 的效果。

通过这些高级实践,可以更有效地利用 Tree Shaking,优化你的项目,减少代码体积,提高应用性能。不过,要注意 Tree Shaking 并不能解决所有性能问题,它只是优化的一部分,还需要结合其他优化技术,如代码分割、懒加载、压缩等。

转载自:https://juejin.cn/post/7377589884189540362
评论
请登录