涨薪面技:写个 enhanced-resolve 插件(7)

114 阅读4分钟

一、前文总结

本文接着上文的流水线注册环节讲述后续的流水线注册:

  1. module 阶段:处理 @xx 类似这种带有命名空间的内部 request,将其变成一个绝对路径;
  2. resolve-as-module:实际上就是 request 只写到文件夹,这个时候如果 resolveToContext 选项为 false 的时候则将当前 request 作为模块解析;
  3. undescribed-resolve-in-package:重新尝试读取 path 下的 package.json 文件;
  4. resolve-in-package:处理 exportsFields 字段;
  5. resolve-in-existing-directory:在已经存在的路径下解析,构造可能的 request;
  6. relative:注册 DescriptionFilePlugin 插件;
  7. described-relative:处理 resolveToContext 选项成立时,则将解析历程指向 directory 钩子;
  8. directory:注册 DirectoryExistsPlugin 插件,判断路径是否存在;

二、directory 后的流水线注册

3.15 // undescribed-existing-directory

根据是否有 resolveToContext 配置行为不同:

3.15.1 有 resolveToContext

注册 NextPlugin 插件,source 钩子:undescribed-existing-directory,target 钩子:resolved。??到 resolved 不就结束了????

3.15.2 否则,注册以下插件:

  1. DescriptionFilePlugin 插件:source 钩子:undescribed-existing-directory,target 钩子:existing-directory;再次解析当前 path 下的 package.json 文件;

  2. 遍历 mainFiles 字段,给每个 mainFiles 注册 UseFilePlugin 插件,source 钩子:undescribed-existing-directory,target 钩子:undescribed-raw-file,(webpack.config.js.resolve.mainFiles 传送门)[webpack.js.org/configurati…] ,其作用是告知webpack 解析为目录时取用的默认文件,比如 import s from 'src',会被解析成 src/index.js 后续还有几个阶段,2.19 开始。

3.16 described-existing-directory

  1. 遍历 mainFields 字段,注册 MainFieldPlugin 插件,source 钩子:existing-directory,target 钩子:resolve-in-existing-directory;该插件作用是把包名和它的主入口文件模块拼接起来得到包的解析结果。
  2. 遍历 mainFiles 字段,给每个 mainFiles 注册 UseFilePlugin 插件,source 钩子:existing-directory,target 钩子:undescribed-raw-file,(webpack.config.js.resolve.mainFiles 传送门)[webpack.js.org/configurati…] ,其作用是告知webpack 解析为目录时取用的默认文件,比如 import s from 'src',会被解析成 src/index.js。

3.17 // undescribed-raw-file

  1. 再注册 DescriptionFilePlugin 插件,source 钩子:undescribed-raw-file,target 钩子:raw-file;再次尝试解析 package.json
  2. 再注册 NextPlugin 插件,source 钩子:after-undescribed-raw-file,target 钩子:raw-file

3.18 // raw-file

  1. 再次注册 ConditionalPlugin,source 钩子:raw-file,target 钩子:file。条件:{ fullySpecified: true }
  2. 再判断 !enforceExtension,是否未强制扩展名,如果为 true 注册 TryNextPlugin 插件,source 钩子:raw-file,target 钩子:file
  3. 遍历 extension,给每个 extension 注册一个 AppendPlugin,source 钩子:raw-file,target 钩子:file;给 path 和 relativePath 追加各个可能得扩展名

3.19 // file

  1. 判断 alias.length 不为空,所谓 alias 就是路径别名,这个大家最常用的配置。如果不为空就再次注册 AliasPlugin,source 钩子 file,target 钩子为 internal-resolve;该插件的作用不再赘述。
  2. 遍历 aliasFields 字段,为没给 aliasField 注册 AliasFiledPlugin 插件,source 钩子:file,target 钩子 internal-resolve。该插件前面叙述过,不再赘述。
  3. 注册 NextPlugin,source 钩子:file,target 钩子:final-file;

3.20 // final-file

注册 FileExistsPlugin,source 钩子:final-file,target 钩子:existing-file ;该插件作用是通过 fs.stat 检查 rquest.path 对应的文件是否存在,如果不是就加入到 resolveContext.missingDependencies,否则加入 resolveContext.fileDependencies 中。

3.21 // existing-file

判断 symlinks 选项,默认 true。其作用开启时会把 symlink(软链接)解析成其对应的真实路径,上 (webpack.config.js.resolve.symlinks 传送门)[webpack.js.org/configurati…] 默认 true,会注册以下插件:

  1. 注册 SymlinkPlguin,source 钩子:existing-file,target 钩子:existing-file 本身,像个递归;
  2. 注册 NextPlugin,souce 钩子:existing-file,target 钩子:resolve。

3.22 // resolved

  1. 判断 restrictions 不为空,注册 RestrictionsPlugin,source 钩子:resolver.hooks.resolved,无 target 钩子。restrictions 是一组限制条件,最后限制解析所得的 path 符合规则,上 (webpack.config.js.resolve.restrictions 传送门)[webpack.js.org/configurati… RestrictionsPlugin 的作用就是在验证是否符合条件,不符合条件就报错了。

  2. 注册 ResultPlugin,source 钩子:resolver.hooks.resolved,无 target 钩子,这个钩子就是最后一个了。

四、总结

结合上一篇的内容,我们完成了 ehanced-resolve 内部的有关各个流水线的注册。本篇完成的是后半段的部部分:

  1. directory:注册 DirectoryExistsPlugin 插件;
  2. undescribed-existing-directory:根据有无 resolveToContext 配置注册不同插件;
  3. described-existing-directory:注册 MainFieldPlugin 和 UseFilePlugin;
  4. raw-file:处理扩展名相关,尝试匹配;
  5. file:处理 alias
  6. final-file:处理各种 dependencies;
  7. resolved:处理 restrictions 配置,符合就返回否则报错;

我对 webpack 的 enhanced-resolve 存在了相当长的误会,并不理解为什么这么干,其实它在做一种很完备的路径解析工作,它及支持 ESM 也支持 CommonJS。

另外,其设计的 hook 系统是 pipeline 形式的,有别于 webpack 的,这一点大家还是需要注意的!