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

触发 NormalModuleFactory.hooks:factory 和 resolver

接上文,开始 module 构建前的 reslove 流程。目的是为了获取个 module 的相关信息及各依赖的 loader 的绝对路径。

来到 NormalModuleFactory.js 文件,该 create 方法先触发了 normalModuleFactory.hooks:beforeResolve,然后在回调里执行:

const factory = this.hooks.factory.call(null);
factory(result, (err, module) => {
  //...
});
复制代码

触发 NormalModuleFactory.hooks:factory,该事件返回了一个 factory 函数。接着执行该 factory 函数:

let resolver = this.hooks.resolver.call(null);
resolver(result, (err, data) => {
  //... 创建一个 `normalModule` 实例
});
复制代码

触发 NormalModuleFactory.hooks:resolver 该事件返回了一个 resolver 函数。接着执行 resolver 函数,该函数作用为解析构建所有 module 所需要的 loaders 的绝对路径及这个 module 的相关构建信息(例如获取 module 的 packge.json 等。

在函数里执行:

const loaderResolver = this.getResolver('loader');
const normalResolver = this.getResolver('normal', data.resolveOptions);
复制代码
  • loaderResolver 为用于解析 loader 的绝对路径

  • normalResolver 用于解析 文件 和 module 的绝对路径

getResolver

this.getResolver 会执行 webpack/lib/ResolverFactory.js 里的 this.resolverFactory.get。方法里判断缓存后,执行 _create:

resolveOptions = this.hooks.resolveOptions.for(type).call(resolveOptions);
const resolver = Factory.createResolver(resolveOptions);
复制代码

此时触发的 ResolverFactory.hooks: resolveOptions for (type) 即在 编译前的准备 - 注册 resolverFactory.hooks 阶段 所注册。

在该钩子里通过 cachedCleverMerge 判断缓存及融合配置(如果是 loaderResolver 则为 配置项: options.resolveLoader,如果是 normalResolver 则为 配置项: options.resolve),并添加了属性 fileSystem: compiler.inputFileSystem,返回一个 resolveOptions 对象,作为Factory.createResolver 执行的参数。

Factory 为 require("enhanced-resolve").ResolverFactory,所以此处进入到enhanced-resolve 包的阶段。

enhanced-resolve

enhanced-resolve 是 webpack 开发的一个用于解析路径的包。进入文件 node_modules/enhanced-resolve/lib/ResolverFactory.js,先融合处理了项目配置 resolve 与默认配置 resolve/resolveLoader,然后执行:

if (!resolver) {
  resolver = newResolver(useSyncFileSystemCalls ? newSyncAsyncFileSystemDecorator(fileSystem) : fileSystem);
}
复制代码

如果没有传入项目的 resolver,那么就自己 new 一个。接着定义了 Resolver 的生命周期钩子和根据配置 push 了一大堆的 plugins,然后执行:

plugins.forEach((plugin) => {
  plugin.apply(resolver);
});
复制代码

对每一个插件执行 apply,主要作用是获取到 hooks 后,在 Resolver 的不同生命周期钩子上注册了一些事件,然后在事件末尾执行:

// 获取hooksconst 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

继续执行,先进行 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.parallelAPI 文档 会并行处理参数数组各任务,任务都完成之后,返回一个 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执行结果)"}]}复制代码

解析配置文件里的 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.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。

得到 data

最后通过 process.nextTick(微任务)异步输出以下组合对象:

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. resolve 流程用于获得各 loader 和模块的绝对路径等信息。

  1. 在 resolver 里,先通过 enhanced-resolve 获取 resolver,提供 resolve 方法。

  1. 解析 inline loader 和 resource 和项目配置的 loader,然后根据配置对其进行合并,排序。

  1. 调用 getParser 和 getGenerator 得到 module 对应的 parser 和 generator,用于后面的 ast 解析及模板生成。

  1. 最后输出一个组合对象 data, 该对象为创建 module 提供各种必备的环境条件。

作者:Korey

链接:https://juejin.cn/post/6844904047225339912

来源:稀土掘金

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值