webpack-plugin工作原理
Webpack 插件的工作原理基于 Tapable 事件流系统(事件驱动的插件系统),允许开发者在 Webpack 构建流程的特定阶段插入自定义逻辑。
一、核心概念
- Tapable
- Webpack 内部使用的事件发布 - 订阅库,提供多种钩子(Hook)类型。
- 钩子类型包括:同步钩子(SyncHook)、异步串行钩子(AsyncSeriesHook)、异步并行钩子(AsyncParallelHook)等。
- 钩子(Hook)
- 定义在 Webpack 关键流程节点的事件点,插件通过监听这些钩子来执行代码。
- 例如:compiler.hooks.compile(编译开始前)、compilation.hooks.optimize(优化资源时)。
- Compiler 与 Compilation
- Compiler:全局单例,包含 Webpack 环境的所有配置信息,生命周期贯穿整个构建过程。
- Compilation:每次构建生成一个新实例,包含当前构建的模块、依赖和资源信息。
二、插件工作流程
- 注册阶段
- 在 Webpack 配置中引入插件并实例化,插件通过 apply 方法注册到 Webpack。
js
// webpack.config.js
const MyPlugin = require('./MyPlugin');
module.exports = {
plugins: [
new MyPlugin(options)
]
};
- 监听钩子
- 插件在 apply 方法中监听特定钩子,绑定回调函数。
js
class MyPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
// 监听编译开始钩子
compiler.hooks.compile.tap('MyPlugin', (params) => {
console.log('编译开始:', params);
});
// 监听资源生成完成钩子(异步)
compiler.hooks.emit.tapAsync('MyPlugin', (compilation, callback) => {
// 修改或添加资源
compilation.assets['custom-asset.txt'] = {
source: () => '这是自定义资源',
size: () => 14
};
callback();
});
}
}
- 执行阶段
- 当 Webpack 运行到注册的钩子时,插件的回调函数被调用。
- 可以在回调中访问 Compilation 实例,修改资源、添加新资源、记录日志等。
三、关键钩子
- Compiler 钩子
- compile:编译开始前触发。
- compilation:创建新的 compilation 实例时触发。
- emit:生成资源到输出目录前触发。
- done:编译完成后触发。
- Compilation 钩子
- buildModule:开始构建模块时触发。
- normalModuleLoader:模块加载时触发(可修改 loader 配置)。
- optimize:优化模块时触发。
- chunkAsset:生成 chunk 资源时触发。
webpack常见的plugin
- htmlWebpackPlugin:用于创建html
- clearWebpackPlugin:用于清理旧的dist目录
- MiniCssExtraPlugin:用于提取css到单独文件
- definePlugin: 用于定义全局变量
- CopyWebpackPlugin:用于复制文件
- UglifyjsWebpackPlugin:用于压缩js
- moduleFedurationPlugin:模块联邦相关
- HotModuleReplacementPlugin:热更新
webpack 插件示例
找出js文件中的连续中文
js
class ChineseCharacterDetectorPlugin {
constructor(options = {}) {
this.options = {
threshold: 2, // 连续中文字符的最小数量
logLevel: 'warning', // 日志级别: 'info' | 'warning' | 'error'
outputFile: null, // 输出报告文件路径
...options
};
}
apply(compiler) {
// 中文正则表达式
const chineseRegex = /[\u4e00-\u9fa5]/;
// 编译完成后处理资源
compiler.hooks.emit.tapAsync('ChineseCharacterDetectorPlugin', (compilation, callback) => {
const warnings = [];
const errors = [];
// 遍历所有JS资源
Object.keys(compilation.assets).forEach(filename => {
if (filename.endsWith('.js')) {
const source = compilation.assets[filename].source();
const matches = this.findConsecutiveChinese(source, filename);
if (matches.length > 0) {
if (this.options.logLevel === 'error') {
errors.push(...matches);
} else {
warnings.push(...matches);
}
}
}
});
// 输出警告
if (warnings.length > 0) {
console.log('发现连续中文字符:');
warnings.forEach(warning => {
console.log(` - ${warning.file}:${warning.line}:${warning.column} - "${warning.text}"`);
});
}
// 输出错误
if (errors.length > 0) {
console.log('发现连续中文字符:');
errors.forEach(error => {
console.log(` - ${error.file}:${error.line}:${error.column} - "${error.text}"`);
});
compilation.errors.push(new Error('发现不符合规范的连续中文字符'));
}
// 输出报告文件
if (this.options.outputFile && (warnings.length > 0 || errors.length > 0)) {
const report = [...warnings, ...errors].map(item =>
`${item.file}:${item.line}:${item.column} - "${item.text}"`
).join('\n');
compilation.assets[this.options.outputFile] = {
source: () => report,
size: () => report.length
};
}
callback();
});
}
// 查找连续的中文字符
findConsecutiveChinese(source, filename) {
const lines = source.split('\n');
const results = [];
lines.forEach((line, lineIndex) => {
let currentMatch = '';
let startColumn = -1;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (chineseRegex.test(char)) {
if (currentMatch === '') {
startColumn = i;
}
currentMatch += char;
} else {
// 连续中文中断,检查长度
this.checkAndAddMatch(results, filename, lineIndex + 1, startColumn + 1, currentMatch);
currentMatch = '';
startColumn = -1;
}
}
// 检查行尾的连续中文
this.checkAndAddMatch(results, filename, lineIndex + 1, startColumn + 1, currentMatch);
});
return results;
}
// 检查匹配并添加到结果
checkAndAddMatch(results, filename, line, column, text) {
if (text.length >= this.options.threshold) {
results.push({
file: filename,
line,
column,
text
});
}
}
}
module.exports = ChineseCharacterDetectorPlugin;
引用插件
js
const ChineseCharacterDetectorPlugin = require('./ChineseCharacterDetectorPlugin');
module.exports = {
// 其他配置...
plugins: [
new ChineseCharacterDetectorPlugin({
threshold: 3, // 连续中文最小数量,默认为2
logLevel: 'error', // 日志级别:'info' | 'warning' | 'error'
outputFile: 'chinese-matches.log' // 输出报告文件路径
})
]
};