背景
项目里有用到inline-manifest-webpack-plugin,当把html-webpack-plugin升级到v4时,插件就报错了。原本想等插件适配,结果发现不维护了。所以自己动手实现一个吧!
webpack 构建流程
webpack的基本构建流程如下:
webpack 初始化: 包括校验配置文件,初始化默认插件,创建Compiler实例等
run: 执行 run 方法
compilation:创建Compilation实例,回调compilation相关钩子
emit:文件内容已经解析完毕,准备生成文件
afterEmit:文件已经写入磁盘
done:完成编译
webpack 插件实例
webpack-plugin需要实现一个apply方法,参数是Compiler实例,下面是一个简单的实例
class MyWebpackPlugin {
constructor(options) {}
apply(compiler) {
compiler.hooks.emit.tap("MyWebpackPlugin", (compilation) => {
console.log("MyWebpackPlugin");
});
}
}
module.exports = MyWebpackPlugin;
复制代码
只需要在webpack的配置文件中,添加插件就能在控制台看见输出了
module.exports = {
plugins:[
new MyWebpackPlugin(),
]
};
复制代码
要实现inline-manifest-webpack-plugin的功能,只需要在合适的hook里面实现我们的逻辑就行了
inline-manifest-webpack-plugin 的作用
将我们选择的chunk文件直接插入到html中,减少请求次数,方便做缓存等,一般用来抽离 webpack 的运行时文件(runtime.js)。它大概实现了一下三个功能:
删除index.html里的script标签
将删除标签所对应的内容直接插入index.html
删除原来的js文件
删除 index.html 里的 script 标签
inline-manifest-webpack-plugin是基于html-webpack-plugin上的插件,html-webpack-plugin为我们提供的 hook 如图所示
在生成标签之前(beforeAssetTagGeneration)去掉指定 chunk 文件
compiler.hooks.compilation.tap("InlineManifestWebpackPlugin", (compilation) => {
// 在标签生成之前
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
"InlineManifestWebpackPlugin",
(htmlPluginData, cb) => {
const assets = htmlPluginData.assets;
const manifestAssetName = this.getAssetByName(compilation.chunks, this.chunkName);
if (manifestAssetName) {
const runtimeIndex = assets.js.indexOf(assets.publicPath + manifestAssetName);
// 缓存第二步里插入的内容和位子
this.chunkScript = {
tagName: "script",
closeTag: true,
attributes: {
type: "text/javascript",
},
innerHTML: sourceMappingURL.removeFrom(compilation.assets[manifestAssetName].source()),
};
this.chunkIndex = runtimeIndex;
// 从html中删除原标签
if (runtimeIndex !== -1) {
assets.js.splice(runtimeIndex, 1);
delete assets.js[this.chunkName];
}
}
cb(null, htmlPluginData);
}
);
});
复制代码
将删除标签所对应的内容直接插入 index.html
生成标签的时候把第一步缓存的标签插入
compiler.hooks.compilation.tap("InlineManifestWebpackPlugin", (compilation) => {
// 插入标签
HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(
"InlineManifestWebpackPlugin",
(htmlPluginData, cb) => {
// 向html中注入
if (this.chunkScript && this.chunkIndex !== -1) {
htmlPluginData.assetTags.scripts.splice(this.chunkIndex, 0, this.chunkScript);
this.chunkScript = null;
this.chunkIndex = -1;
}
cb(null, htmlPluginData);
}
);
});
复制代码
删除原来的 js 文件
在 webpack 生成所以文件之后,删除原本的文件
// 在emit阶段插入钩子函数
compiler.hooks.emit.tap("InlineManifestWebpackPlugin", (compilation) => {
// 删除原文件
delete compilation.assets[this.getAssetByName(compilation.chunks, this.chunkName)];
});
复制代码
此时插件的功能基本上实现完成,一下是完整代码
const sourceMappingURL = require("source-map-url");
const HtmlWebpackPlugin = require("html-webpack-plugin");
class InlineManifestWebpackPlugin {
constructor(chunkName) {
this.chunkName = chunkName || "runtime";
this.chunkScript = null;
this.chunkIndex = -1;
}
getAssetByName = (chunks, chunkName) => {
return (chunks.find((chunk) => chunk.name === chunkName) || { files: [] }).files[0];
};
apply(compiler) {
// 注入 html-webpack-plugin 的处理过程
compiler.hooks.compilation.tap("InlineManifestWebpackPlugin", (compilation) => {
// 在标签生成之前
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(
"InlineManifestWebpackPlugin",
(htmlPluginData, cb) => {
const assets = htmlPluginData.assets;
const manifestAssetName = this.getAssetByName(compilation.chunks, this.chunkName);
if (manifestAssetName) {
const runtimeIndex = assets.js.indexOf(assets.publicPath + manifestAssetName);
this.chunkScript = {
tagName: "script",
closeTag: true,
attributes: {
type: "text/javascript",
},
innerHTML: sourceMappingURL.removeFrom(compilation.assets[manifestAssetName].source()),
};
this.chunkIndex = runtimeIndex;
// 从html中删除原标签
if (runtimeIndex !== -1) {
assets.js.splice(runtimeIndex, 1);
delete assets.js[this.chunkName];
}
}
cb(null, htmlPluginData);
}
);
// 插入标签
HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync(
"InlineManifestWebpackPlugin",
(htmlPluginData, cb) => {
// 向html中注入
if (this.chunkScript && this.chunkIndex !== -1) {
htmlPluginData.assetTags.scripts.splice(this.chunkIndex, 0, this.chunkScript);
this.chunkScript = null;
this.chunkIndex = -1;
}
cb(null, htmlPluginData);
}
);
});
// 在emit阶段插入钩子函数
compiler.hooks.emit.tap("InlineManifestWebpackPlugin", (compilation) => {
// 删除原文件
delete compilation.assets[this.getAssetByName(compilation.chunks, this.chunkName)];
});
}
}
module.exports = InlineManifestWebpackPlugin;
复制代码