directory "代碼已上傳github, 地址" Detailed Webpack 就像一條生產線, 要經過一系列的處理流程才能將源文件轉換成輸出結果。這條生產線上的每個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後才會轉交到下一個流程。 插件就像一個插入到生產線中的一個功能, ...
directory
- src
- sim ---- 簡單的模擬實現
- /.js$/ ---- 使用
代碼已上傳github, 地址
Detailed
Webpack 就像一條生產線, 要經過一系列的處理流程才能將源文件轉換成輸出結果。這條生產線上的每個流程都是單一的, 多個流程之間存在依賴關係。只能完成當前處理後才會轉交到下一個流程。
插件就像一個插入到生產線中的一個功能, 它會在特定的時機對生產線上的資源進行處理。
這條生產線很複雜, Webpack則是通過 tapable
核心庫來組織這條生產線。
Webpack 在運行中會通過 tapable
提供的鉤子進行廣播事件, 插件只需要監聽它關心的事件,就可以加入到這條生產線中,去改變生產線的運作。使得 Webpack整體擴展性很好。
Tapable Hook
Tapable 提供同步(Sync)和非同步(Async)鉤子類。而非同步又分為 非同步串列
、非同步並行
鉤子類。
右鍵圖片,在新標簽中查看完整圖片
逐個分析每個鉤子類的使用及其原理
同步鉤子類
- SyncHook
- SyncBailHook
- SyncWaterfallHook
- SyncLoopHook
同步鉤子類通過實例的 tap
方法監聽函數, 通過 call
發佈事件
SyncHook
同步串列不關心訂閱函數執行後的返回值是什麼。其原理是將監聽(訂閱)的函數存放到一個數組中, 發佈時遍曆數組中的監聽函數並且將發佈時的 arguments
傳遞給監聽函數
class SyncHook {
constructor(options) {
this.options = options
this.hooks = [] //存放監聽函數的數組
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
for (let i = 0; i < this.hooks.length; i++) {
this.hooks[i](...args)
}
}
}
const synchook = new SyncHook('name')
// 註冊監聽函數
synchook.tap('name', (data) => {
console.log('name', data)
})
synchook.tap('age', (data) => {
console.log('age', data)
})
// 發佈事件
synchook.call('qiqingfu')
列印結果:
name qiqingfu
age qiqingfu
SyncBailHook
同步串列, 但是如果監聽函數的返回值不為 null
, 就終止後續的監聽函數執行
class SyncBailHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
let ret, i = 0
do {
// 將第一個函數的返回結果賦值給ret, 在while中如果結果為 true就繼續執行do代碼塊
ret = this.hooks[i++](...args)
} while(!ret)
}
}
const syncbailhook = new SyncBailHook('name')
syncbailhook.tap('name', (data) => {
console.log('name', data)
return '我的返回值不為null'
})
syncbailhook.tap('age', (data) => {
console.log('age', data)
})
syncbailhook.call('qiqingfu')
執行結果
name qiqingfu
SyncWaterfallHook
同步串列瀑布流, 瀑布流指的是第一個監聽函數的返回值,做為第二個監聽函數的參數。第二個函數的返回值作為第三個監聽函數的參數,依次類推...
class SyncWaterfallHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
let [firstHook, ...otherHooks] = this.hooks
/**
* 通過解構賦值先取出第一個監聽函數執行
* 並且將第一個函數的執行結果傳遞給第二個, 第二個傳遞給第三個,迭代的過程
*/
let ret = firstHook(...args)
otherHooks.reduce((f,n) => {
return n(f)
}, ret)
}
}
const syncWaterfallHook = new SyncWaterfallHook('name')
syncWaterfallHook.tap('name', data => {
console.log('name', data)
return 23
})
syncWaterfallHook.tap('age', data => {
console.log('age', data)
})
syncWaterfallHook.call('qiqingfu')
列印結果
name qiqingfu
age 23
SyncLoopHook
同步串列, 如果監聽函數的返回值為 true
, 則反覆執行當前的監聽函數,直到返回指為 undefind
則繼續執行下麵的監聽函數
class SyncLoopHook {
constructor(options) {
this.options = options
this.hooks = []
}
tap(name, callback) {
this.hooks.push(callback)
}
call(...args) {
for (let i = 0; i < this.hooks.length; i++) {
let hook = this.hooks[i], ret
do{
ret = hook(...args)
}while(ret === true && ret !== undefined)
}
}
}
const syncLoopHook = new SyncLoopHook('name')
let n1 = 0
syncLoopHook.tap('name', data => {
console.log('name', data)
return n1 < 2 ? true : undefined
})
syncLoopHook.tap('end', data => {
console.log('end', data)
})
syncLoopHook.call('qiqingfu')
執行結果
name qiqingfu
name qiqingfu
name qiqingfu 第三次列印的時候, n1的指為2, 返回值為 undefined則執行後面的監聽函數
end qiqingfu
非同步鉤子
- 非同步並行
(Parallel)
- AsyncParallelHook
- AsyncParalleBailHook
- 非同步串列
(Series)
- AsyncSeriesHook
- AsyncSeriesBailHook
- AsyncSeriesWaterfallHook
凡有非同步,必有回調
同步鉤子是通過 tap
來監聽函數的, call
來發佈的。
非同步鉤子是通過 tapAsync
或 tapPromise
來監聽函數,通過 callAsync
或 promise
來發佈訂閱的。
AsyncParallelHook
非同步並行, 監聽的函數會一塊執行, 哪個函數先執行完就先觸發。不需要關心監聽函數的返回值。
class AsyncParallelHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
// 訂閱
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
// 發佈
callAsync(...args) {
/**
* callAsync(arg1, arg2,..., cb)
* 發佈的時候最後一個參數可以是回調函數
* 訂閱的每一個函數的最後一個參數也是一個回調函數,所有的訂閱函數執行完
* 且都調用了最後一個函數,才會執行cb
*/
const finalCallback = args.pop()
let i = 0
// 將這個作為最後一個參數傳過去,使用的時候選擇性調用
const done = () => {
++i === this.asyncHooks.length && finalCallback()
}
this.asyncHooks.forEach(hook => {
hook(...args, done)
})
}
}
const asyncParallelHook = new AsyncParallelHook('name')
asyncParallelHook.tapAsync('name', (data, done) => {
setTimeout(() => {
console.log('name', data)
done()
}, 2000)
})
asyncParallelHook.tapAsync('age', (data, done) => {
setTimeout(() => {
console.log('age', data)
done()
}, 3000)
})
console.time('time')
asyncParallelHook.callAsync('qiqingfu', () => {
console.log('監聽函數都調用了 done')
console.timeEnd('time')
})
列印結果
name qiqingfu
age qiqingfu
監聽函數都調用了 done
time: 3002.691ms
AsyncParalleBailHook
暫時不理解
AsyncSeriesHook
非同步串列鉤子類, 不關心 callback
的參數。非同步函數一個一個的執行,但是必須調用 done函數。
class AsyncSeriesHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0
const done = () => {
let task = this.asyncHooks[i++]
task ? task(...args, done) : finalCallback()
}
done()
}
}
const asyncSeriesHook = new AsyncSeriesHook('name')
asyncSeriesHook.tapAsync('name', (data, done) => {
setTimeout(() => {
console.log('name', data)
done()
}, 1000)
})
asyncSeriesHook.tapAsync('age', (data, done) => {
setTimeout(() => {
console.log('age', data)
done()
}, 2000)
})
console.time('time')
asyncSeriesHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('time')
})
執行結果
name qiqingfu
age qiqingfu
end
time: 3010.915ms
AsyncSeriesBailHook
同步串列鉤子類, callback的參數如果不是 null
, 後面所有的非同步函數都不會執行,直接執行 callAsync
方法的回調函數
class AsyncSeriesBailHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0
const done = data => {
if (data) return finalCallback()
let task = this.asyncHooks[i++]
task ? task(...args, done) : finalCallback()
}
done()
}
}
const asyncSeriesBailHook = new AsyncSeriesBailHook('name')
asyncSeriesBailHook.tapAsync('1', (data, done) => {
setTimeout(() => {
console.log('1', data)
done(null)
}, 1000)
})
asyncSeriesBailHook.tapAsync('2', (data, done) => {
setTimeout(() => {
console.log('2', data)
done(null)
}, 2000)
})
console.time('times')
asyncSeriesBailHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('times')
})
列印結果
1 qiqingfu
2 qiqingfu
end
times: 3012.060ms
AsyncSeriesWaterfallHook
同步串列鉤子類, 上一個監聽函數 callback(err, data)的第二個參數, 可以作為下一個監聽函數的參數
class AsyncSeriesWaterfallHook {
constructor(options) {
this.options = options
this.asyncHooks = []
}
tapAsync(name, callback) {
this.asyncHooks.push(callback)
}
callAsync(...args) {
const finalCallback = args.pop()
let i = 0, once
const done = (err, data) => {
let task = this.asyncHooks[i++]
if (!task) return finalCallback()
if (!once) {
// 只執行一次
task(...args, done)
once = true
} else {
task(data, done)
}
}
done()
}
}
const asyncSeriesWaterfallHook = new AsyncSeriesWaterfallHook('name')
asyncSeriesWaterfallHook.tapAsync('1', (data, done) => {
setTimeout(() => {
console.log('1', data)
done(null, '第一個callback傳遞的參數')
}, 1000)
})
asyncSeriesWaterfallHook.tapAsync('2', (data, done) => {
setTimeout(() => {
console.log('2', data)
done(null)
}, 1000)
})
console.time('timer')
asyncSeriesWaterfallHook.callAsync('qiqingfu', () => {
console.log('end')
console.timeEnd('timer')
})
列印結果
1 qiqingfu
2 第一個callback傳遞的參數
end
timer: 2015.445ms
END
如果理解有誤, 麻煩糾正!
參考文章
webpack 4.0 Tapable 類中的常用鉤子函數源碼分析