webpack 4 源碼主流程分析(三):編譯前的準備

原文首發於 blog.flqin.com。若有錯誤,請聯繫筆者。分析碼字不易,轉載請代表出處,謝謝!webpack

Compiler 簡述

  • webpack/lib/Compiler.js 該文件是 webpack 的核心, Compiler 類定義了整個構建的流程,然後文的 Compilation 類則負責如何具體去構建;
  • new Compiler 執行 constructor,首先擴展了 Tapable,在 constructor 裏定義了一堆鉤子 done,beforeRun,run,emit 等等;
  • 而後註冊了this._pluginCompat.tap("Compiler"),這個用來兼容以前的老版 webpackplugin 的鉤子,觸發時機在tapable/lib/Tapable.js裏調用plugin 的時候;
  • Compiler 類的 run 即爲整個打包的主流程函數;

封裝 FS

繼續執行 webpack.js,執行:web

new NodeEnvironmentPlugin({
  infrastructureLogging: options.infrastructureLogging
}).apply(compiler);
複製代碼

該類主要對文件系統作了一些封裝,包括輸入,輸出,緩存,監聽等等,這些擴展後的方法所有掛載在 compiler 對象下。json

註冊 plugins

項目配置的 plugins

而後對本身 config 文件裏的 plugins 進行了註冊:數組

if (options.plugins && Array.isArray(options.plugins)) {
  for (const plugin of options.plugins) {
    if (typeof plugin === 'function') {
      plugin.call(compiler, compiler);
    } else {
      plugin.apply(compiler);
    }
  }
}
複製代碼

在這裏,會把 compiler 實例傳進去供 plugin 使用,compiler 包含整個構建流程的所有鉤子,經過它能夠把控整個 webpack 構建週期。緩存

掌握流程裏各對象(如 compilercompilation)的事件鉤子觸發的時機,就是掌握如何寫一個插件的關鍵。如何寫一個 webpack 插件?app

接着觸發了 compilerhooks: environment,afterEnvironment,而後執行:函數

compiler.options = new WebpackOptionsApply().process(options, compiler);
複製代碼

項目默認的 plugins

WebpackOptionsApply 類的 process 除了把配置裏的一些屬性添加到 compiler 對象下,更主要的是根據 options 的配置不一樣,註冊激活一些默認自帶的插件和 resolverFactory.hooks,大部分插件的做用是往 compiler.hooks:compilation,thisCompilation 裏註冊一些事件(此時該鉤子已經獲取到 normalModuleFactory 等參數),如:ui

new JavascriptModulesPlugin().apply(compiler); //給normalModuleFactory的js模塊提供Parser、JavascriptGenerator對象 ,並給seal階段的template提供renderManifest數組(包含render方法)
new JsonModulesPlugin().apply(compiler); //給normalModuleFactory的json模塊提供Parser、JavascriptGenerator對象
new WebAssemblyModulesPlugin({
  mangleImports: options.optimization.mangleWasmImports
}).apply(compiler); // 同理,webassembly模塊
複製代碼
new EntryOptionPlugin().apply(compiler);
compiler.hooks.entryOption.call(options.context, options.entry); //建立多入口仍是單入口 SingleEntryPlugin | MultiEntryPlugin,二者均會在 apply 方法裏註冊 Compiler.hooks:compilation, make
複製代碼

插件處理完畢後,觸發 compiler.hooks: afterPluginsthis

註冊 resolverFactory.hooks

compiler.resolverFactory.hooks.resolveOptions.for('normal').tap('WebpackOptionsApply', resolveOptions => {
  return Object.assign(
    {
      fileSystem: compiler.inputFileSystem
    },
    cachedCleverMerge(options.resolve, resolveOptions) //配置項 options.resolve
  );
});
compiler.resolverFactory.hooks.resolveOptions.for('context').tap('WebpackOptionsApply', resolveOptions => {
  return Object.assign(
    {
      fileSystem: compiler.inputFileSystem,
      resolveToContext: true
    },
    cachedCleverMerge(options.resolve, resolveOptions) //配置項 options.resolve
  );
});
compiler.resolverFactory.hooks.resolveOptions.for('loader').tap('WebpackOptionsApply', resolveOptions => {
  return Object.assign(
    {
      fileSystem: compiler.inputFileSystem
    },
    cachedCleverMerge(options.resolveLoader, resolveOptions) //配置項 options.resolveLoader
  );
});
複製代碼

而後註冊 compiler.resolverFactory.hooks: resolveOptions for (normal/context/loader),目的是爲 Factory.createResolver 提供默認的參數對象(含有相關的 resolve 項目配置項)。spa

註冊完成後,觸發 compiler.hooks: afterResolvers,到此 compiler 初始化完畢。

回到 cli.js

回到cli.js,處理配置項 progressinfoVerbosity,而後判斷 options 裏是否有 watch,有則走 compiler.watch,無則走 compiler.run,這裏咱們走compiler.run,進入 webpack 核心構建流程!

本章小結

  1. 實例化了 Compiler,它擴展於Tapable,是 webapck 的核心;
  2. 封裝了輸入輸出等方法 FS ,而後註冊了 plugins,包括項目配置的和項目默認的;
  3. 註冊 resolverFactory.hooks 用於 Factory.createResolver 方法提供參數對象;
  4. 最後根據配置是否有 watch 來決定程序走向。
相關文章
相關標籤/搜索