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

一、前文回顾

本文详细介绍了 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-resolvenew-internal-resolve source 钩子注册两个插件:UnsafeCachePlugin、ParsePlugin 插件,这两个插件的 target 都是 parsed-resolve;如果没未启用,则为 resolveinternal-resolve source 钩子注册 ParsePlugin,这两个钩子的 target 钩子都是 parsed-resolvewebpack.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: 原始解析阶段

  1. 如果 alias.length 不为 0,说明配置了 resolve.alias 选项。则为 raw-resolve source 钩子注册 AliasPlguin,其 target 钩子为 internal-resolve
  2. 遍历 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 钩子。
  3. 遍历 extensionAlias,给每个 extensionAlias 注册 ExtensionAliasPlugin 插件,该插件的 source 钩子 raw-resolve,target 钩子是 normal-resolve。extensionAlias 是扩展名的别名映射。上webpack.config.js.resolve.extensionAlias 传送门
  4. 注册 NextPlugin 插件,引导从 raw-resolve source 钩子到 normal-resolve target 钩子。

三、总结

受限于篇幅,这里并没有完全完成整个流水线的注册工作,之所以不在一篇写完也是不想给各位读者带来过大的压力!

我们大致回故一下从今天的流水线内容:

  1. resolve 开始解析:注册 UnsafeCachePlugin、ParsePlugin 插件;
  2. paresed-resolve:request 解析阶段;
  3. described-resolve 描述文件已解析;
  4. raw-resolve: 原始解析阶段,处理 alias/aliasFields/extension/extensionAlias;
相关推荐
漂流瓶jz12 分钟前
Webpack如何实现万物皆可import?loader的使用/配置/手写实践
前端·javascript·webpack
ZC跨境爬虫28 分钟前
跟着 MDN 学CSS day_41:显式轨道、隐式网格与区域命名放置
前端·javascript·css·ui·交互
笑尘~Y2 小时前
每日技术面试高频题精选
面试
拼尽全力前进2 小时前
Guava Cache vs Caffeine 面试详解
面试·职场和发展·guava
Moment2 小时前
长上下文会最终杀死 Rag 吗?
前端·javascript·后端
kyriewen4 小时前
大文件上传最全指南:分片、断点续传、秒传,一篇就够了
前端·javascript·面试
我叫黑大帅4 小时前
解决聊天页内部滚轮改为页面滚动问题
javascript·后端·面试
新酱爱学习5 小时前
手搓 10 个 Skill 后,我把重复劳动收敛成了一套零依赖 CLI 工具
前端·javascript·人工智能
罗超驿5 小时前
13.JavaScript 新手入门指南:语法、变量、流程控制全解析
开发语言·javascript
ct9785 小时前
Three.js 性能优化(测量-定位-优化)
javascript·性能优化·three