上述 9 个 hooks 都继承自 Hook 这个 classjavascript
hook 对外提供了 isUsed
call
promise
callAsync
compile
tap
tapAsync
tapPromise
intercept
这些方法java
其中 tap
开头的方法是用来订阅事件的,call
promise
callAsync
是用来触发事件的,isUsed
返回了一个 boolean
值用来标记当前 hook
中注册的事件是否被执行完成。webpack
isUsed
源码webisUsed() { return this.taps.length > 0 || this.interceptors.length > 0; } 复制代码
tap
tapAsync
tapPromise
这三个方法第一个参数传入能够支持传入 string
(通常是指 plugin 的名称) 或者一个 Tap
类型,第二个参数是一个回调用来接收事件被 emit
时的调用。数组
export interface Tap {
name: string; // 事件名称,通常就是 plugin 的名字
type: TapType; // 支持三种类型 'sync' 'async' 'promise'
fn: Function;
stage: number;
context: boolean;
}
复制代码
call
promise
callAsync
这三个方法在传入参数的时候是依赖于 hook
被实例化的时候传入的 args
数组占位符的数量的,以下示例:promise
const sync = new SyncHook(['arg1', 'arg2']) // 'arg1' 'arg2' 为参数占位符
sync.tap('Test', (arg1, arg2) => {
console.log(arg1, arg2) // a2
})
sync.call('a', '2')
复制代码
其中 promise
调用会返回一个 Promise
,callAsync
默认支持传入一个 callback
。bash
Sync
开头的 hook
不支持使用 tapAsync
和 tapPromise
,能够看下述的以 SyncHook
的源码为例app
const TAP_ASYNC = () => {
throw new Error("tapAsync is not supported on a SyncHook");
};
const TAP_PROMISE = () => {
throw new Error("tapPromise is not supported on a SyncHook");
};
const COMPILE = function(options) {
factory.setup(this, options);
return factory.create(options);
};
function SyncHook(args = [], name = undefined) {
const hook = new Hook(args, name);
hook.constructor = SyncHook;
hook.tapAsync = TAP_ASYNC;
hook.tapPromise = TAP_PROMISE;
hook.compile = COMPILE;
return hook;
}
SyncHook.prototype = null;
复制代码
在这里面咱们能够看到 tapAsync
和 tapPromise
是被重写了直接 throw error
了async
下面的例子会给你们带来一个简单地示范函数
class TapableTest {
constructor() {
this.hooks = {
sync: new SyncHook(['context', 'hi']),
syncBail: new SyncBailHook(),
syncLoop: new SyncLoopHook(),
syncWaterfall: new SyncWaterfallHook(['syncwaterfall']),
asyncParallel: new AsyncParallelHook(),
asyncParallelBail: new AsyncParallelBailHook(),
asyncSeries: new AsyncSeriesHook(),
asyncSeriesBail: new AsyncSeriesBailHook(),
asyncSeriesWaterfall: new AsyncSeriesWaterfallHook(['asyncwaterfall'])
}
}
emitSync() {
this.hooks.sync.call(this, err => {
console.log(this.hooks.sync.promise)
console.log(err)
})
}
emitAyncSeries() {
this.hooks.asyncSeries.callAsync(err => {
if (err) console.log(err)
})
}
}
const test = new TapableTest()
test.hooks.sync.tap('TestPlugin', (context, callback) => {
console.log('trigger: ', context)
callback(new Error('this is sync error'))
})
test.hooks.asyncSeries.tapAsync('AsyncSeriesPlugin', callback => {
callback(new Error('this is async series error'))
})
test.emitSync()
test.emitAyncSeries()
复制代码
上述的运行结果能够这查看 runkit
当咱们定义了 webpack
的配置文件后,webpack
会根据这些配置生成一个或多个 compiler
,而插件就是在建立 compiler
时被添加到 webpack
的整个运行期间的, 能够看下述源码:(相关源码能够在 webpack
lib 下的 webpack.js
中找到)
const createCompiler = rawOptions => {
const options = getNormalizedWebpackOptions(rawOptions);
applyWebpackOptionsBaseDefaults(options);
const compiler = new Compiler(options.context);
compiler.options = options;
new NodeEnvironmentPlugin({
infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
applyWebpackOptionsDefaults(options);
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
new WebpackOptionsApply().process(options, compiler);
compiler.hooks.initialize.call();
return compiler;
};
复制代码
咱们能够看到遍历 options.plugins
这一段,这一段分了两种状况来进行插件的插入
webpack
调用,也就是说咱们能够用函数来写插件,这个函数的做用域是当前的 compiler
,函数也会接收到一个 compiler
apply
方法的对象实例,apply
方法会被传入 compiler
因此这也就解释了为何咱们的插件须要 new
出来以后传入到 webpack
上一个中咱们了解到了 plugins
是什么时候被注入的,咱们能够看到在 plugin
的注入时传入了当前被实例化出来的 Compiler
,因此如今咱们须要了解下 Compiler
中作了什么
进入 Compiler.js
(也在 lib 中)咱们能够第一时间看到 Compiler
的 constructor
中定义了一个庞大的 hooks
:
this.hooks = Object.freeze({
/** @type {SyncHook<[]>} */
initialize: new SyncHook([]),
/** @type {SyncBailHook<[Compilation], boolean>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<[Stats]>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {SyncHook<[Stats]>} */
afterDone: new SyncHook(["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, AssetEmittedInfo]>} */
assetEmitted: new AsyncSeriesHook(["file", "info"]),
/** @type {AsyncSeriesHook<[Compilation]>} */
afterEmit: new AsyncSeriesHook(["compilation"])
...
})
复制代码
看到这些 hook
是否是很熟悉,全是 tapable
中的 hook
,webpack 正是依赖于这些复杂的构建 hook
而完成了咱们的代码构建,因此在咱们编写 plugin
时就能够利用这些 hook
来完成咱们的特殊需求。
好比咱们常常用到的 HtmlWebpackPlugin
,咱们能够看下他是如何运行的,在 HtmlWebpackPlugin
的 apply
中咱们能够找到这样一段代码:
compiler.hooks.emit.tapAsync('HtmlWebpackPlugin', (compiler, callback) => {
...
})
复制代码
说明 HtmlWebpackPlugin
是利用了 Compiler
的 emit
的 hook
来完成的
经过深刻了解,webpack
是在庞大的插件上运行的,他本身内置了不少插件
上述内容若有错误,请指正