前言
- 這篇講plugin。
- 官網鏈接
Compiler與Compilation
-
compiler 對象代表了完整的 webpack 環境配置。這個對象在啓動 webpack 時被一次性建立,並配置好所有可操作的設置,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件將收到此 compiler 對象的引用。可以使用它來訪問 webpack 的主環境。
-
compilation 對象代表了一次資源版本構建。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,就會創建一個新的 compilation,從而生成一組新的編譯資源。一個 compilation 對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息。compilation 對象也提供了很多關鍵時機的回調,以供插件做自定義處理時選擇使用。
-
神圖:
-
webpack目錄的lib目錄下的compiler文件裏面的compiler對象上寫的hook
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"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
- 這個就跟第一篇的tapable連起來了,不然都不知道這些鉤子是什麼鉤子。
DonePlugin
- 先製作個簡單的plugin:
class DonePlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log(stats)
})
}
}
module.exports = DonePlugin
- 都會有apply方法,然後收到compiler,在compiler裏的hooks就是上面那個hook,可以拿到它的鉤子。
- 可以看見前面那個done是new的asyncSeriesHook,就是串行異步執行鉤子:
let hook = new AsyncSeriesHook(['name', 'age'])
hook.tapAsync('1', (name, age, callback) => {
setTimeout(() => {
console.log(1)
callback()
}, 1000);
})
hook.tapAsync('2', (data, age, callback) => {
setTimeout(() => {
console.log(2)
callback()
}, 1000);
})
hook.tapAsync('3', (data, age, callback) => {
setTimeout(() => {
console.log(3)
callback()
}, 2000);
})
hook.callAsync('yehuozhili', 111, err => {
console.log('執行完成')
})
- 插件直接在webpack裏面new就可以打印出來了,可以發現是個stats對象。
- 這個對象東西太多太多了,主要含有modules、chunks和assets三個屬性值的對象。
- 可以通過下面命令把stats對象輸出出來:
npx webpack --profile --json > stats.json
- 上面那個async鉤子其實應該異步tap,異步tap會有回調函數,必須要調用:
class DonePlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.done.tapAsync('DonePlugin', (stats, callback) => {
console.log(stats)
callback()
})
}
}
module.exports = DonePlugin
AssetsPlugin
- 同樣一個簡單的plugin,就是打印下資源由哪個chunk生成哪個filename:
class AssestPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
compiler.hooks.compilation.tap('yehuozhili', (compliation, params) => {
compliation.hooks.chunkAsset.tap('yehuozhili', (chunk, filename) => {
console.log(`通過${chunk}生成了${filename}`)
})
})
}
}
module.exports = AssestPlugin
ZipPlugin
- 寫個插件用來把打包後的文件做成壓縮包,便於後面部署用。
const jszip = require('jszip')
const { RawSource } = require('webpack-sources')
class ZipPlugin {
constructor(options) {
this.options = options
}
apply(compiler) {
let that = this
compiler.hooks.emit.tapAsync('yehuozhili', (compiltion, callback) => {
let zip = new jszip
for (let filename in compiltion.assets) {
let source = compiltion.assets[filename].source()
zip.file(filename, source)
}
zip.generateAsync({ type: 'nodebuffer' }).then(content => {
compiltion.assets[that.options.filename] = new RawSource(content)
callback()
})
})
}
}
module.exports = ZipPlugin
- 這個是在emit階段,也就是快最後生成階段,通過compiltion.assets獲取文件列表,文件以鍵值對形式,通過值裏的source方法獲取內容,然後通過jszip進行打包。
- 打包完生成文件,需要輸出,給其加上option裏配置的filename,然後使用webpack的RawSource輸出,其實也可以配個對象,只要包含souce和size就行,具體可以點到rawSource裏面看這個類的屬性。
new ZipPlugin({
filename: Date.now() + '.zip'
})
- 先就寫這麼多,基本上這裏算是初步把webpack基礎功能搞完了,plugin要寫的複雜需要對webpack流程有很深認識,所以後面得從webpack流程寫起。