背景
在一些需求场景下,我们需要对项目里编译后的代码进行处理,例如将指定的代码替换为远程文件的代码片段,这样插入代码相比远程动态加载方式,对于页面加载性能会好一些,减少http请求,而且对有所依赖其他变量的代码片段可以确保执行上下文的先后顺序。
webpack打包构建核心流程
在编写插件前,先简单介绍一些webpack打包构建大致流程:
- 在命令行中执行webpack xxx命令
- 进行webpack初始化,包括使用yargs读取命令行参数,合并webpack配置文件以及命令行参数,形成最终的webpack配置
- 加载entry入口文件代码
- 将入口模块代码转化为抽象语法树
- 遍历抽象语法树,通过分析import以及require函数,分析出入口模块依赖的其他资源
- 转换抽象语法树,将抽象语法树中的import、require函数转化为webpack内置的导入函数。生成最终的代码,并且以导入模块绝对路径为key,将转换的代码以及导出的内容缓存起来。
- 开始依次加载导入的其他资源的内容。并且将其交给对应的loader进行处理,转化webpack可以识别的js代码。
- 得到依赖模块的js代码之后就继续按照处理入口模块的方式进行处理;构建抽象语法树,分析依赖模块,转换导入语句和函数,缓存模块资源。
- 在所有模块分析完毕之后,将所有模块的转换内容合并生成一个bundle。
- 生成bundle文件,对bundle内容进行hash。
插件生命周期
- constructor :初始化插件,接收配置选项
- apply :在 Webpack 启动时调用,注册插件逻辑
- compiler.hooks.emit.tapAsync :在生成资源到输出目录之前执行
插件结构
class ReplaceCommentPlugin {
constructor(options) {
this.options = options || {};
this.targetComment = this.options.targetComment || "targetText";
this.replacementCode = this.options.replacementCode || "Replaced";
this.fileTypes = this.options.fileTypes || [".js"]; // 默认只替换 .js 文件
}
apply(compiler) {
compiler.hooks.emit.tapAsync(
"ReplaceCommentPlugin",
(compilation, callback) => {
Object.keys(compilation.assets).forEach((filename) => {
// 只处理指定类型的文件
if (this.fileTypes.some((ext) => filename.endsWith(ext))) {
const asset = compilation.assets[filename];
let source = asset.source();
// 替换指定的注释
const replacedSource = source.replace(
new RegExp(this.targetComment, "g"),
this.replacementCode
);
// 将修改后的源代码回写到 assets
compilation.assets[filename] = {
source: () => replacedSource,
size: () => replacedSource.length,
};
}
});
callback();
}
);
}
}
module.exports = ReplaceCommentPlugin;
使用插件
const ReplaceCommentPlugin = require('./ReplaceCommentPlugin');
module.exports = {
// ... 其他配置 ...
plugins: [
new ReplaceCommentPlugin({
targetComment: '// TODO: Replace this comment',
replacementCode: '// This comment has been replaced',
fileTypes: ['.js', '.ts']
})
]
};
注意
- 在打包过程中优化的操作,例如压缩和混淆会影响直接基于字符串进行替换的操作。因此要确保插件的执行顺序。
- 要确保替换正确,最好在 压缩和混淆之前 进行替换,或者在构建过程中使用 正则表达式 或 SourceMap 辅助来定位目标字符串。
总结
通过编写和使用 Webpack 插件,可以灵活地扩展 Webpack 的功能,满足各种构建需求。