webpack4插件及工做流程

基礎篇能夠參考webpack4基礎node

webpack做者是c#出身,不少代碼是OOP模式,能夠借鑑下webpack

Tapable

webpack4重寫了Tapable, 是webpack的插件組織的核心。它提供給各個插件鉤子,在事件觸發時執行這些掛載的方法。webapck的插件裏必須有apply()方法,當其被調用的時候webpack將鉤子上的方法掛載到各個事件下面有點像nodejsEventEmitter$ongit

class Car {
	constructor() {
		this.hooks = {
			accelerate: new SyncHook(["newSpeed"]),
			brake: new SyncHook(),
			calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
		};
	},
    setSpeed(newSpeed) {
		this.hooks.accelerate.call(newSpeed);
	}
}
複製代碼

如上代碼所示先是在實例化的過程當中註冊了三個鉤子函數,在實例上調用方法時觸發鉤子函數。 下面介紹webpack裏主要的6個Tapable的實例, 它們都繼承了Tapable,定義了一些本身的hookgithub

Compiler

最高層的實例,初始化配置,提供全局性的鉤子好比done, compilation。其餘的Tapable實例須要經過其訪問,如web

compiler.hooks.compilation.tap(
      "myFirstWebpackPlugin",
      (compilation, params) => {
        compilation.hooks.seal.tap()
      }
    );
複製代碼

Compilation

Compiler建立,整個構建就在這裏完成,進行依賴圖構建,優化資源,渲染出runtime時的代碼等。下面的4個實例都是發生在這個階段。json

Resolver

當你請求一個模塊的時候,你將模塊名或者相對地址發給模塊解析器,它會去解析出絕對地址去尋找那個模塊,看是否存在,若是存在則返回相應的模塊信息,包括上下文等。這裏的請求能夠相似網絡請求同樣攜帶上查詢參數之類的,Resolver將會返回額外信息。webpack4裏將Resolver這個實例抽出來單獨發了一個包enhanced-resolve, 抽象出來能夠便於用戶實現本身的Resolverc#

ModuleFactory

模塊工廠就是負責構造模塊的實例,介紹兩種NormalModuleFactoryContextModuleFactory。二者不一樣的地方在於後者用於解析動態import(). 模塊工廠主要是用於將Resolver解析成功的請求裏的源碼從文件中拿出,在內存中建立一個模塊對象(NormalModule)數組

Parser

Parser主要用於將代碼解析成AST抽象語法🌲.能夠在ast查看代碼轉換成AST後的樣子。webpack默認採用acorn解析器,babel是babylonParserModuleFactory返回的對象裏的代碼字符串轉換成AST後進行解析,發現import或者require或者define相似模塊引用時會將這些引用信息也就是依賴添加到當前模塊的對象裏,這樣每一個模塊對象裏不但有本身模塊的信息還包含它的依賴信息。webpack會在不單單會在模塊聲明處觸發事件,它甚至會在解析到變量時也觸發事件。以下在webpack/lib/Parser.js裏能夠看到以下三個鉤子函數瀏覽器

varDeclaration: new HookMap(() => new SyncBailHook(["declaration"])),
    varDeclarationLet: new HookMap(() => new SyncBailHook(["declaration"])),
    varDeclarationConst: new HookMap(() => new SyncBailHook(["declaration"])),
複製代碼

Template

負責生成運行時的代碼babel

// 源碼
// index.js
    var multiply  = require('./multiply')
    var sum = (a,b)=>{
        return a+b;
    }
    module.exports = sum;
// multiply.js
    module.exports = (a, b) => a*b

// 生成的runtime
[
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
        var multiply  = __webpack_require__(1)
        var sum = (a,b)=>{
            return a+b;
        }
        module.exports = sum;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
        module.exports = (a, b) => a*b
/***/ })
];
複製代碼

如上面代碼所示,裏面包含三個模板,分別負責chunk, module, dependency. chunk是包含多個模塊的數組,就是外面數組的形式;module就是裏面用當即執行函數包圍的部分;dependency就是將原先import, require等引用模塊部分轉換成 __webpack_require__.

工做流程

目前只看到模塊構建那部分,後續再補充╮(╯_╰)╭。。。心得就是利用好vscode的調試工具多打斷點~~

介紹完了這六個實例,下面大體講下webpack的工做流程,webpack作的工做很是多,這裏只挑主要的講下。括號裏的是源碼所在的文件位置,上下文是node_modules/webpack. 本流程基於webpack4.30.0版本。

  1. 首先是將配置文件讀入,webpack4有默認配置options = new WebpackOptionsDefaulter().process(options);會以用戶的配置爲先。Compiler進行建立compiler = new Compiler(options.context);將配置裏的plugin部分進行綁定調用
for (const plugin of options.plugins) {
        if (typeof plugin === "function") {
            plugin.call(compiler, compiler);
        } else {
            plugin.apply(compiler);
        }
    }
複製代碼

進行其餘配置設置compiler.options = new WebpackOptionsApply().process(options, compiler); (lib/webpack.js) 2. 接着根據打包的目標(web, node, electron等)生成不一樣的打包模板

switch (options.target) {
        case "web":
            JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
            ...
            break;
        case "webworker":
            ...
複製代碼

由於瀏覽器端請求異步加載的模塊會相似jsonp插入dom中<script>標籤,而好比node端是沒有dom結構的。

new EntryOptionPlugin().apply(compiler);
    compiler.hooks.entryOption.call(options.context, options.entry);
複製代碼

這部分是將入口處配置添加調用entryOption鉤子。 (lib/WebpackOptionsApply.js) 3. 根據不一樣接口類型調用不一樣的類,webpack裏處處都是類

// lib/EntryOptionPlugin.js
    if (typeof entry === "string" || Array.isArray(entry)) {
        itemToPlugin(context, entry, "main").apply(compiler);
    } else if (typeof entry === "object") {
        for (const name of Object.keys(entry)) {
            itemToPlugin(context, entry[name], name).apply(compiler);
        }
    } else if (typeof entry === "function") {
        new DynamicEntryPlugin(context, entry).apply(compiler);
    }
複製代碼

這裏舉例是單文件入口, 在compilation鉤子上綁定(即Compiler建立compilation後調用)回調,指定當前依賴的模塊生成方法。

compiler.hooks.compilation.tap(
        "SingleEntryPlugin",
        (compilation, { normalModuleFactory }) => {
            compilation.dependencyFactories.set(
                SingleEntryDependency,
                normalModuleFactory
            );
        }
    );
複製代碼

(lib/SingleEntryPlugin.js) 4. 建立compilation (lib/Compiler.js).

const compilation = this.createCompilation();
複製代碼
// lib/SingleEntryPlugin.js
    compiler.hooks.make.tapAsync(
        "SingleEntryPlugin",
        (compilation, callback) => {
            const { entry, name, context } = this;
            const dep = SingleEntryPlugin.createDependency(entry, name);
            compilation.addEntry(context, dep, name, callback);
        }
    );
複製代碼

上面這段是以前註冊的,可是會在compilation建立完成前調用,是個異步鉤子。compilation建立好後傳入,它會將入口建立一個依賴。 5. 開始執行addEntry()方法,在addEntry方法裏調用_addModuleChain,將當前入口文件建立模塊moduleFactory.create,模塊建立好後處理當前模塊的依賴項this.processModuleDependencies. 將依賴建立模塊後再依次解析模塊的依賴。(lib/Compilation.js)

參考資料

主要仍是Sean的課程 webpack-plugins

contribute-to-webpack

相關文章
相關標籤/搜索