【webpack】webpack構建流程筆記(六)

前言

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流程寫起。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章