原文首发于 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
。