webpack 4 源碼主流程分析(五):reslove 流程

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

觸發 NormalModuleFactory.hooks:factory

接上文,來到 NormalModuleFactory.js 文件,該 create 先觸發了 normalModuleFactory.hooks:beforeResolve,而後在回調裏執行:css

const factory = this.hooks.factory.call(null);
factory(result, (err, module) => {
  //...
});
複製代碼

觸發 NormalModuleFactory.hooks:factory,該事件返回了一個 factory 函數。接着執行該 factory 函數:html

let resolver = this.hooks.resolver.call(null);
resolver(result, (err, data) => {
  //...
});
複製代碼

factory 函數觸發 NormalModuleFactory.hooks:resolver 後,最終在 resolver 的回調裏建立一個 normalModule 實例。java

觸發 NormalModuleFactory.hooks:resolver

觸發 NormalModuleFactory.hooks:resolver 該事件返回了一個 resolver 函數。node

執行 resolver 函數,該函數做用爲解析構建全部 module 所須要的 loaders 的絕對路徑及這個 module 的相關構建信息(例如獲取 modulepackge.json 等。webpack

在函數裏執行:git

const loaderResolver = this.getResolver('loader');
const normalResolver = this.getResolver('normal', data.resolveOptions);
複製代碼
  • loaderResolver 爲用於解析 loader 的絕對路徑
  • normalResolver 用於解析 文件module 的絕對路徑

this.getResolver 會執行 resolverFactory.get,判斷緩存後,即執行 webpack/lib/ResolverFactory.js 裏的 _create,執行:github

resolveOptions = this.hooks.resolveOptions.for(type).call(resolveOptions);
const resolver = Factory.createResolver(resolveOptions);
複製代碼

此時觸發的 ResolverFactory.hooks: resolveOptions for (type) 即在 編譯前的準備 - 註冊 resolverFactory.hooks 階段 所註冊。web

在該鉤子裏經過 cachedCleverMerge 判斷緩存及融合配置(若是是 loaderResolver 則爲 配置項: options.resolveLoader,若是是 normalResolver 則爲 配置項: options.resolve),並添加了屬性 fileSystem: compiler.inputFileSystem,返回一個 resolveOptions 對象,做爲Factory.createResolver 執行的參數。npm

Factoryrequire("enhanced-resolve").ResolverFactory,因此此處進入到enhanced-resolve 包的階段。

enhanced-resolve

執行 Factory.createResolver(resolveOptions),進入文件 node_modules/enhanced-resolve/lib/ResolverFactory.js,先融合處理了項目配置 resolve 與默認配置 resolve/resolveLoader,而後執行:

if (!resolver) {
  resolver = new Resolver(useSyncFileSystemCalls ? new SyncAsyncFileSystemDecorator(fileSystem) : fileSystem);
}
複製代碼

若是沒有傳入項目的 resolver,那麼就本身 new 一個。接着定義了 Resolver 的生命週期鉤子和根據配置 push 了一大堆的 plugins,而後執行:

plugins.forEach(plugin => {
  plugin.apply(resolver);
});
複製代碼

對每個插件執行 apply,主要做用是獲取到 hooks 後,在 Resolver 的不一樣生命週期鉤子上註冊了一些事件,而後在事件末尾執行:

// 獲取hooks
const target = resolver.ensureHook(this.target);
// 觸發插件後的回調裏,執行:
 resolver.doResolve(target, obj, ...);
複製代碼

target 爲事件鉤子 hook,在觸發完當前插件後,最後經過 doResolvehook 帶入到下一個插件中,實現了遞歸串聯調用一系列的插件。包括:UnsafeCachePlugin,ParsePlugin,DescriptionFilePlugin,NextPlugin,AliasPlugin,AliasFieldPlugin,ModuleKindPlugin,SymlinkPlugin 等等,完成各自的插件操做。

註冊事件完成後,最後獲得返回 resolver 對象回到 _create 觸發 ResolverFactory.hooks: resolver for (type),此處能夠對 resolver 進行篡改。而後返回對應的 resolver 回到 NormalModuleFactory.hooks: resolver 的鉤子函數裏繼續執行。

resolver 對象暴露 resolve 方法,用於解析路徑。

解析 inline loader 和 resource

繼續執行,先進行 inline loader 和對應資源文件 resource 的解析:

let elements = requestWithoutMatchResource
  .replace(/^-?!+/, '')
  .replace(/!!+/g, '!')
  .split('!');
let resource = elements.pop();
elements = elements.map(identToLoaderRequest);
複製代碼

'import Styles from style-loader!css-loader?modules!./styles.css',會獲得:

{
  "resource": "./styles.css",
  "elements": [
    {
      "loader": "style-loader"
    },
    {
      "loader": "css-loader",
      "options": "modules"
    }
  ]
}
複製代碼

而後執行:

asyncLib.parallel(
  [
    callback => this.resolveRequestArray(contextInfo, context, elements, loaderResolver, callback), // 解析`elements`(`inline loader`)
    callback => {
      //...
      normalResolver.resolve(contextInfo, context, resource, {}, (err, resource, resourceResolveData) => {...}); // //解析對應的 `module` 的絕對路徑等信息
    }
  ],
  (err, results) => {
    //...
  }
);
複製代碼

asyncLib 來自 neo-asyncnpmasyncLib.parallel API 文檔 會並行處理參數數組各任務,任務都完成以後,返回一個 results 列表,列表順序爲參數數組順序,與執行順序無關。

this.resolveRequestArray 內部採用 asyncLib.map 循環調用 resolver.resolve

獲得 results

{
  "results": [
    [
      {
        "loader": "loader的絕對路徑1",
        "options": "loader參數1"
      },
      {
        "loader": "loader的絕對路徑2",
        "options": "loader參數2"
      }
    ],
    {
      "resource": "模塊絕對路徑",
      "resourceResolveData": "模塊基本信息(即enhanced-resolve執行結果)"
    }
  ]
}
複製代碼

解析 config module rules 裏的 loader

在回調裏執行:

const result = this.ruleSet.exec({
  resource: resourcePath,
  realResource: matchResource !== undefined ? resource.replace(/\?.*/, '') : resourcePath,
  resourceQuery, // module 路徑上所帶的 query 參數
  issuer: contextInfo.issuer, // 所解析的 module 的發佈者
  compiler: contextInfo.compiler
});
複製代碼

exec(上一章已經介紹過)過濾 webpack.config.js 中獲得 module 所須要的 loader

{
  "result": [
    { "type": "type", "value": "javascript/auto" },
    { "type": "resolve", "value": {} },
    { "type": "use", "value": { "loader": "babel-loader" } }
  ]
}
複製代碼

合併,排序 loader

接着處理了 inline loader 若是帶有前綴!,!!,-!(注意,這部分的 API 在中文文檔上沒有寫,要在官方原版文檔裏纔有連接)和 result 項帶有 enforce 參數的狀況,用來對 loader的禁用和排序。

最後獲得 useLoadersPost, useLoadersPre, useLoaders, settings:{type: "javascript/auto", resolve: {}},並經過 asyncLib.parallelthis.resolveRequestArray 並行處理 useLoadersPost, useLoadersPre, useLoaders 獲得對應的 resolve 結果即路徑信息,在回調裏執行:

if (matchResource === undefined) {
  loaders = results[0].concat(loaders, results[1], results[2]); //參數 loaders 爲inline loader
} else {
  loaders = results[0].concat(results[1], loaders, results[2]);
}
複製代碼

排序、合併 loader,即 loaders 順序爲 postLoader,inlineLoader,loader(normal config loader),preLoader。由於 loader 是從右至左執行,即執行順序爲 preLoader,loader(normal config loader),inlineLoader,postLoader

獲得 data

最後輸出如下組合對象:

callback(null, {
  context: context,
  request: loaders
    .map(loaderToIdent)
    .concat([resource])
    .join('!'),
  dependencies: data.dependencies,
  userRequest,
  rawRequest: request,
  loaders,
  resource,
  matchResource,
  resourceResolveData,
  settings,
  type,
  parser: this.getParser(type, settings.parser),
  generator: this.getGenerator(type, settings.generator),
  resolveOptions
});
複製代碼

其中:

getParser

主要做用是爲該 module 提供 parser,用於解析模塊爲 ast

this.getParser(type, settings.parser) 建立 parser 並緩存。

執行 createParser,方法裏觸發 NormalModuleFactory.hooks:createParser for (type),該事件註冊在 JavascriptModulesPlugin 插件,根據 type 不一樣返回不一樣的 parser 實例。

實例化以後,觸發 NormalModuleFactory.hooks:parser for (type),會去註冊一些在 parser 階段(遍歷解析 ast 的時候)被觸發的 hooks

getGenerator

主要做用是爲該 module 提供 generator,用於模版生成時提供方法。

parser 相似,this.getGenerator(type, settings.generator) 建立 generator 並緩存。

執行 createGenerator,方法裏觸發 NormalModuleFactory.hooks:createGenerator for (type),該事件註冊在 JavascriptModulesPlugin 插件,根據 type 不一樣返回不一樣的 generator 實例(目前代碼裏都是返的一致的 new JavascriptGenerator() )。

實例化以後,觸發 NormalModuleFactory.hooks:generator for (type)

獲得這個組合對象 data 後,跳出 resolver 函數,執行 resolver 函數回調,到此 resolve 流程結束,開啓建立 module 流程!

本章小結

  1. resolver 裏,先經過 enhanced-resolve 獲取 resolver,提供 resolve 方法。
  2. 解析 inline loaderresource 和項目配置的 loader,而後根據配置對其進行合併,排序。
  3. 調用 getParsergetGenerator 獲得 module 對應的 parsergenerator,用於後面的 ast 解析及模板生成。
  4. 最後輸出一個組合對象 data
相關文章
相關標籤/搜索