一、前文回顾
本文详细介绍了 webpack.config.js.resolve 和 resolveLoader 选项对象的各个配置项及其作用。
下一篇我们正式进入到 createResolver 的重点环节 ------ 流水线钩子的注册!
二、enhanced-resolve 与构建流程
在 NormaoModuleFactory 模块构建过程中,参与模块路径的解析,将解析所得的 loader 和模块路径交 给后续的构建流程。
2.1 getResolver
NormalModuleFactory.prototype.getResolver 方法,调用 this.resolverFactory.get 方法
kotlin
class NormalModuleFactory {
// ....
getResolver(type, resolveOptions) {
return this.resolverFactory.get(type, resolveOptions);
}
}
2.2 webpack/lib/ResolverFactory.get
该方法来自 webpack/lib/ResolverFactory.js 的静态方法:
js
class ResolverFactory {
get(type, resolveOptions = EMPTY_RESOLVE_OPTIONS) {
let typedCaches = this.cache.get(type);
if (!typedCaches) {
typedCaches = {
direct: new WeakMap(),
stringified: new Map()
};
this.cache.set(type, typedCaches);
}
// 获取缓存
const cachedResolver = typedCaches.direct.get(resolveOptions);
if (cachedResolver) {
// 命中缓存
return cachedResolver;
}
const ident = JSON.stringify(resolveOptions);
const resolver = typedCaches.stringified.get(ident);
if (resolver) {
typedCaches.direct.set(resolveOptions, resolver);
return resolver;
}
// 无缓存创建新的 resolver
const newResolver = this._create(type, resolveOptions);
typedCaches.direct.set(resolveOptions, newResolver);
typedCaches.stringified.set(ident, newResolver);
return newResolver;
}
_create () {
const originalResolveOptions = { ...resolveOptionsWithDepType };
const resolveOptions = convertToResolveOptions(
this.hooks.resolveOptions.for(type).call(resolveOptionsWithDepType)
);
const resolver = (
// 新建 Resolver
Factory.createResolver(resolveOptions)
);
const childCache = new WeakMap();
resolver.withOptions = options => {
const cacheEntry = childCache.get(options);
if (cacheEntry !== undefined) return cacheEntry;
const mergedOptions = cachedCleverMerge(originalResolveOptions, options);
const resolver = this.get(type, mergedOptions);
childCache.set(options, resolver);
return resolver;
};
this.hooks.resolver
.for(type)
.call(resolver, resolveOptions, originalResolveOptions);
return resolver;
}
}
webpack/lib/ResolverFactory.js 的 _create
方法调用了 enhanced-resolve/lib/ResolverFactory.js 的 createResolver 方法:
三、流水线注册
3.1 // resolve: 开始 resolve
如果启用 unsafeCache
注册则在 new-resolve
和 new-internal-resolve
source 钩子注册两个插件:UnsafeCachePlugin、ParsePlugin 插件,这两个插件的 target 都是 parsed-resolve
;如果没未启用,则为 resolve
和 internal-resolve
source 钩子注册 ParsePlugin,这两个钩子的 target 钩子都是 parsed-resolve
。webpack.config.js.resolve.unsafeCache 传送门,缓存一切!
3.2 // parsed-resolve: request 解析阶段
为 parsed-resolve
source 钩子注册 DescriptionFilePlugin
插件,该插件的 target 钩子为 described-resolve
注册 NextPlugin,引导从 described-resolved
source 钩子到 raw-resolve
target 钩子;
3.3 // described-resolve 描述文件已解析
如果 fallback.length 不为0,fallback 的作用是当解析失败时的解析重定向,详细配置 resolve.fallback 配置传送门。具体实现:为 described-resolved
source 钩子注册 AliasPlugin,其 target 钩子为 internal-resolve
, 这个 internal-resolve
在前面 【4】中注册了 ParsePlugin,这就说明这个执行流程被退回到了 internal-resolve
阶段。啥意思嘞,很简单,alias 被分析后需要重新解析,重新走流程;
3.4 // raw-resolve: 原始解析阶段
- 如果 alias.length 不为 0,说明配置了 resolve.alias 选项。则为
raw-resolve
source 钩子注册 AliasPlguin,其 target 钩子为internal-resolve
; - 遍历
aliasFields
配置,该配置源数据来自 webpack.cofig.js.resolve.aliasFields 字段,表示的 resolve.aliasFields 传送门 ,这个玩意儿是当 webpack 构建的代码产物运行于浏览器
环境时告知 resolver 解析模块过程中需要查找的 package.json 中代表浏览器入口文件的标识字段。有点拗口了。。。我也组织好几次才说出来。。。就是说:有的 npm 包里面的某些文件不能直接在浏览器运行,而 npm 包的作者提供了另一个文件专门用在浏览器环境,npm 包作者通过在它包里面的 package.json.browser 字段告诉你这个浏览器版本的文件是哪个,相当于官方给你 hack 好了的。这种情况下需要 enhanced-resolver 专门处理。处理插件就是 AliasFieldPlugin 插件,该插件注册的 source 钩子raw-resolve
,target 钩子internal-resolve
钩子。 - 遍历
extensionAlias
,给每个 extensionAlias 注册 ExtensionAliasPlugin 插件,该插件的 source 钩子raw-resolve
,target 钩子是normal-resolve
。extensionAlias 是扩展名的别名映射。上webpack.config.js.resolve.extensionAlias 传送门 - 注册 NextPlugin 插件,引导从
raw-resolve
source 钩子到normal-resolve
target 钩子。
三、总结
受限于篇幅,这里并没有完全完成整个流水线的注册工作,之所以不在一篇写完也是不想给各位读者带来过大的压力!
我们大致回故一下从今天的流水线内容:
- resolve 开始解析:注册 UnsafeCachePlugin、ParsePlugin 插件;
- paresed-resolve:request 解析阶段;
- described-resolve 描述文件已解析;
- raw-resolve: 原始解析阶段,处理 alias/aliasFields/extension/extensionAlias;