Webpack插件主要工作流程
- 读取命令行或配置文件
webpack.config.js参数 - 实例化
Compiler,注册plugin配置中的插件 - 执行
Compiler.run()方法进行编译,过程中实例化一个Compilation对象进行构建打包操作Webpack内部数据,执行callback()回调 - 打包生成代码块
chunk及各类资源输出到output路径
Compiler和Compilation继承自Tapable,整个流程使用Tapable的Hook机制管理打包
编写Webpack插件必备条件
Webpack插件是一个函数或者类- 该方法或者类包含一个
apply方法,接收compiler对象参数
实现一个简单的Webpack插件例子
- 目的是将
script标签 插入html,并且给src值添加 时间戳(例子来源网络,进行改造) 1、方法一 - 编译时获取
plugin配置,实例化SetScriptTimestampPlugin对象 - 利用
Compiler的Compilaion对象,监听html-webpack-plugin的beforeAssetTagGeneration钩子,在把js插入到html之前进行拦截 - 遍历
HtmlWebpackPlugin的assets参数获取打包后的js,添加时间戳,将修改后的js文件重新赋值给HtmlWebpackPlugin.assets.js - 调用
callback回调函数完成流程
// SetScriptTimestampPlugin.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
class SetScriptTimestampPlugin {
constructor(options) {
this.options = options;
this.name = "SetScriptTimestampPlugin"
}
apply(compiler) {
compiler.hooks.compilation.tap(this.name, (compilation, callback) => {
const run = this.run.bind(this, compilation);
if (compilation.hooks.htmlWebpackPluginAfterHtmlProcessing) {
// html-webpack-plugin v3 插件
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync(this.name, run);
} else {
// html-webpack-plugin v4
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(this.name, run);
}
}
);
}
run(compilation, HtmlWebpackPluginData, callback) {
const jsList = HtmlWebpackPluginData.assets.js
const timeStrip = `?${new Date().getTime()}`
HtmlWebpackPluginData.assets.js = jsList.map(item => {
item += timeStrip
return item
})
callback(null, HtmlWebpackPluginData)
}
}
module.exports = SetScriptTimestampPlugin;
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SetScriptTimestampPlugin = require('../plugins/SetScriptTimestampPlugin');
const path = require('path');
const config = {
mode: 'development',
entry: {
main: path.resolve(__dirname,'../src/index.js'),
index: path.resolve(__dirname,'../src/entry.js')
},
output: {
path: path.resolve(__dirname,'../dist'),
filename:'[name].bundle.js'
},
plugins: [
new HtmlWebpackPlugin({filename: "index.html",template: './src/index.html'}),
new SetScriptTimestampPlugin(), // 引入SetScriptTimestampPlugin插件
],
devServer: {
contentBase: path.join(__dirname, '../dist'),
publicPath: '/',
port: 1111,
}
};
module.exports = config;
2、方法二
- 监听
Compiler.hooks.emit类,此时状态已完成编译,生成资源到output目录之前 - 遍历
compilation的namedChunks(还是chunks也可以????)获取输出块的js名字,加上时间戳(可以打印看看compilation的属性) - 获取
compilation.assets的html文件内容,正则匹配原来的script标签,替换成src有时间戳的script标签,最后返回修改后的文件
// SetScriptTimestampPlugin.js
class SetScriptTimestampPlugin {
constructor(options) {
this.options = options;
this.name = "SetScriptTimestampPlugin"
}
apply(compiler) {
compiler.hooks.emit.tap(this.name, (compilation) => {
Object.keys(compilation.assets).forEach((data) => {
if (data.includes(".html")) {
let content = compilation.assets[data].source() // 欲处理的文本
let newScriptStr = ""
const timeStr = `?${new Date().getTime()}`
const reg = new RegExp("<script\\b[^>]*>[\\s\\S]*<\\/script>")
compilation.namedChunks.forEach(item => {
newScriptStr += `<script src="${item.files[0] + timeStr}"></script>`
})
content = content.replace(reg, newScriptStr)
compilation.assets[data] = {
source() {
return content
},
size() {
return content.length
}
}
}
})
}
);
}
}
module.exports = SetScriptTimestampPlugin;
- 执行结果
最后
Compiler和Compilation存在很多钩子,在编写插件的时候会不清楚何时调用什么钩子- 官网直接调用的
Compiler.plugin为啥本地会报错喃 - 版本问题-WEBPACK 4迁移说明- compiler.hooks.emit.tap 还是 compiler.hooks.emit.tapAsync?
- 例子很简单,感觉整个编译过程还是挺复杂的,革命尚未成功,同志仍需努力!