核心对象:
- compiler: 代表Webpack构建的全局上下文,钩子是全局生命周期钩子
- compilation: 代表一次构建的编译过程,钩子是编译阶段钩子
生命周期:
整个过程分为【初始化->编译->输出->结束】,一共四个阶段,以下列举一下各个阶段的核心钩子:
- 初始化:
- entryOption 动态新增入口
- 编译:
- compile:等同于vite的config阶段,能够获得最终生成的config文件,如果需要添加一些环境值的占位符,可以在这个阶段介入
js
compiler.hooks.compiler.tap('插件名',()=>{
compiler.options.plugins.push(
new webpack.DefinePlugin({
'process.env.APP_VERSION': JSON.stringify(PLACEHOLDER),
})
);
console.log('✅ 已注入APP_VERSION占位符:', PLACEHOLDER);
})
- compilation: 内部包含整个打包构建流程,像是buildModule和optimize等等,实际打包实例已经创建完成,一般这个阶段可以拿到文件最终的contenthash值
js
compiler.hooks.compilation.tap('ContentHashReplacePlugin', () => {
this.contentHash = this.generateContentHash();
console.log('✅ 生成真实contentHash:', this.contentHash);
});
- 输出:
- emit: 异步钩子!必须和callback和tapAsync结合使用!准备将数据写入磁盘,这个阶段可以把环境值的占位符替换成真正的数据
js
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 遍历所有输出的资源
for (const [filename, source] of Object.entries(compilation.assets)) {
if (filename.endsWith('.js')) {
// 读取原内容,添加版权注释
const content = source.source();
const newContent = `/* 版权所有 © 2025 MyProject */\n${content}`;
// 替换资源内容
compilation.assets[filename] = {
source: () => newContent,
size: () => newContent.length
};
}
}
callback(); // 异步钩子必须调用 callback 结束
});
- 结束:
- done: 构建完成之后输出一些日志信息,生成报告
tap和tapAsync的区别:
同步钩子和异步钩子调用时候的函数。
tapAsync必须和callback结合使用,用于通知webpack异步流程已经结束,可以继续接下来的流程,不然会卡住。
Plugin: 用于webpack打包生命周期中执行的一些函数,比如css和图片压缩的plugin,无视打包模块的IgnorePlugin
Loader: 用于代码转换,因为浏览器只能解析html,css和js,所以会有各种loader将浏览器没法解析的东西转换成能解析的语言,同时webpack本身无法识别一些文件,也需要Loader做转换,比如css-loader,sass-loader,ts-loader,style-loader,postcss-loader
(1.1) css-loader:因为webpack没办法识别css文件,webpack其实只能理解js和json,所以才会使用需要css-loader去处理css文件引用,将其转为模板字符串
(1.2) style-loader: 将css-loader生成的样式字符串注入到style标签中
(1.3) postcss-loader: 对css做兼容处理,自动增加前缀
执行顺序是postcss-loader->css-loader->style-loader,而配置的时候需要从右向左配置,顺序错误,会导致报错
Loader整体执行阶段类似于洋葱模型,从左到右依次遍历对应的loader,再从右到左执行对应的loader
js
module.exports = {
module: {
rules: [
{
test: /.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
};
vite自身没有loader,可以通过装插件来配置loader,但是没有原生好用,而且vite很多loader功能都原生内嵌了,而且因为工具特性,vite依赖es模块,而浏览器是完全支持es模块的,无需loader做模块转换,整体上都是依靠plugin进行处理