原文首發於 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
的相關構建信息(例如獲取 module
的 packge.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
Factory
爲 require("enhanced-resolve").ResolverFactory
,因此此處進入到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
,在觸發完當前插件後,最後經過 doResolve
將 hook
帶入到下一個插件中,實現了遞歸串聯調用一系列的插件。包括: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
的解析:
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-async
包npm, asyncLib.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執行結果)"
}
]
}
複製代碼
在回調裏執行:
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" } }
]
}
複製代碼
接着處理了 inline loader
若是帶有前綴!
,!!
,-!
(注意,這部分的 API
在中文文檔上沒有寫,要在官方原版文檔裏纔有連接)和 result
項帶有 enforce
參數的狀況,用來對 loader
的禁用和排序。
最後獲得 useLoadersPost
, useLoadersPre
, useLoaders
, settings:{type: "javascript/auto", resolve: {}}
,並經過 asyncLib.parallel
與 this.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
。
最後輸出如下組合對象:
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
});
複製代碼
其中:
主要做用是爲該 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
。
主要做用是爲該 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
流程!
resolver
裏,先經過 enhanced-resolve
獲取 resolver
,提供 resolve
方法。inline loader
和 resource
和項目配置的 loader
,而後根據配置對其進行合併,排序。getParser
和 getGenerator
獲得 module
對應的 parser
和 generator
,用於後面的 ast
解析及模板生成。data
。