Webpack 自定义插件开发指南:构建流程详解与实战开发全攻略

发布于:2025-06-28 ⋅ 阅读:(18) ⋅ 点赞:(0)

一. webpack打包流程

开发 Webpack 插件的第一步,就是明确:我的插件要接入 Webpack 构建流程的哪个阶段,解决什么问题。

  • 了解流程之前首先要了解插件的两个核心概念:compilercompilation
1. compiler:全局构建控制器
你可以这样理解它:
  • 是 Webpack 的“大脑”,负责整个构建流程
  • 只会在整个 Webpack 启动时创建一次
  • 提供入口:如 compiler.hooks.compile、compiler.hooks.run 等

注册插件

class MyPlugin {
  apply(compiler) {
    // 插件注册时调用一次
  }
}

使用钩子

compiler.hooks.beforeRun.tap('MyPlugin', () => { ... })
2. compilation:本次构建上下文
你可以这样理解它:
  • 是每次构建过程中的“局部快照”
  • 管理模块 (module)、代码块 (chunk)、资源 (asset)等
  • 在监听模式或热更新时,每次都会重新生成一个 compilation 实例

你要修改文件,生成资源,访问chunk的时候,一定是在 compilation 阶段完成的

compiler.hooks.thisCompilation.tap('MyPlugin', (compilation) => {
  // compilation 是这一次构建的上下文
  compilation.hooks.buildModule.tap(...)
  compilation.hooks.processAssets.tap(...)
});
举个例子
class FileListPlugin {
  apply(compiler) {
    // 第一次执行,仅执行一次,注册插件钩子
    compiler.hooks.thisCompilation.tap('FileListPlugin', (compilation) => {

      // 每次构建及热更新都会触发
      compilation.hooks.processAssets.tap(
        {
          name: 'FileListPlugin',
          stage: compilation.constructor.PROCESS_ASSETS_STAGE_SUMMARIZE
        },
        () => {
          const files = Object.keys(compilation.assets).join('\n');
          compilation.emitAsset('filelist.md', new compiler.webpack.sources.RawSource(files));
        }
      );
    });
  }
}
webpack打包流程
阶段 对应方法 插件钩子 说明
1️⃣ 初始化 webpack() 创建 compiler compiler.hooks.environment 读取配置、初始化插件
2️⃣ 编译开始 compiler.run() compiler.hooks.beforeRunruncompile 启动构建,调用编译
3️⃣ 创建 Compilation new Compilation() thisCompilationcompilation 代表一次构建,管理模块、资源
4️⃣ 构建模块 buildModule/handleModuleCreation compilation.hooks.buildModule 构建入口与递归依赖
5️⃣ 完成模块 seal() sealoptimizeModules 完成依赖分析与模块关系确定
6️⃣ 生成代码块 createChunkGraph() optimizeChunks 把模块打成 chunk
7️⃣ 生成资源 emitAssets() processAssets 将 chunk 渲染为文件(此时文件还在内存中)
8️⃣ 输出结果 outputFileSystem.writeFile afterEmitdone 写入磁盘,构建完成

二. 插件的开发

1. 插件的基本结构
class MyPlugin {
  constructor(options) {
    this.options = options;
  }
  // 创建插件类并实现 apply(compiler) 方法
  apply(compiler) {
    // 注册 compiler 生命周期钩子
    compiler.hooks.thisCompilation.tap('MyPlugin', (compilation) => {
      // 注册 compilation 生命周期钩子
      compilation.hooks.processAssets.tap(
        {
          name: 'MyPlugin',
          stage: compilation.constructor.PROCESS_ASSETS_STAGE_SUMMARIZE
        },
        (assets) => {
          const { RawSource } = compiler.webpack.sources;
	      // 修改、添加资源
	      const content = 'hello plugin';
	      compilation.emitAsset('my-output.txt', new RawSource(content));
        }
      );
    });
  }
}

module.exports = MyPlugin;

  • stage 是一个数字,标识你要插入的逻辑在整个资源处理生命周期中的阶段优先级
  • PROCESS_ASSETS_STAGE_SUMMARIZE的stage为7000,代表总结阶段,可以做输出文件列表、日志、清单等操作
  • 其他更多的stage可以翻阅webpack官网查看
2. 注册钩子的方法
tap: 同步钩子
compiler.hooks.beforeRun.tap('MyPlugin', (compiler) => {
  console.log('Before run');
});
tapAsync:异步钩子(使用回调)
compiler.hooks.beforeRun.tapAsync('MyPlugin', (compiler, callback) => {
  setTimeout(() => {
    console.log('Before run (async)');
    callback();
  }, 1000);
});
  • 用于需要手动处理异步任务的情况,必须调用 callback()
tapPromise:异步钩子(使用 Promise)
compiler.hooks.beforeRun.tapPromise('MyPlugin', (compiler) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Before run (promise)');
      resolve();
    }, 1000);
  });
});
  • 推荐用于现代异步开发场景

三. 利用webpack插件实现上线后热更新的功能

1. 构建完成后自动生成一个带 hash 的版本号文件
class VersionFilePlugin {
  constructor(options = {}) {
    this.filename = options.filename || 'version.txt';
  }

  apply(compiler) {
    compiler.hooks.thisCompilation.tap('VersionFilePlugin', (compilation) => {
      const { RawSource } = compiler.webpack.sources;

      compilation.hooks.processAssets.tap(
        {
          name: 'VersionFilePlugin',
          stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
        },
        () => {
          // 获取当前构建 hash
          const hash = compilation.fullHash;
          const content = `version: ${hash}`;

          // 添加 version.txt 文件
          compilation.emitAsset(this.filename, new RawSource(content));
        }
      );
    });
  }
}

module.exports = VersionFilePlugin;
注册插件
const VersionFilePlugin = require('./plugins/VersionFilePlugin');

module.exports = {
  // ...
  plugins: [
    new VersionFilePlugin({
      filename: 'version.txt'
    })
  ]
};
2. 定时轮询 version.txt,检测 hash 是否变化
<script>
(function () {
  const VERSION_CHECK_INTERVAL = 1000 * 60 * 5; // 每 5 分钟检查一次
  const VERSION_URL = '/version.txt';

  let currentVersion = null;

  async function checkVersion() {
    try {
      const res = await fetch(VERSION_URL, { cache: 'no-store' });
      const text = await res.text();
      const latestVersion = text.trim().split('version: ')[1];

      if (currentVersion && latestVersion !== currentVersion) {
        console.warn('[version check] 新版本已发布,自动刷新页面');
        location.reload(true); // 强制刷新页面
      }

      currentVersion = latestVersion;
    } catch (err) {
      console.error('[version check] 检查失败:', err);
    }
  }

  // 初始加载
  checkVersion();

  // 定时检查
  setInterval(checkVersion, VERSION_CHECK_INTERVAL);
})();
</script>

⚠️ 需要将该文件一并部署到 Web 服务器根目录

有好多全局构建的生命周期钩子每次构建生成的模块、资源、chunk 等上下文生命周期钩子本文没有展示,想了解更多的可以去看下webpack官方文档


网站公告

今日签到

点亮在社区的每一天
去签到