深入Webpack Plugin开发与常用Plugin推荐

9/13/2023 Webpack

在前一篇文章中,我们已经介绍了什么是 Webpack 插件以及与 Loader 的区别。本文将深入探讨 Webpack 插件,介绍 Webpack 的生命周期,如何自定义插件,并最后推荐一些常用的 Webpack 插件。

注意: 本文基于 Webpack 5,因为 Webpack 5 在发布一段时间后已经变得相对稳定。当然,现在最流行的构建工具可能是 Vite,其开发和编译速度比 Webpack 快数倍,但 Webpack 插件生态相对丰富。

# Webpack 的生命周期

因为插件是 Webpack 生态系统的关键组成部分,它们伴随 Webpack 的整个生命周期运行。Webpack 在达到特定阶段时提供了钩子(hook),用于触发插件的执行。因此,了解 Webpack 的生命周期对于编写插件至关重要。

Webpack 中最主要的两个对象,Compiler 和 Compilation。

Compiler 管理 Webpack 的整个生命周期,代表完整的 Webpack 的环境配置,具体可以点击此处前往 (opens new window)

Compilation 代表了一次资源版本构建,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。

这里列举一些非常常用的 hooks:

  1. entryOption,当入口文件 entry 被处理之后调用

  2. beforeRun,当 Webpack 读取完配置后,准备开始编译前调用

  3. run,开始编译之后,读取 records 之前

  4. watchRun, 只有在监听模式下,才会触发,例如修改了一些 js 文件,触发重新编译的时候,webpack-dev-server就会调用。

  5. beforeCompilecompilation参数创建之后执行

  6. compile,在一个新的 compilation 创建之前执行

  7. thisCompilation,初始化 compilation 时调用,这时候回调函数中就能访问到compilation对象

    compilation也自己的生命周期,可以点击此处前往 (opens new window),列举一些常用的 hooks

    • buildModule,模块构建开始之前触发,可以用来修改模块
    • seal,编译(compilation)停止接收新模块时触发
    • optimize,优化阶段开始时触发
    • optimizeModules,模块的优化
    • optimizeChunks,优化 chunks
    • additionalAssets,为编译(compilation)创建附加资源(asset)
    • optimizeChunkAssets,优化所有 chunk 资源(asset)
    • optimizeAssets,优化存储在 compilation.assets 中的所有资源 (asset)
  8. compilation,创建了 compilation 之后执行。

  9. emit,输出 asset 到 output 目录之前执行

  10. assetEmitted,在 asset 被输出时执行。此钩子可以访问被输出的 asset 的相关信息

compiler.hooks.assetEmitted.tap(
  'MyPlugin',
  (file, { content, source, outputPath, compilation, targetPath }) => {
    console.log(content); // <Buffer 66 6f 6f 62 61 72>
  }
);
  1. done, 在 compilation 完成时执行。

通过这段代码就可以看到 hooks 的执行顺序

const webpack = require('webpack');
const webpackConfig = require('./config/webpack.common.js');

const compiler = webpack(webpackConfig());
Object.keys(compiler.hooks).forEach((hookName) => {
  if (compiler.hooks[hookName].tap) {
    compiler.hooks[hookName].tap('anyString', () => {
      console.log(`run -> ${hookName}`);
    });
  }
});
compiler.run();

以上就是 Webpack 的生命周期,其实整个 Webpack 的生命周期可以分为三个阶段。

  1. 准备阶段,这个阶段主要准备环境和配置参数来创建CompilerCompilation对象,到了thisCompilation创建完对象之后就进入第二阶段
  2. 编译阶段,这个阶段主要做 modules 的解析,生成chunks
  3. 产出阶段,这个阶段就是根据chunks去输出到文件目录中,模板 Hash 更新,模板渲染 chunk,生成文件。

# 自定义 Plugin

如果想要深入前端技术,开发前端基建提高工作效率是必不可少的事情。

对于如何编写 Plugin,官方也提供了文档参考 (opens new window)

主要是实现两个方法,constructorapply

constructor 是让传入参数,初始化 Plugin。

apply 就是真正实现 Plugin 的作用,它提供一个compiler对象,这个对象通过 hooks 来监听 Webpack 的各个阶段。最常用的就是监听thisCompilation这个时候参数就会多一个compilation对象,这时候就能监听compilation的生命周期。

一般参照官方的插件去实现自己的需求。

一些自定义插件的写法,下面的插件主要是拉取 types 的内容,解压包并放到 node_modules 里面。

const fs = require('fs');
const path = require('path');
const http = require('http');
const compressing = require('compressing');

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

module.exports = class MFTypesInjectPlugin {
  constructor(props) {
    const {
      name,
      typesZipUrl,
    } = props;

    this.name = name;
    this.typesZipUrl = typesZipUrl;
  }

  apply(compiler) {
    compiler.hooks.watchRun.tapAsync(
      'MFTypesInjectPlugin',
      (compiler, callback) => {
        if (process.env.NODE_ENV === 'development') {
          const zipFile = resolveApp(path.resolve('node_modules', `${this.name}_types.zip`));
          const unzipPath = resolveApp(path.resolve('node_modules', '@types', `${this.name}_temp`));
          const target = resolveApp(path.resolve('node_modules', '@types', this.name));

          deleteFolder(unzipPath);
          deleteFolder(target);

          fs.mkdirSync(unzipPath, { recursive: true });

          downloadFile(this.typesZipUrl, zipFile, () => {
            compressing.zip.uncompress(zipFile, unzipPath).then(() => {
              fs.renameSync(path.resolve(unzipPath, 'types'), target);
            }).finally(() => {
              callback();
            });
          });
        } else {
          callback();
        }
      }
    );
  }
}
... // 剩下的代码就不展示了,不然篇幅太长

# 常用的 Plugin 推荐

插件名 官方文档 作用
HtmlWebpackPlugin https://webpack.js.org/plugins/html-webpack-plugin 自动生成 HTML 文件,引入打包后的资源,简化配置和构建过程。
MiniCssExtractPlugin https://webpack.js.org/plugins/mini-css-extract-plugin/ 将 CSS 从 JS 中提取为单独的文件,实现 CSS 代码的分离与优化。一般打正式包的时候才会使用,开发环境使用 style-loader
BundleAnalyzerPlugin https://github.com/webpack-contrib/webpack-bundle-analyzer 文件分析插件,可以用于打包后资源的依赖及大小分析
CompressionWebpackPlugin https://webpack.js.org/plugins/compression-webpack-plugin/ 用于在构建过程中压缩和优化生成的 JavaScript 和 CSS 文件,减小文件大小,提高加载速度。一般用于 GZIP 之类的,但现在云厂商会自动开启
TerserWebpackPlugin https://webpack.js.org/plugins/terser-webpack-plugin/ 用于压缩和混淆 JavaScript 代码,减小文件体积,提高网页加载速度。Webpack5 默认开启,只要配置 minimize:true。如果要配置的话,还是要安装
CssMinimizerWebpackPlugin https://webpack.js.org/plugins/css-minimizer-webpack-plugin/ 用于压缩和优化 CSS 代码,减小样式文件的大小,提高页面加载速度。
DefinePlugin https://webpack.js.org/plugins/define-plugin/ 用于在编译过程中定义全局常量,可以在代码中使用这些常量,用于配置环境变量或进行条件编译。