Skip to content
On this page

Webpack 插件(Plugins)

插件是 Webpack 生态系统的核心,它们提供了一种强大而灵活的方式来定制 Webpack 构建过程的几乎每个方面。插件可以执行各种任务,如打包优化、资源管理和注入环境变量。

插件基础概念

插件通过 Webpack 的生命周期钩子(hooks)系统来影响构建过程。它们通常是一个带有 apply 方法的对象,该方法在 Webpack 编译器实例被激活时被调用。

基础插件示例

javascript
// 自定义插件示例
class MyExampleWebpackPlugin {
  apply(compiler) {
    // Webpack 模块编译完成后执行
    compiler.hooks.done.tap('MyExampleWebpackPlugin', (stats) => {
      console.log('编译完成!');
    });
    
    // Webpack 编译开始前执行
    compiler.hooks.compile.tap('MyExampleWebpackPlugin', (params) => {
      console.log('开始编译...');
    });
  }
}

// 在配置中使用
module.exports = {
  plugins: [
    new MyExampleWebpackPlugin()
  ]
};

常用插件详解

1. HtmlWebpackPlugin

HtmlWebpackPlugin 用于生成 HTML 文件,并自动引入 Webpack 打包后的资源。

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My App',                             // 页面标题
      filename: 'index.html',                      // 输出文件名
      template: './src/index.html',                // 模板文件
      inject: true,                               // 自动注入资源
      minify: {                                  // 压缩配置
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      },
      chunksSortMode: 'auto',                     // 资源排序方式
      cache: true,                               // 启用缓存
      showErrors: true                           // 显示错误
    })
  ]
};

2. CleanWebpackPlugin

CleanWebpackPlugin 用于在每次构建前清理输出目录。

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

module.exports = {
  plugins: [
    new CleanWebpackPlugin({
      dry: false,                                 // 实际删除文件
      verbose: true,                             // 控制台显示删除信息
      cleanStaleWebpackAssets: true,             // 清理过时的资产
      protectWebpackAssets: true,                // 保护 webpack 资产
      cleanOnceBeforeBuildPatterns: ['**/*'],    // 清理模式
      cleanAfterEveryBuildPatterns: []           // 构建后清理模式
    })
  ]
};

3. MiniCssExtractPlugin

MiniCssExtractPlugin 用于将 CSS 提取到单独的文件中。

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

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'styles/[name].[contenthash].css',    // 输出文件名
      chunkFilename: '[id].[contenthash].css',        // 非入口 chunk 文件名
      ignoreOrder: false                             // 启用 CSS 顺序警告
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,              // 替代 style-loader
          'css-loader'
        ]
      }
    ]
  }
};

4. DefinePlugin

DefinePlugin 用于定义全局常量。

javascript
const webpack = require('webpack');

module.exports = {
  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(require('./package.json').version),
      '__DEBUG__': JSON.stringify(process.env.NODE_ENV === 'development')
    })
  ]
};

5. CopyWebpackPlugin

CopyWebpackPlugin 用于复制文件和目录。

javascript
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'public/',                        // 源目录
          to: '.',                               // 目标目录
          globOptions: {
            ignore: ['**/index.html']            // 忽略的文件
          }
        },
        {
          from: 'src/assets/',
          to: 'assets/',
          // 只在生产环境复制
          noErrorOnMissing: true
        }
      ]
    })
  ]
};

高级插件配置

1. 环境特定插件

javascript
const isProduction = process.env.NODE_ENV === 'production';

const plugins = [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  })
];

if (isProduction) {
  plugins.push(
    new MiniCssExtractPlugin({
      filename: 'styles/[name].[contenthash].css'
    }),
    new webpack.optimize.MinChunkSizePlugin({
      minChunkSize: 10000
    })
  );
} else {
  plugins.push(
    new webpack.HotModuleReplacementPlugin()
  );
}

module.exports = {
  plugins
};

2. 多页面应用插件配置

javascript
const HtmlWebpackPlugin = require('html-webpack-plugin');

const htmlPages = ['home', 'about', 'contact'].map(page => {
  return new HtmlWebpackPlugin({
    template: `src/pages/${page}/index.html`,
    filename: `${page}.html`,
    chunks: [page],                              // 只包含特定 chunk
    inject: true
  });
});

module.exports = {
  entry: {
    home: './src/pages/home/index.js',
    about: './src/pages/about/index.js',
    contact: './src/pages/contact/index.js'
  },
  plugins: [
    ...htmlPages
  ]
};

3. 自定义插件开发

javascript
// 资源统计插件
class AssetStatsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('AssetStatsPlugin', (compilation, callback) => {
      const assets = Object.keys(compilation.assets);
      const stats = {};
      
      assets.forEach(asset => {
        const size = compilation.assets[asset].size();
        stats[asset] = {
          size: size,
          sizeKB: (size / 1024).toFixed(2) + ' KB'
        };
      });
      
      // 将统计信息写入文件
      const statsJson = JSON.stringify(stats, null, 2);
      compilation.assets['stats.json'] = {
        source: () => statsJson,
        size: () => statsJson.length
      };
      
      callback();
    });
  }
}

// 文件指纹插件
class FileHashPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('FileHashPlugin', (compilation) => {
      compilation.hooks.processAssets.tap(
        {
          name: 'FileHashPlugin',
          stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ANALYSE
        },
        (assets) => {
          Object.keys(assets).forEach(filename => {
            if (filename.endsWith('.js')) {
              const asset = assets[filename];
              const source = asset.source();
              const hash = this.generateHash(source);
              
              // 重命名文件以包含哈希
              delete assets[filename];
              assets[filename.replace(/(\.\w+)$/, `.${hash}$1`)] = asset;
            }
          });
        }
      );
    });
  }
  
  generateHash(content) {
    const crypto = require('crypto');
    return crypto.createHash('md5').update(content).digest('hex').substring(0, 8);
  }
}

优化相关插件

1. 代码分割插件

javascript
const webpack = require('webpack');

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10,
          chunks: 'all'
        },
        common: {
          name: 'common',
          minChunks: 2,
          priority: 5,
          chunks: 'all',
          enforce: true
        }
      }
    },
    runtimeChunk: {
      name: 'runtime'
    }
  },
  plugins: [
    // 模块联邦插件 (Webpack 5)
    new webpack.container.ModuleFederationPlugin({
      name: 'host_app',
      remotes: {
        remote_app: 'remote_app@http://localhost:3001/remoteEntry.js'
      },
      shared: ['react', 'react-dom']
    })
  ]
};

2. 压缩优化插件

javascript
const TerserPlugin = require('terser-webpack-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,                    // 删除 console
            drop_debugger: true,                   // 删除 debugger
            pure_funcs: ['console.log']           // 删除特定函数调用
          },
          mangle: true,
          format: {
            comments: false                       // 删除注释
          }
        },
        extractComments: false
      }),
      new CssMinimizerPlugin({
        test: /\.css$/g,
        parallel: true,                          // 并行处理
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true }
            }
          ]
        }
      })
    ]
  }
};

3. 分析插件

javascript
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',                    // 静态 HTML 报告
      openAnalyzer: false,                       // 不自动打开浏览器
      reportFilename: 'bundle-report.html',      // 报告文件名
      defaultSizes: 'parsed'                     // 显示解析后的大小
    })
  ]
};

性能优化插件

1. 缓存插件

javascript
module.exports = {
  cache: {
    type: 'filesystem',                         // 文件系统缓存
    buildDependencies: {
      config: [__filename]                      // 配置文件变更时清除缓存
    },
    cacheDirectory: path.resolve(__dirname, '.webpack-cache')
  },
  plugins: [
    // HardSourceWebpackPlugin (适用于 Webpack 4)
    // new HardSourceWebpackPlugin()
  ]
};

2. 进度条插件

javascript
const ProgressBarPlugin = require('progress-bar-webpack-plugin');

module.exports = {
  plugins: [
    new ProgressBarPlugin({
      format: '  build [:bar] ' +
        ':percent ' +
        '(:elapsed seconds)',
      clear: false
    })
  ]
};

小结

Webpack 插件系统提供了强大的功能扩展机制,通过插件我们可以定制构建过程的几乎所有方面。常用的插件包括 HTML 生成、资源清理、CSS 提取、环境变量定义等。理解插件的工作原理和配置方法,可以帮助我们构建更高效的开发环境和生产构建流程。