Skip to content

Webpack

插件(Plugins)

在 Webpack 的构建流程中,插件(Plugins)是扩展和定制功能的核心机制。它们可以在编译器的不同阶段插入钩子,实现诸如生成 HTML、提取静态资源、优化体积、压缩代码、复制文件等功能。常用插件既包括官方内置(或官方维护)的插件,也包括社区提供的高效第三方插件。

核心功能插件

HtmlWebpackPlugin

简化生成 HTML 文件,将打包后的脚本自动注入到 HTML 中,支持自定义模板与生产环境最小化处理。它尤其适用于名称带 hash 的文件,确保每次编译都能正确引用最新脚本。

js
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new HtmlWebpackPlugin({
      title: '应用名称',
      template: './src/index.html',
      filename: 'index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true,
      },
      // 注入选项: true | 'body' | 'head' | false
      inject: true,
    }),
  ],
}

MiniCssExtractPlugin

将 CSS 从 JS 中分离,生成独立的 CSS 文件,支持按需加载与 SourceMap,非常适合生产环境的样式提取。

js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  // ... existing code ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../',
            },
          },
          'css-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[id].[contenthash:8].css',
    }),
  ],
}

CleanWebpackPlugin

在每次构建前清理输出目录,删除旧的无用文件,保持 dist 文件夹干净,防止冗余资源累积

js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new CleanWebpackPlugin({
      // 不删除的文件或文件夹
      cleanOnceBeforeBuildPatterns: ['**/*', '!static-files/**'],
      // 在每次构建前清理匹配的文件
      cleanStaleWebpackAssets: true,
      // 在控制台输出日志
      verbose: true,
    }),
  ],
}

优化相关插件

TerserWebpackPlugin

使用 Terser 压缩 JavaScript,支持 ES6+ 语法,Webpack 5 默认集成,Webpack 4 需要单独安装。

js
const TerserPlugin = require('terser-webpack-plugin')

module.exports = {
  // ... existing code ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 移除 console
            drop_debugger: true, // 移除 debugger
          },
          format: {
            comments: false, // 移除注释
          },
        },
        extractComments: false, // 不将注释提取到单独的文件
        parallel: true, // 使用多进程并行运行
      }),
    ],
  },
}

CssMinimizerWebpackPlugin

基于 cssnano 优化与压缩 CSS,支持并行与缓存,比 optimize-css-assets-webpack-plugin 更完善。

js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  // ... existing code ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  optimization: {
    minimizer: [
      '...', // 继承默认配置
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true },
            },
          ],
        },
        parallel: true, // 启用多进程并行处理
      }),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
}

SplitChunksPlugin

内置插件,负责自动拆分共享模块,减少重复代码并实现缓存优化。自 webpack 4 起取代 CommonsChunkPlugin。

js
module.exports = {
  // ... existing code ...
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有模块都进行分割
      minSize: 20000, // 生成 chunk 的最小体积
      minRemainingSize: 0,
      minChunks: 1, // 拆分前必须共享模块的最小 chunks 数
      maxAsyncRequests: 30, // 最大异步请求数
      maxInitialRequests: 30, // 最大初始化请求数
      enforceSizeThreshold: 50000,
      cacheGroups: {
        defaultVendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          reuseExistingChunk: true,
          name: 'vendors',
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
  },
}

HtmlMinimizerWebpackPlugin

使用多种底层工具(如 html-minifier-terser、@swc/html、@minify-html/node)对 HTML 进行压缩、最小化。

js
const HtmlMinimizerPlugin = require('html-minimizer-webpack-plugin')

module.exports = {
  // ... existing code ...
  optimization: {
    minimize: true,
    minimizer: [
      '...', // 继承默认配置
      new HtmlMinimizerPlugin({
        minimizerOptions: {
          collapseWhitespace: true,
          removeComments: true,
          removeRedundantAttributes: true,
          removeScriptTypeAttributes: true,
          removeStyleLinkTypeAttributes: true,
          useShortDoctype: true,
        },
        parallel: true, // 启用多进程并行处理
      }),
    ],
  },
}

CompressionWebpackPlugin

生成 Gzip 或 Brotli 等压缩资源,便于 HTTP 服务端以 Content-Encoding 交付,提升传输效率。

js
const CompressionPlugin = require('compression-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new CompressionPlugin({
      algorithm: 'gzip', // 压缩算法
      test: /\.(js|css|html|svg)$/, // 匹配文件
      threshold: 10240, // 只处理大于此大小的资源(以字节为单位)
      minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
      deleteOriginalAssets: false, // 是否删除原始资源
    }),
  ],
}

ImageMinimizerWebpackPlugin

集成 imagemin,自动优化(压缩)图片资源,减小体积,适合有大量图片的项目。

js
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')

module.exports = {
  // ... existing code ...
  optimization: {
    minimizer: [
      '...', // 继承默认配置
      new ImageMinimizerPlugin({
        minimizer: {
          implementation: ImageMinimizerPlugin.imageminMinify,
          options: {
            plugins: [
              ['gifsicle', { interlaced: true }],
              ['jpegtran', { progressive: true }],
              ['optipng', { optimizationLevel: 5 }],
              ['svgo', { plugins: [{ removeViewBox: false }] }],
            ],
          },
        },
        // 启用文件缓存
        cache: true,
        // 仅处理更改的图像
        filter: (source) => {
          return source.byteLength > 8192 // 只处理大于 8kb 的图像
        },
      }),
    ],
  },
}

资源管理插件

CopyWebpackPlugin

将项目中的静态文件或目录复制到输出目录,如字体、图标、静态 HTML 等。

js
const CopyPlugin = require('copy-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: 'public', // 源目录
          to: 'assets', // 目标目录
          globOptions: {
            ignore: ['**/index.html'], // 忽略的文件
          },
        },
        {
          from: 'src/static/favicon.ico',
          to: 'favicon.ico',
        },
      ],
    }),
  ],
}

DefinePlugin

内置插件,可在编译时注入全局常量(如 process.env.NODE_ENV),方便不同环境下的条件编译,无需额外安装。

js
const webpack = require('webpack')

module.exports = {
  // ... existing code ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
      'process.env.API_URL': JSON.stringify('https://api.example.com'),
      VERSION: JSON.stringify('1.0.0'),
      PRODUCTION: process.env.NODE_ENV === 'production',
      DEBUG: false,
    }),
  ],
}

ProvidePlugin

自动加载模块,当代码中出现特定变量时,无需 import 即可使用(如 $ 对应 'jquery'),简化全局依赖。

js
const webpack = require('webpack')

module.exports = {
  // ... existing code ...
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      'window.jQuery': 'jquery',
      _: 'lodash',
      React: 'react',
    }),
  ],
}

分析与其他插件

BundleAnalyzerPlugin

可视化打包结果,生成交互式树状图,帮助分析各模块体积与依赖,定位体积热点。(需安装 webpack-bundle-analyzer)。

js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
  // ... existing code ...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server', // 'server'|'static'|'json'|'disabled'
      analyzerHost: '127.0.0.1',
      analyzerPort: 8888,
      reportFilename: 'report.html',
      defaultSizes: 'parsed', // 'stat'|'parsed'|'gzip'
      openAnalyzer: true,
      generateStatsFile: false,
      statsFilename: 'stats.json',
    }),
  ],
}

ForkTsCheckerWebpackPlugin

在 TypeScript 项目中异步执行类型检查与 ESLint 校验,避免编译阻塞,加快开发模式下的构建速度。

js
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      typescript: {
        configFile: './tsconfig.json',
        diagnosticOptions: {
          semantic: true,
          syntactic: true,
        },
        mode: 'write-references', // 'write-references'|'readonly'
      },
      eslint: {
        files: './src/**/*.{ts,tsx,js,jsx}',
        options: {
          fix: true,
        },
      },
      issue: {
        include: [{ file: '../**/src/**/*.{ts,tsx}' }],
        exclude: [{ file: '**/*.spec.ts' }],
      },
    }),
  ],
}

EslintWebpackPlugin

在构建中自动执行 ESLint 检查,可修复部分问题并在命令行输出错误/警告,保持代码质量。

js
const ESLintPlugin = require('eslint-webpack-plugin')

module.exports = {
  // ... existing code ...
  plugins: [
    new ESLintPlugin({
      context: './src',
      extensions: ['js', 'jsx', 'ts', 'tsx'],
      exclude: ['node_modules', 'dist'],
      fix: true, // 自动修复
      emitWarning: true, // 将警告作为警告而不是错误输出
      failOnError: process.env.NODE_ENV === 'production', // 生产环境下出错时停止构建
      quiet: false, // 不隐藏警告
    }),
  ],
}

文件指纹

文件指纹(File Fingerprint)是指在打包后的文件名中添加一串 hash 值,用于标识文件内容的变化。这对于浏览器缓存优化非常重要,因为当文件内容发生变化时,文件名也会改变,从而强制浏览器重新加载新文件。

文件指纹的类型

  1. Hash:整个项目的 hash 值,项目文件有变化时,所有文件的 hash 值都会改变
  2. Chunkhash:根据不同的入口文件(Entry)进行依赖文件解析,构建对应的 chunk,生成对应的 hash 值
  3. Contenthash:根据文件内容生成 hash 值,文件内容不变,hash 值不变

文件指纹的配置

javascript
// webpack.config.js
module.exports = {
  output: {
    filename: '[name].[chunkhash:8].js', // JS 文件指纹
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css', // CSS 文件指纹
    }),
    new CleanWebpackPlugin(),
  ],
}

图片文件指纹

javascript
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[hash:8].[ext]', // 图片文件指纹
            },
          },
        ],
      },
    ],
  },
}

最佳实践

  1. JS 文件使用 chunkhash

    • 原因:JS 文件通常由 webpack 打包生成,使用 chunkhash 可以确保只有相关文件变化时才会改变 hash 值
  2. CSS 文件使用 contenthash

    • 原因:CSS 文件内容变化时才会改变 hash 值,避免不必要的缓存失效
  3. 图片等静态资源使用 hash

    • 原因:图片等静态资源通常不会频繁变化,使用 hash 可以确保内容变化时更新缓存

实际应用示例

javascript
// webpack.config.js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: {
    app: './src/index.js',
    vendor: './src/vendor.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[chunkhash:8].js',
    chunkFilename: '[name].[chunkhash:8].js',
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[hash:8].[ext]',
              outputPath: 'images/',
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash:8].css',
      chunkFilename: '[id].[contenthash:8].css',
    }),
  ],
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          filename: '[name].[chunkhash:8].js',
        },
      },
    },
  },
}

注意事项

  1. 确保在生产环境中使用文件指纹
  2. 合理设置 hash 长度,通常 8 位就足够了
  3. 注意文件指纹对开发环境的影响,建议在开发环境禁用文件指纹
  4. 使用 clean-webpack-plugin 清理旧的构建文件
  5. 合理配置 splitChunks 优化代码分割

开发环境配置

javascript
// webpack.dev.js
module.exports = {
  mode: 'development',
  output: {
    filename: '[name].js', // 开发环境不使用文件指纹
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css', // 开发环境不使用文件指纹
    }),
  ],
}

通过合理使用文件指纹,可以:

  1. 优化浏览器缓存策略
  2. 提高页面加载速度
  3. 确保用户始终使用最新的文件版本
  4. 减少不必要的网络请求