Plugin 详解

Plugin 是 Webpack 的扩展机制,用于执行更广泛的任务。本章详细介绍 Plugin 的工作原理、常用 Plugin 的使用方法,以及如何自定义 Plugin。


📋 学习目标

  • ✅ 理解 Plugin 的工作原理
  • ✅ 掌握常用 Plugin 的配置
  • ✅ 理解 Plugin 的生命周期钩子
  • ✅ 学会自定义 Plugin
  • ✅ 掌握 Plugin 的最佳实践

Plugin 工作原理

什么是 Plugin

Plugin 是一个类,通过 apply 方法注册到 Webpack 的生命周期钩子中。

class MyPlugin {
  apply(compiler) {
    // 注册到 Webpack 生命周期钩子
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('构建完成!')
    })
  }
}

Plugin 的基本结构

class MyPlugin {
  constructor(options) {
    this.options = options || {}
  }
  
  apply(compiler) {
    // 在这里注册钩子
  }
}
 
module.exports = MyPlugin

常用 Plugin

1. HtmlWebpackPlugin(生成 HTML)

安装

npm install --save-dev 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/index.html',
  filename: 'index.html',
  inject: true,  // 自动注入 script 和 link 标签
  minify: {
    removeComments: true,
    collapseWhitespace: true,
    removeRedundantAttributes: true,
    useShortDoctype: true,
    removeEmptyAttributes: true,
    removeStyleLinkTypeAttributes: true,
    minifyJS: true,
    minifyCSS: true
  },
  chunks: ['main'],  // 指定注入的 chunk
  hash: true  // 添加 hash
})

2. CleanWebpackPlugin(清理输出目录)

安装

npm install --save-dev clean-webpack-plugin

配置

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
 
module.exports = {
  plugins: [
    new CleanWebpackPlugin({
      cleanOnceBeforeBuildPatterns: ['**/*', '!important-file.js']
    })
  ]
}

3. MiniCssExtractPlugin(提取 CSS)

安装

npm install --save-dev mini-css-extract-plugin

配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
 
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash].css',
      chunkFilename: 'css/[name].[contenthash].chunk.css'
    })
  ]
}

4. DefinePlugin(定义环境变量)

内置 Plugin,无需安装

const webpack = require('webpack')
 
module.exports = {
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production'),
      'process.env.API_URL': JSON.stringify('https://api.example.com'),
      '__VERSION__': JSON.stringify('1.0.0')
    })
  ]
}

5. ProvidePlugin(自动引入模块)

内置 Plugin

const webpack = require('webpack')
 
module.exports = {
  plugins: [
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery',
      _: 'lodash'
    })
  ]
}

使用

// 无需 import,直接使用
$('#app').html('Hello')

6. BundleAnalyzerPlugin(分析打包结果)

安装

npm install --save-dev webpack-bundle-analyzer

配置

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'static',  // 'server' | 'static' | 'disabled'
      openAnalyzer: false,
      reportFilename: 'bundle-report.html'
    })
  ]
}

7. CopyWebpackPlugin(复制文件)

安装

npm install --save-dev copy-webpack-plugin

配置

const CopyWebpackPlugin = require('copy-webpack-plugin')
 
module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        { from: 'public', to: 'public' },
        { from: 'favicon.ico', to: 'favicon.ico' }
      ]
    })
  ]
}

8. CompressionPlugin(Gzip 压缩)

安装

npm install --save-dev compression-webpack-plugin

配置

const CompressionPlugin = require('compression-webpack-plugin')
 
module.exports = {
  plugins: [
    new CompressionPlugin({
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 8192,
      minRatio: 0.8
    })
  ]
}

Plugin 生命周期钩子

常用钩子

class MyPlugin {
  apply(compiler) {
    // 编译开始
    compiler.hooks.compile.tap('MyPlugin', () => {
      console.log('编译开始')
    })
    
    // 编译完成
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      console.log('编译中')
    })
    
    // 生成资源之前
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      console.log('生成资源')
      callback()
    })
    
    // 构建完成
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('构建完成')
    })
  }
}

钩子类型

  • SyncHook:同步钩子,使用 tap
  • AsyncSeriesHook:异步串行钩子,使用 tapAsynctapPromise
  • AsyncParallelHook:异步并行钩子

自定义 Plugin

基础 Plugin

class MyPlugin {
  constructor(options) {
    this.options = options || {}
  }
  
  apply(compiler) {
    compiler.hooks.done.tap('MyPlugin', (stats) => {
      console.log('构建完成!')
    })
  }
}
 
module.exports = MyPlugin

使用

const MyPlugin = require('./plugins/my-plugin')
 
module.exports = {
  plugins: [
    new MyPlugin({ option: 'value' })
  ]
}

修改资源的 Plugin

class ModifyAssetsPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('ModifyAssetsPlugin', (compilation, callback) => {
      // 修改所有 JS 文件
      Object.keys(compilation.assets).forEach(filename => {
        if (filename.endsWith('.js')) {
          const asset = compilation.assets[filename]
          const source = asset.source()
          const modified = source.replace(/console\.log\(/g, '// console.log(')
          
          compilation.assets[filename] = {
            source: () => modified,
            size: () => modified.length
          }
        }
      })
      
      callback()
    })
  }
}
 
module.exports = ModifyAssetsPlugin

生成文件的 Plugin

class GenerateFilePlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('GenerateFilePlugin', (compilation, callback) => {
      const manifest = JSON.stringify({
        version: '1.0.0',
        buildTime: new Date().toISOString()
      })
      
      compilation.assets['manifest.json'] = {
        source: () => manifest,
        size: () => manifest.length
      }
      
      callback()
    })
  }
}
 
module.exports = GenerateFilePlugin

Plugin 最佳实践

1. 条件使用 Plugin

const isProduction = process.env.NODE_ENV === 'production'
 
module.exports = {
  plugins: [
    ...(isProduction ? [
      new MiniCssExtractPlugin(),
      new CompressionPlugin()
    ] : [])
  ]
}

2. 使用数组配置多个实例

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      chunks: ['main']
    }),
    new HtmlWebpackPlugin({
      template: './src/admin.html',
      filename: 'admin.html',
      chunks: ['admin']
    })
  ]
}

3. 错误处理

class MyPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
      try {
        // 处理逻辑
        callback()
      } catch (error) {
        compilation.errors.push(error)
        callback(error)
      }
    })
  }
}

完整配置示例

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpack = require('webpack')
 
const isProduction = process.env.NODE_ENV === 'production'
 
module.exports = {
  plugins: [
    new CleanWebpackPlugin(),
    
    new HtmlWebpackPlugin({
      template: './src/index.html',
      filename: 'index.html',
      minify: isProduction
    }),
    
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }),
    
    ...(isProduction ? [
      new MiniCssExtractPlugin({
        filename: 'css/[name].[contenthash].css'
      })
    ] : [])
  ]
}

总结

  • Plugin 作用:执行更广泛的任务,如资源管理、优化等
  • 工作原理:通过 apply 方法注册到生命周期钩子
  • 常用 Plugin:HtmlWebpackPlugin、CleanWebpackPlugin、MiniCssExtractPlugin 等
  • 自定义 Plugin:可以创建自己的 Plugin 处理特殊需求
  • 最佳实践:条件使用、错误处理、合理组织

下一步


Webpack Plugin 插件 自定义Plugin