1. Webpack 主要生命周期钩子流程
class LifecyclePlugin {
apply(compiler) {
// 1. 初始化阶段
compiler.hooks.initialize.tap('LifecyclePlugin', () => {
console.log('1. 初始化 Webpack');
});
// 2. 开始编译
compiler.hooks.beforeRun.tap('LifecyclePlugin', () => {
console.log('2. 编译开始前');
});
// 3. compilation 对象创建
compiler.hooks.compilation.tap('LifecyclePlugin', (compilation) => {
console.log('3. compilation 对象创建完成');
// compilation 的子生命周期
compilation.hooks.buildModule.tap('LifecyclePlugin', () => {
console.log('3.1 模块构建开始');
});
compilation.hooks.finishModules.tap('LifecyclePlugin', () => {
console.log('3.2 所有模块构建完成');
});
});
// 4. 生成资源
compiler.hooks.emit.tap('LifecyclePlugin', () => {
console.log('4. 生成资源到 output 目录之前');
});
// 5. 完成编译
compiler.hooks.done.tap('LifecyclePlugin', () => {
console.log('5. 编译完成');
});
}
}
2. 主要生命周期阶段及其作用
class ExamplePlugin {
apply(compiler) {
// 1. 配置阶段
compiler.hooks.entryOption.tap('ExamplePlugin', (context, entry) => {
// 入口文件配置完成后触发
// 适合做入口文件的检查或修改
});
// 2. 编译阶段
compiler.hooks.compilation.tap('ExamplePlugin', (compilation) => {
// 一次新的编译创建完成后触发
// 适合处理模块相关的事务
compilation.hooks.optimizeModules.tap('ExamplePlugin', (modules) => {
// 模块优化阶段
// 可以对模块进行优化处理
});
});
// 3. 输出阶段
compiler.hooks.emit.tapAsync('ExamplePlugin', (compilation, callback) => {
// 资源输出到目录之前触发
// 适合修改最终的资源内容
const manifest = {};
for (const name of Object.keys(compilation.assets)) {
manifest[name] = compilation.assets[name].size();
}
callback();
});
}
}
3. 实际应用示例
class FileListPlugin {
apply(compiler) {
// compilation 阶段:收集模块信息
compiler.hooks.compilation.tap('FileListPlugin', (compilation) => {
const fileList = [];
// 监听模块构建
compilation.hooks.buildModule.tap('FileListPlugin', (module) => {
if (module.resource) {
fileList.push(module.resource);
}
});
// emit 阶段:生成文件清单
compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, callback) => {
// 创建文件清单
const content = fileList.join('\n');
compilation.assets['filelist.txt'] = {
source: () => content,
size: () => content.length
};
callback();
});
});
}
}
4. 常见生命周期钩子的使用场景
class CommonUsagePlugin {
apply(compiler) {
// 1. 用于环境检查
compiler.hooks.initialize.tap('CommonUsagePlugin', () => {
if (process.env.NODE_ENV !== 'production') {
console.warn('当前为开发环境');
}
});
// 2. 用于资源统计
compiler.hooks.compilation.tap('CommonUsagePlugin', (compilation) => {
let totalSize = 0;
compilation.hooks.chunkAsset.tap('CommonUsagePlugin', (chunk, filename) => {
const asset = compilation.assets[filename];
totalSize += asset.size();
});
});
// 3. 用于性能分析
let startTime;
compiler.hooks.run.tap('CommonUsagePlugin', () => {
startTime = Date.now();
});
compiler.hooks.done.tap('CommonUsagePlugin', () => {
const endTime = Date.now();
console.log(`编译耗时: ${endTime - startTime}ms`);
});
}
}
5. 异步生命周期的处理
class AsyncLifecyclePlugin {
apply(compiler) {
// 异步处理方式一:callback
compiler.hooks.emit.tapAsync('AsyncLifecyclePlugin', (compilation, callback) => {
setTimeout(() => {
console.log('异步处理完成');
callback();
}, 1000);
});
// 异步处理方式二:Promise
compiler.hooks.emit.tapPromise('AsyncLifecyclePlugin', (compilation) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Promise异步处理完成');
resolve();
}, 1000);
});
});
}
}
6. 生命周期钩子的执行顺序
// Webpack 编译流程的主要步骤:
1. entry-option -> 初始化选项
2. run -> 开始编译
3. make -> 从入口开始递归分析依赖,对每个依赖模块进行构建
4. build-module -> 构建模块
5. normal-module -> 普通模块构建
6. loader -> 加载器加载模块
7. program -> 解析AST
8. seal -> 封装构建结果
9. emit -> 把各个chunk输出到结果文件
10. done -> 完成编译
这些生命周期钩子的优点是:
- 可以在特定的时机介入编译过程
- 提供了灵活的异步处理能力
- 可以访问和修改编译过程中的数据
- 便于插件之间的解耦和组合
使用这些生命周期钩子时需要注意:
- 选择合适的钩子时机
- 正确处理异步操作
- 注意不同钩子之间的依赖关系
- 避免在不必要的钩子中执行耗时操作