MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Webpack 打包速度与文件体积的平衡优化

2024-05-225.7k 阅读

Webpack 打包速度优化

1. 升级 Webpack 和相关插件

Webpack 自身在不断发展,新版本通常在性能上有优化。同时,一些常用插件如 babel - loadercss - loader 等也需要及时更新。例如,Webpack 5 相较于 Webpack 4 在打包速度上有显著提升,其内置了更好的持久化缓存机制。

// package.json
{
  "devDependencies": {
    "webpack": "^5.0.0",
    "webpack - cli": "^4.0.0",
    "babel - loader": "^8.0.0"
  }
}

通过 npm installyarn install 安装新版本依赖,这有助于利用最新的性能优化成果。

2. 优化 loader 配置

  • 减少 loader 执行次数
    • 配置 includeexclude 选项,让 loader 只处理特定目录下的文件。例如,babel - loader 只需要处理 src 目录下的 JavaScript 文件。
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            use: 'babel - loader',
            include: path.resolve(__dirname,'src'),
            exclude: /node_modules/
          }
        ]
      }
    };
    
    • 这样可以避免对庞大的 node_modules 目录进行不必要的处理,大大减少 babel - loader 的执行次数,提高打包速度。
  • 优化 loader 性能
    • 对于 babel - loader,可以启用缓存。在 webpack.config.js 中配置如下:
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel - loader',
              options: {
                cacheDirectory: true
              }
            },
            include: path.resolve(__dirname,'src'),
            exclude: /node_modules/
          }
        ]
      }
    };
    
    • cacheDirectory 选项会启用缓存,babel 将编译结果缓存到文件系统中。下次编译相同文件时,直接从缓存中读取,而不需要重新编译,显著提升编译速度。

3. 合理使用插件

  • TerserPlugin 优化
    • TerserPlugin 是 Webpack 内置的用于压缩 JavaScript 的插件。在 Webpack 5 中,默认启用了 parallel 选项,它会并发运行代码压缩,利用多核 CPU 的优势提高压缩速度。但如果项目中 CPU 资源有限,可以适当调整 parallel 的值。
    const TerserPlugin = require('terser - webpack - plugin');
    module.exports = {
      optimization: {
        minimizer: [
          new TerserPlugin({
            parallel: true, // 根据 CPU 核心数调整,默认自动检测
            terserOptions: {
              compress: {
                drop_console: true // 移除 console.log 等语句,进一步优化体积
              }
            }
          })
        ]
      }
    };
    
  • html - webpack - plugin 优化
    • 这个插件用于生成 HTML 文件并自动注入打包后的 JavaScript 和 CSS 文件。如果项目中有多个 HTML 页面,可以为每个页面单独配置 html - webpack - plugin 实例,避免不必要的重复操作。
    const HtmlWebpackPlugin = require('html - webpack - plugin');
    module.exports = {
      plugins: [
        new HtmlWebpackPlugin({
          template: './src/index.html',
          filename: 'index.html'
        }),
        new HtmlWebpackPlugin({
          template: './src/about.html',
          filename: 'about.html'
        })
      ]
    };
    
    • 这样每个 HTML 页面都能针对性地进行处理,而不是所有页面共用一个配置导致不必要的资源处理。

4. 配置 resolve

  • 优化 alias
    • 使用 alias 可以为常用模块路径设置别名,减少 Webpack 查找模块的时间。例如,项目中经常引用 src/components 目录下的组件,可以这样配置:
    module.exports = {
      resolve: {
        alias: {
          '@components': path.resolve(__dirname,'src/components')
        }
      }
    };
    
    • 这样在代码中引用组件时就可以使用 @components 别名,如 import Button from '@components/Button';,Webpack 能更快地找到模块位置。
  • 减少 resolve.extensions
    • resolve.extensions 用于配置 Webpack 在解析模块时尝试的文件扩展名。尽量减少这个数组中的扩展名数量,因为 Webpack 会按照顺序逐一尝试这些扩展名来查找模块。例如,如果项目主要使用 .js.jsx 文件,可以这样配置:
    module.exports = {
      resolve: {
        extensions: ['.js', '.jsx']
      }
    };
    
    • 避免像 ['.js', '.json', '.jsx', '.css', '.less', '.scss'] 这样包含过多扩展名的配置,减少不必要的查找时间。

5. 启用多进程打包

  • thread - loader
    • thread - loader 可以将耗时的 loader 操作分配到多个子进程中并行执行。它需要在 webpack.config.js 中配置在其他 loader 之前。例如:
    module.exports = {
      module: {
        rules: [
          {
            test: /\.js$/,
            use: [
              'thread - loader',
              'babel - loader'
            ],
            include: path.resolve(__dirname,'src'),
            exclude: /node_modules/
          }
        ]
      }
    };
    
    • thread - loader 会根据 CPU 核心数自动分配任务,在多核 CPU 的机器上能显著提升打包速度。不过要注意,进程间通信会有一定开销,对于小型项目可能提升效果不明显,甚至可能会因为开销导致打包变慢。

6. 分析打包结果

  • 使用 Webpack Bundle Analyzer
    • 安装 webpack - bundle - analyzer 插件:npm install --save - dev webpack - bundle - analyzer
    • webpack.config.js 中配置:
    const BundleAnalyzerPlugin = require('webpack - bundle - analyzer').BundleAnalyzerPlugin;
    module.exports = {
      plugins: [
        new BundleAnalyzerPlugin()
      ]
    };
    
    • 运行 Webpack 打包后,它会打开一个浏览器窗口,以可视化的方式展示打包后的文件体积分布。可以直观地看到哪些模块体积较大,哪些模块引用过多,从而针对性地进行优化。例如,如果发现某个第三方库体积过大,可以考虑是否有更小的替代品,或者是否可以只引入部分功能。

Webpack 文件体积优化

1. 代码分割

  • 动态导入(import())
    • 使用 ES2020 的动态导入语法 import() 可以实现代码的按需加载,从而将大的 JavaScript 文件分割成多个小的 chunk。例如,在一个大型的单页应用中,可能有一些路由组件不需要在首页加载时就全部引入。
    const routes = [
      {
        path: '/home',
        component: () => import('./components/Home')
      },
      {
        path: '/about',
        component: () => import('./components/About')
      }
    ];
    
    • 这样在 Webpack 打包时,HomeAbout 组件会被单独打包成各自的 chunk 文件。当用户访问对应的路由时,才会加载相应的 chunk,减少了初始加载的文件体积。
  • splitChunks 配置
    • splitChunks 是 Webpack 中用于代码分割的强大配置选项。它可以将第三方库、公共模块等提取出来,避免在多个 chunk 中重复包含。
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all',
          minSize: 30000,
          minChunks: 1,
          maxAsyncRequests: 5,
          maxInitialRequests: 3,
          automaticNameDelimiter: '~',
          name: true,
          cacheGroups: {
            vendors: {
              test: /[\\/]node_modules[\\/]/,
              priority: -10
            },
            default: {
              minChunks: 2,
              priority: -20,
              reuseExistingChunk: true
            }
          }
        }
      }
    };
    
    • chunks: 'all' 表示对所有类型的 chunk 都进行分割;minSize 设置分割的最小文件大小;cacheGroups 可以定义不同的缓存组,如 vendors 组专门处理 node_modules 中的第三方库,将它们提取到一个单独的文件中,减少主 bundle 的体积。

2. 优化图片资源

  • 使用 image - webpack - loader
    • 安装 image - webpack - loadernpm install --save - dev image - webpack - loader
    • webpack.config.js 中配置:
    module.exports = {
      module: {
        rules: [
          {
            test: /\.(png|jpg|jpeg|gif)$/i,
            use: [
              {
                loader: 'image - webpack - loader',
                options: {
                  mozjpeg: {
                    progressive: true,
                    quality: 65
                  },
                  // optipng.enabled: false will disable optipng
                  optipng: {
                    enabled: false
                  },
                  pngquant: {
                    quality: [0.65, 0.90],
                    speed: 4
                  },
                  gifsicle: {
                    interlaced: false
                  },
                  // the webp option will enable WEBP
                  webp: {
                    quality: 75
                  }
                }
              }
            ]
          }
        ]
      }
    };
    
    • 这个 loader 可以在打包时对图片进行压缩,通过调整各种图片格式的压缩参数,如 mozjpegqualitypngquantqualityspeed 等,可以在保证图片质量可接受的前提下,显著减小图片文件的体积。
  • 采用合适的图片格式
    • 对于色彩丰富的照片,JPEG 格式通常是较好的选择,但可以通过压缩降低质量。对于有透明度的图片,PNG 是常用格式,不过对于简单的图标,可以考虑使用 SVG。SVG 是矢量图形,无论如何缩放都不会失真,并且文件体积通常较小。在代码中可以直接引入 SVG 文件,Webpack 可以通过 svg - loader 等进行处理。
    <img src="./icon.svg" alt="icon">
    
    • 或者在 JavaScript 中使用:
    import icon from './icon.svg';
    // 然后在组件中使用 icon 进行渲染
    

3. CSS 优化

  • 压缩 CSS
    • 使用 css - minimizer - webpack - plugin 插件来压缩 CSS 文件。安装:npm install --save - dev css - minimizer - webpack - plugin
    const MiniCssExtractPlugin = require('mini - css - extract - plugin');
    const CssMinimizerPlugin = require('css - minimizer - webpack - plugin');
    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [MiniCssExtractPlugin.loader, 'css - loader']
          }
        ]
      },
      optimization: {
        minimizer: [
          new CssMinimizerPlugin()
        ]
      },
      plugins: [
        new MiniCssExtractPlugin()
      ]
    };
    
    • 该插件会移除 CSS 中的冗余空格、注释等,缩短选择器和属性名,从而减小 CSS 文件的体积。
  • Tree - shaking CSS
    • 虽然 Tree - shaking 主要用于 JavaScript,但对于 CSS 也有类似的概念。一些工具如 purgecss - webpack - plugin 可以帮助实现 CSS 的 Tree - shaking。它会分析 HTML 和 JavaScript 文件,找出实际使用的 CSS 规则,移除未使用的部分。
    • 安装:npm install --save - dev purgecss - webpack - plugin
    const PurgeCSSPlugin = require('purgecss - webpack - plugin');
    const glob = require('glob - all');
    module.exports = {
      plugins: [
        new PurgeCSSPlugin({
          paths: glob.sync(`${path.join(__dirname, 'src')}/**/*`, {nodir: true}),
          safelist: function () {
            return {
              standard: ['body - dark']
            };
          }
        })
      ]
    };
    
    • paths 配置需要分析的文件路径,safelist 可以指定一些不被移除的 CSS 类,比如可能通过 JavaScript 动态添加的类。

4. 移除未使用的代码

  • TerserPlugin 移除未使用代码
    • 前面提到的 TerserPlugin 不仅可以压缩代码,还能移除未使用的代码。在 terserOptions 中配置 compress.drop_console: true 可以移除 console.log 等语句。同时,它还能进行死代码消除。例如,如果有一个函数从未被调用,TerserPlugin 会将其移除。
    const TerserPlugin = require('terser - webpack - plugin');
    module.exports = {
      optimization: {
        minimizer: [
          new TerserPlugin({
            parallel: true,
            terserOptions: {
              compress: {
                drop_console: true
              }
            }
          })
        ]
      }
    };
    
  • ESLint 规则辅助移除未使用代码
    • 可以通过 ESLint 规则来发现未使用的变量、函数等。例如,no - unused - vars 规则会检测未使用的变量。在 .eslintrc.json 文件中配置:
    {
      "rules": {
        "no - unused - vars": "error"
      }
    }
    
    • 开发过程中,遵循 ESLint 规则及时清理未使用的代码,有助于减少最终打包文件的体积。

5. 优化第三方库引入

  • 按需引入
    • 对于一些大型的第三方库,很多时候我们只需要使用其中的部分功能。例如,lodash 是一个常用的工具库,如果项目中只需要使用 debounce 函数,可以按需引入。
    import debounce from 'lodash/debounce';
    
    • 而不是 import _ from 'lodash'; 这样引入整个库,大大减小了引入的代码体积。
  • 寻找轻量级替代品
    • 如果某个功能有多个第三方库可供选择,在满足项目需求的前提下,优先选择体积小的库。例如,对于简单的日期处理,day - js 的体积就比 moment.js 小很多。
    // 使用 day - js
    import dayjs from 'day - js';
    const now = dayjs();
    
    • 相比之下,moment.js 功能更全面,但体积也更大,如果项目中只是简单的日期格式化,day - js 是更好的选择。

平衡打包速度与文件体积

1. 权衡优化策略

  • 不同阶段的重点
    • 在开发阶段,打包速度更为重要,因为频繁的代码修改和编译需要快速反馈。此时可以适当放宽对文件体积的要求,例如减少对图片等资源的过度压缩,关闭一些耗时但对体积优化效果显著的插件(如 purgecss - webpack - plugin 在开发阶段可以先不启用)。而在生产阶段,文件体积则成为关键,需要全面启用各种体积优化策略,哪怕这可能会稍微增加一些打包时间。
  • 硬件环境考虑
    • 如果部署服务器的带宽有限,那么文件体积优化就尤为重要,因为较小的文件体积可以加快用户的加载速度。而如果开发机器配置较低,过多的多进程打包等优化可能会导致机器资源耗尽,反而降低打包速度,此时需要根据实际硬件情况调整优化策略。例如,在低配置开发机器上,减少 thread - loader 的并行数量。

2. 持续监控与调整

  • 定期分析打包结果
    • 随着项目的不断发展,代码结构和依赖会发生变化。定期使用 webpack - bundle - analyzer 分析打包结果,观察文件体积和模块引用情况。如果发现某个模块体积突然增大,或者出现了新的大体积依赖,及时进行调查和优化。
  • 性能指标设定
    • 为项目设定明确的打包速度和文件体积性能指标。例如,规定打包时间不能超过 30 秒,初始加载的 JavaScript 和 CSS 文件总体积不能超过 200KB 等。每次发布或重大代码变更后,检查是否满足这些指标,不满足时及时调整优化策略。

3. 自动化优化流程

  • 脚本集成
    • 可以编写一些脚本将优化过程自动化。例如,在 package.json 中定义不同的脚本用于开发和生产环境的打包。
    {
      "scripts": {
        "dev": "webpack serve --config webpack.dev.js",
        "build": "webpack --config webpack.prod.js"
      }
    }
    
    • webpack.dev.js 中可以侧重于打包速度优化,而 webpack.prod.js 则重点进行文件体积优化。这样通过简单的命令就可以切换不同的优化模式。
  • CI/CD 集成
    • 将优化流程集成到 CI/CD 管道中。在每次代码提交或合并时,自动运行打包和优化过程,并检查是否满足性能指标。如果不满足,CI/CD 流程可以失败并通知开发人员进行调整,确保项目始终保持较好的打包速度和文件体积平衡。