序言
- 今天多种维度来大家解析plugin实现原理。这样可以让大家知道plugin是干什么的,在什么时期来执行plugin是最好的时机
- 从源码的角度来分析。2. 从插件html-webpack-plugin的实现原理看分析。3. 从自我实现来分析
多维度分析
① webpack中plugin怎么使用???
- 以上的代码是:用vue-cli来创建vue2的项目,在文件webpack.prod.config.js中书写的
- 通过上述的实例其实我们知道plugin插件在webpack中的哪个位置进行工作,使用的方式是什么,接下来我们来说下,他是如何在webpack中加载的。
② webpack中plugin如何实现???
compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
// 如果是方法直接调用
plugin.call(compiler, compiler);
} else {
// 一般都是类 类实例一定是对象 直接调用方法apply
plugin.apply(compiler);
}
}
}
以上代码来自webpack4源码,地址是resolve-code, 46 ~60行
- 通过上述的判断可以得到,如果在options中存在plugins的话,直接进行遍历
- plugin有两种实现方案,如果是function的话,直接调用。反之如果不是的话调用apply方法。
- 这里肯定会有很多人疑问???为什么是apply方法呢??首先大家要记住这个apply跟Function.prototype.apply完全是两码事,至于为什么叫apply的话,只能去问webpack作者他命名这个的意义了
- 至于插件怎么写,我们会在下面进行讲述。
③ html-webpack-plugin是如何实现的呢???
// 插件HtmlWebpackPlugin
class HtmlWebpackPlugin {
/**
* @param {HtmlWebpackOptions} [options]
*/
constructor (options) {
/** @type {HtmlWebpackOptions} */
this.userOptions = options || {};
this.version = HtmlWebpackPlugin.version;
}
// 表示调用入口 compiler -- 表示webpack compiler实例
apply (compiler) {
// Wait for configuration preset plugions to apply all configure webpack defaults
// 当编译器对象被初始化时调用
compiler.hooks.initialize.tap('HtmlWebpackPlugin', () => {
const userOptions = this.userOptions;
以上的代码来自html-webpack-plugin源码,地址是code-resolve 28 ~ 44行
- 通过上述源码,我们就可以看到其实这个插件本身是一个类,在类中实现了apply方法
- 所以在webpack在调用插件的时候,其实调用了实例的apply方法,将自身的编译器实例compiler当参数进行传递
④ 那插件的调用时机是是什么???
// webpack的 编译器
class Compiler extends Tapable {
constructor(context) {
super();
// 通过Tapable 实现各种hook钩子
// 每个钩子 都有不同的调用时机,可以参照<https://webpack.docschina.org/api/compiler-hooks/#hooks>
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
...
以上的代码来自webpack4源码,地址是 code-resolve 42~65行
- 其实想了解触发时机,必须先知道发布订阅模式以及Tapable插件
- 上述代码的this.hooks 就是发布订阅的实例,每种订阅的类型都不同
- 那插件是如何订阅的呢,请看第二幅源码(html-webpack-plugin源码)
compiler.hooks.initialize.tap('HtmlWebpackPlugin', () => { - 这里做下解释,compiler就是this,可以通过initialize.tap进行订阅。
- 那到底订阅的时机是什么呢,可以参照下官网Compiler
| 订阅方法 | 描述 |
|---|---|
| environment | 在编译器准备环境时调用,时机就在配置文件中初始化插件之后 |
| afterEnvironment | 当编译器环境设置完成后,在 environment hook 后直接调用 |
| initialize | 当编译器对象被初始化时调用 |
| ... | ... |
- 这里大致的意思就是不同时期订阅不同的钩子,代码执行过程中会在合适的时机进行发布
⑤ 如何实现自己的插件呢???
class RunPlugin {
apply(compiler) {
compiler.hooks.run.tap('run', () => {
console.log('已经开始执行了.............')
})
}
}
module.exports = RunPlugin
class Compiler {
constructor(options) {
this.hooks = {
run: new SyncHook(),
done: new SyncHook()
}
this.options = options
}
以上代码的地址是webpack-flow, webpack-flow-Comilper
- 其实说到这里,我想大家都应该明白webpack-plugin实现原理了吧。但是具体要用plugin做什么事情,需要大家慢慢探索啊
结语
- 如果有不对的地方希望大家多多指针,大家一起学习一起“卷”
- 其实小编还写了其他的文章,可以参照个人博客