10 分钟了解 webpack 核心内容
直接上手稿了
Tapable 是 webpack 核心工具之一,提供了插件接口。webpack 中许多对象扩展自 Tapable
类(如,负责编译的 Compiler 和负责创建 bundles 的 Compilation)。这个类暴露 tap
, tapAsync
和 tapPromise
方法,可以使用这些方法,注入自定义的构建步骤,这些步骤将在整个编译过程中不同时机触发。
Compiler.js#L104 每一个事件钩子决定了它该如何应用插件的注册
代码语言:javascript复制class Compiler {
this.hooks = Object.freeze({
initialize: new SyncHook([]),
shouldEmit: new SyncBailHook(["compilation"]),
done: new AsyncSeriesHook(["stats"]),
...
})
}
Tapable 的核心原理是发布订阅模式。基于 Tapable 使得 webpack 具有很好的扩展性,但对于调试来说比较痛苦(代码跳跃)。
代码语言:javascript复制const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
主线
hook 事件注册 ==> hook 触发 ==> 生成 hook 执行代码new Function()
==> 执行
更多动态执行脚本方式 ,请参照:动态执行脚本
注册&触发
注册
代码语言:javascript复制interface Hook {
tap: (name: string | Tap, fn: (context?, ...args) => Result) => void,
tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void
}
触发
代码语言:javascript复制interface Hook {
call: (...args) => Result,
callAsync: (...args, callback: (err, result: Result) => void) => void,
promise: (...args) => Promise<Result>
}
Hook types
按时序方式分类
- Sync: 同步,通过
myHook.tap()
调用 - Async:异步
- AsyncSeries: 连续调用每个异步方法,通过
myHook.tap()
、myHook.tapAsync()
、myHook.tapPromise()
调用 - AsyncParallel:并行运行每个异步方法,调用方式同 AsyncSeries
- AsyncSeries: 连续调用每个异步方法,通过
按流程控制分类
- 基础类型:名称中没有 Bail、Waterfall、Loop,在触发事件之后,会按照事件注册的先后顺序执行所有的事件处理函数,不关心返回值。SyncHook、AsyncParallelHook、AsyncSeriesHook
- Waterfall:「返回结果具有流动性–瀑布」如果前一个 Hook 函数的结果
result !== undefined
,则 result 会作为后一个 Hook 函数的第一个参数。-- 有点类似于Array.prototype.reduce()
。 SyncWaterfallHook、AsyncSeriesWaterfallHook 注意:没有 AsyncParallelWaterfallHook,并行操作无法确保返回顺序,值无法传递 类别Interface返回形式tapfn: (context?, ...args) => Resultreturn resulttapAsyncfn: (context?, ...args, callback: (err, result: Result) => void) => voidcallback(err, result)tapPromisefn: (context?, ...args) => Promiseresolve(result)
- Bail:顺序执行 Hook,遇到第一个结果
result !== undefined
则返回,不再继续执行。-- 有点类似于Promise.race(iterable)
。 SyncBailHook、AsyncSeriseBailHook、AsyncParallelBailHook - Loop:不停的循环执行 Hook,直到所有函数结果
result === undefined
。SyncLoopHook、AsyncSeriseLoopHook
示例
示例1:基础示例
代码语言:javascript复制const { SyncHook } = require('tapable')
class Test {
constructor () {
this.hooks = {
compiler: new SyncHook(['name'])
}
}
tap () {
this.hooks.compiler.tap('consumer1', (name) => {
console.log('consumer1', name)
return 'consumer1'
})
this.hooks.compiler.tap('consumer2', (name) => {
console.log('consumer2', name)
return 'consumer2'
})
}
call () {
this.hooks.compiler.call('ligang')
}
}
const t = new Test()
t.tap()
t.call()
// 输出结果
// consumer1 ligang
// consumer2 ligang
SyncHook
不会处理 result 值。
示例2:SyncWaterfallHook
代码语言:javascript复制const { SyncWaterfallHook } = require('tapable')
class Test {
constructor () {
this.hooks = {
compiler: new SyncWaterfallHook(['name'])
}
}
tap () {
this.hooks.compiler.tap('consumer1', (name) => {
console.log('consumer1', name)
return 'consumer1'
})
this.hooks.compiler.tap('consumer2', (name) => {
console.log('consumer2', name)
return 'consumer2'
})
}
call () {
this.hooks.compiler.call('ligang')
}
}
const t = new Test()
t.tap()
t.call()
// 输出结果
// consumer1 ligang
// consumer2 consumer1
return 值不是undefined,发生了传递。
示例3:SyncBailHook
代码语言:javascript复制const { SyncBailHook } = require('tapable')
class Test {
constructor () {
this.hooks = {
compiler: new SyncBailHook(['name'])
}
}
tap () {
this.hooks.compiler.tap('consumer1', (name) => {
console.log('consumer1', name)
return 'consumer1'
})
this.hooks.compiler.tap('consumer2', (name) => {
console.log('consumer2', name)
return 'consumer2'
})
}
call () {
this.hooks.compiler.call('ligang')
}
}
const t = new Test()
t.tap()
t.call()
consumer1 返回的 result 值不是 undefined,因此后续路程被终止,consumer2 未被执行!
示例4:其他
异步方式类似,只要注意返回的 result 形式不同。
tap
:return result
tapAsync
:callback(err, result)
tapPromise
:resolve(result)
拦截 intercept
所有的 Hook 都提供了额外的拦截API
代码语言:javascript复制interface HookInterceptor {
call: (context?, ...args) => void,
loop: (context?, ...args) => void,
tap: (context?, tap: Tap) => void,
register: (tap: Tap) => Tap
}
- register:
(tap: Tap) => Tap | undefined
插件用tap*
方法注册时触发; - call:
(...args) => void
当被call 调用时触发,并可以访问到 hooks 参数(call 之前触发); - tap:
(tap: Tap) => void
当插件被调用触发,提供的是Tap对象,但不可修改(在call 之后,回调之前触发); - loop:
(...args) => void
loop hook 的插件被调用时触发(每个循环触发);
const { SyncHook } = require('tapable')
class Test {
constructor () {
this.hooks = {
compiler: new SyncHook(['name'])
}
}
interceptor () {
this.hooks.compiler.intercept({
register: (tap) => {
console.log('register!!!', tap)
return tap
},
call: (args => {
console.log('call!!!', args)
}),
tap: (tap => {
console.log('tap!!!', tap)
})
})
}
tap () {
this.hooks.compiler.tap('consumer1', (name) => {
console.log('consumer1', name)
return 'consumer1'
})
this.hooks.compiler.tap('consumer2', (name) => {
console.log('consumer2', name)
return 'consumer2'
})
}
call () {
this.hooks.compiler.call('ligang')
}
}
const t = new Test()
t.interceptor()
t.tap()
t.call()
// 输出结果
// register!!! { type: 'sync', fn: [Function], name: 'consumer1' }
// register!!! { type: 'sync', fn: [Function], name: 'consumer2' }
// call!!! ligang
// tap!!! { type: 'sync', fn: [Function], name: 'consumer1' }
// consumer1 ligang
// tap!!! { type: 'sync', fn: [Function], name: 'consumer2' }
// consumer2 ligang
上下文 Context
插件和拦截器都可以往里面传一个上下文对象的参数,该对象可用于向后续插件和拦截器传递任意值。通过此,可以对拦截或后续插件做灵活控制!
代码语言:javascript复制const { SyncHook } = require('tapable')
class Test {
constructor () {
this.hooks = {
compiler: new SyncHook(['name'])
}
}
interceptor () {
this.hooks.compiler.intercept({
context: true,
call: (context, args) => {
context.params = {a: 1, b: 2}
console.log('call!!!', args)
}
})
}
tap () {
this.hooks.compiler.tap({
name: 'consumer',
context: true
}, (context, name) => {
console.log('consumer1', name, context)
return 'consumer1'
})
}
call () {
this.hooks.compiler.call('ligang')
}
}
const t = new Test()
t.interceptor()
t.tap()
t.call()
// 输出结果
// call!!! ligang
// consumer1 ligang { params: { a: 1, b: 2 } }
参考地址
- https://github.com/webpack/tapable
- https://segmentfault.com/a/1190000020146256
- https://segmentfault.com/a/1190000017420937
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
- https://github.com/webpack/webpack/blob/master/lib/Compiler.js