一. webpack打包流程
开发 Webpack 插件的第一步,就是明确:我的插件要接入 Webpack 构建流程的哪个阶段,解决什么问题。
- 了解流程之前首先要了解插件的两个核心概念:compiler,compilation
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.beforeRun 、run 、compile |
启动构建,调用编译 |
3️⃣ 创建 Compilation | new Compilation() | thisCompilation 、compilation |
代表一次构建,管理模块、资源 |
4️⃣ 构建模块 | buildModule/handleModuleCreation | compilation.hooks.buildModule |
构建入口与递归依赖 |
5️⃣ 完成模块 | seal() | seal 、optimizeModules |
完成依赖分析与模块关系确定 |
6️⃣ 生成代码块 | createChunkGraph() | optimizeChunks |
把模块打成 chunk |
7️⃣ 生成资源 | emitAssets() | processAssets |
将 chunk 渲染为文件(此时文件还在内存中) |
8️⃣ 输出结果 | outputFileSystem.writeFile | afterEmit 、done |
写入磁盘,构建完成 |
二. 插件的开发
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官方文档