1. Webpack自定义插件生命周期钩子函数执行顺序
entryOption
:在Webpack开始解析入口(entry)配置时触发。这是Webpack生命周期中最早触发的钩子之一,在解析入口之前调用。
run
:在Webpack启动读取配置,但是还未开始读取文件系统时触发。此时,Webpack已经初始化完毕,但还未真正开始编译。
watchRun
:在开启监听模式时,每次检测到文件变化,重新启动编译之前触发。它和run
类似,只不过是在监听模式下文件变化时触发。
compile
:在Webpack创建新的Compilation
对象之前触发。Compilation
对象包含了此次编译的所有模块和资源等信息,此钩子可以用来修改Compilation
的创建过程。
compilation
:在Webpack创建Compilation
对象之后触发。可以在此钩子中访问和操作Compilation
对象,例如添加自定义加载器(loader)、解析器(parser)等。
buildModule
:在一个模块开始构建时触发。可以利用此钩子对模块构建过程进行干预,比如在模块构建前对其进行预处理。
normalModuleLoader
:在loader
加载一个NormalModule
(普通模块,非chunk模块)时触发。常用于对loader
加载模块的过程进行观察或修改。
finishModules
:当所有模块都被构建完成时触发。可以在此处对构建完成的所有模块进行统一的处理,例如对模块的统计分析等。
seal
:在所有模块及其依赖都被解析完毕,并且优化操作(如代码压缩、Tree - shaking等)即将开始之前触发。可以在此处对即将进行优化的资源进行最后的调整。
optimize
:在Webpack开始执行优化操作(如代码压缩、Tree - shaking等)时触发。用于在优化阶段执行自定义的优化逻辑。
afterOptimizeAssets
:在优化资源(如压缩文件大小等)完成后触发。可以在此处对优化后的资源进行检查或进一步处理。
afterOptimizeChunks
:在优化代码块(chunks)完成后触发。例如,可以在此时对优化后的代码块进行分析,查看是否达到预期的拆分效果。
afterEmit
:在Webpack将编译后的资源输出到输出目录之前触发。常用于在资源输出前对其进行最后的修改,如添加版权声明等。
emit
:在Webpack即将输出资源到输出目录时触发。可以在这个钩子中生成额外的文件,比如生成一个包含编译信息的文件。
done
:在整个编译过程完成时触发。可以在此处执行一些清理工作,或者向外部工具发送编译完成的通知等。
2. 每个钩子函数在实际开发中的应用场景举例
entryOption
- 应用场景:在多入口项目中,动态修改入口配置。例如,根据不同的环境变量,决定是否加载某些特定的入口文件。
- 示例代码:
class DynamicEntryPlugin {
constructor(env) {
this.env = env;
}
apply(compiler) {
compiler.hooks.entryOption.tap('DynamicEntryPlugin', (context, entry) => {
if (this.env === 'production') {
// 在生产环境下,移除某些调试入口
delete entry.debugEntry;
}
});
}
}
run
- 应用场景:在编译开始前,初始化一些全局变量或进行一些准备工作,比如初始化日志记录器。
- 示例代码:
class InitLoggerPlugin {
apply(compiler) {
compiler.hooks.run.tap('InitLoggerPlugin', () => {
console.log('Webpack compilation is starting...');
// 初始化日志记录器逻辑
});
}
}
watchRun
- 应用场景:在监听模式下,每次重新编译前清理一些临时文件。
- 示例代码:
const fs = require('fs');
const path = require('path');
class CleanTempFilesPlugin {
apply(compiler) {
compiler.hooks.watchRun.tap('CleanTempFilesPlugin', () => {
const tempDir = path.join(__dirname, 'temp');
if (fs.existsSync(tempDir)) {
fs.readdirSync(tempDir).forEach(file => {
fs.unlinkSync(path.join(tempDir, file));
});
fs.rmdirSync(tempDir);
}
});
}
}
compile
- 应用场景:修改
Compilation
的默认行为,例如添加自定义的模块解析规则。
- 示例代码:
class CustomModuleParserPlugin {
apply(compiler) {
compiler.hooks.compile.tap('CustomModuleParserPlugin', (params) => {
params.normalModuleFactory.hooks.parser
.for('javascript/auto')
.tap('CustomModuleParserPlugin', (parser) => {
// 添加自定义解析规则,例如解析特定的自定义语法
parser.hooks.expression.for('@custom').tap('CustomModuleParserPlugin', () => {
// 处理逻辑
});
});
});
}
}
compilation
- 应用场景:添加自定义的加载器(loader)或解析器(parser)到
Compilation
中。例如,添加一个自定义的CSS处理加载器。
- 示例代码:
class CustomCSSLoaderPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('CustomCSSLoaderPlugin', (compilation) => {
compilation.normalModuleFactory.hooks.loaders.tap('CustomCSSLoaderPlugin', (loaders) => {
loaders.push({
loader: path.resolve(__dirname, 'custom - css - loader.js'),
options: { /* 配置项 */ }
});
return loaders;
});
});
}
}
buildModule
- 应用场景:在模块构建前对模块内容进行预处理。例如,对JavaScript模块添加一些全局变量声明。
- 示例代码:
class PreprocessModulePlugin {
apply(compiler) {
compiler.hooks.buildModule.tap('PreprocessModulePlugin', (module) => {
if (module.type === 'javascript/auto') {
const source = module._source.source();
const newSource = `const globalVar = 'Hello, Webpack!';\n${source}`;
module._source = new compiler.webpack.sources.RawSource(newSource);
}
});
}
}
normalModuleLoader
- 应用场景:观察
loader
加载模块的过程,记录加载时间等信息。
- 示例代码:
class LoaderTimingPlugin {
apply(compiler) {
compiler.hooks.normalModuleLoader.tap('LoaderTimingPlugin', (loaderContext, module) => {
const startTime = Date.now();
loaderContext._next = loaderContext.next;
loaderContext.next = function () {
const endTime = Date.now();
console.log(`Loader for ${module.resource} took ${endTime - startTime} ms`);
return loaderContext._next.apply(this, arguments);
};
});
}
}
finishModules
- 应用场景:统计所有模块的大小等信息,生成模块报告。
- 示例代码:
class ModuleStatsPlugin {
apply(compiler) {
compiler.hooks.finishModules.tap('ModuleStatsPlugin', (compilation) => {
let totalSize = 0;
compilation.modules.forEach(module => {
totalSize += module.size();
});
console.log(`Total size of all modules: ${totalSize} bytes`);
});
}
}
seal
- 应用场景:在优化操作前,对资源进行最后的调整,比如移除一些无用的注释。
- 示例代码:
const UglifyJS = require('uglify-js');
class RemoveCommentsPlugin {
apply(compiler) {
compiler.hooks.seal.tap('RemoveCommentsPlugin', (compilation) => {
compilation.chunks.forEach(chunk => {
chunk.files.forEach(file => {
if (/\.js$/.test(file)) {
const source = compilation.assets[file].source();
const { code } = UglifyJS.minify(source, {
compress: { drop_console: true },
output: { comments: false }
});
compilation.assets[file] = new compiler.webpack.sources.RawSource(code);
}
});
});
});
}
}
optimize
- 应用场景:执行自定义的优化逻辑,例如对CSS进行自定义的压缩。
- 示例代码:
const cssnano = require('cssnano');
class CustomCSSOptimizationPlugin {
apply(compiler) {
compiler.hooks.optimize.tap('CustomCSSOptimizationPlugin', (compilation) => {
compilation.chunks.forEach(chunk => {
chunk.files.forEach(file => {
if (/\.css$/.test(file)) {
const source = compilation.assets[file].source();
const optimized = cssnano.process(source).css;
compilation.assets[file] = new compiler.webpack.sources.RawSource(optimized);
}
});
});
});
}
}
afterOptimizeAssets
- 应用场景:检查优化后的资源大小,若超过一定阈值则发出警告。
- 示例代码:
class AssetSizeCheckPlugin {
constructor(maxSize) {
this.maxSize = maxSize;
}
apply(compiler) {
compiler.hooks.afterOptimizeAssets.tap('AssetSizeCheckPlugin', (assets) => {
Object.keys(assets).forEach(file => {
const size = assets[file].size();
if (size > this.maxSize) {
console.warn(`${file} size exceeds the limit: ${size} > ${this.maxSize}`);
}
});
});
}
}
afterOptimizeChunks
- 应用场景:分析优化后的代码块,检查代码块的拆分是否合理,例如是否存在过大的代码块。
- 示例代码:
class ChunkAnalysisPlugin {
constructor(maxChunkSize) {
this.maxChunkSize = maxChunkSize;
}
apply(compiler) {
compiler.hooks.afterOptimizeChunks.tap('ChunkAnalysisPlugin', (chunks) => {
chunks.forEach(chunk => {
const size = chunk.getSize();
if (size > this.maxChunkSize) {
console.warn(`Chunk ${chunk.name} size exceeds the limit: ${size} > ${this.maxChunkSize}`);
}
});
});
}
}
afterEmit
- 应用场景:在资源输出前对其进行最后的修改,比如添加版权声明。
- 示例代码:
class CopyrightPlugin {
constructor(copyrightText) {
this.copyrightText = copyrightText;
}
apply(compiler) {
compiler.hooks.afterEmit.tap('CopyrightPlugin', (compilation) => {
Object.keys(compilation.assets).forEach(file => {
const source = compilation.assets[file].source();
const newSource = `${this.copyrightText}\n${source}`;
compilation.assets[file] = new compiler.webpack.sources.RawSource(newSource);
});
});
}
}
emit
- 应用场景:生成额外的文件,比如生成一个包含编译信息的文件。
- 示例代码:
const fs = require('fs');
const path = require('path');
class CompilationInfoPlugin {
apply(compiler) {
compiler.hooks.emit.tap('CompilationInfoPlugin', (compilation) => {
const info = {
timestamp: new Date().toISOString(),
entryPoints: Object.keys(compilation.entrypoints),
outputPath: compilation.outputOptions.path
};
const infoFilePath = path.join(compilation.outputOptions.path, 'compilation - info.json');
compilation.assets['compilation - info.json'] = new compiler.webpack.sources.RawSource(JSON.stringify(info, null, 2));
});
}
}
done
- 应用场景:在编译完成后,执行一些清理工作,或者向外部工具发送编译完成的通知。
- 示例代码:
class CleanupAndNotifyPlugin {
constructor(notifyUrl) {
this.notifyUrl = notifyUrl;
}
apply(compiler) {
compiler.hooks.done.tap('CleanupAndNotifyPlugin', () => {
// 清理临时文件逻辑
const tempDir = path.join(__dirname, 'temp');
if (fs.existsSync(tempDir)) {
fs.readdirSync(tempDir).forEach(file => {
fs.unlinkSync(path.join(tempDir, file));
});
fs.rmdirSync(tempDir);
}
// 发送通知逻辑
if (this.notifyUrl) {
// 发送HTTP请求通知外部工具
}
});
}
}