最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Webpack 中 enhanced-resolve 路径解析流程详解

    正文概述 掘金(滴滴WebApp架构组)   2021-01-18   446

    前言

    webpack 使用 enhanced-resolve 进行路径解析。它的作用类似于一个异步的 require.resolve 方法,将 require / import 语句中引入的字符串,解析为引入文件的绝对路径。

    // 绝对路径
    const moduleA = require('/Users/didi/Desktop/github/test-enhanced-resolve/src/moduleA.js')
    
    // 相对路径
    const moduleB = require('../moduleB.js')
    
    // 模块路径,npm 包名或者是通过 alias 配置的别名
    const moduleC = require('moduleC')
    

    在其官方文档中,将其描述为高度可配置,这得益于它完善的插件系统。事实上,enhanced-resolve 的所有内置功能都是通过插件实现的。

    webpack 如何集成 enhanced-resolve

    在 WebpackOptionsApply.js 中,合并 webpack.config.js 中的 resolve 选项:

    compiler.resolverFactory.hooks.resolveOptions
      .for("normal")
      .tap("WebpackOptionsApply", resolveOptions => {
        return Object.assign(
          {
            fileSystem: compiler.inputFileSystem
          },
          cachedCleverMerge(options.resolve, resolveOptions)
        );
      });
      
    compiler.resolverFactory.hooks.resolveOptions
      .for("context")
      .tap("WebpackOptionsApply", resolveOptions => {
        return Object.assign(
          {
            fileSystem: compiler.inputFileSystem,
            resolveToContext: true
          },
          cachedCleverMerge(options.resolve, resolveOptions)
        );
      });
      
    compiler.resolverFactory.hooks.resolveOptions
      .for("loader")
      .tap("WebpackOptionsApply", resolveOptions => {
        return Object.assign(
          {
            fileSystem: compiler.inputFileSystem
          },
          cachedCleverMerge(options.resolveLoader, resolveOptions)
        );
      });
    

    创建 normalModuleFactory 和 contextModuleFactory 的时候传入 resolverFactory,normalModuleFactory 和 contextModuleFactory 在解析路径时就可以使用 enhanced-resolve 的功能。

    createNormalModuleFactory() {
      const normalModuleFactory = new NormalModuleFactory(
        this.options.context,
        this.resolverFactory,
        this.options.module || {}
      );
      this.hooks.normalModuleFactory.call(normalModuleFactory);
      return normalModuleFactory;
    }
    
    createContextModuleFactory() {
      const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
      this.hooks.contextModuleFactory.call(contextModuleFactory);
      return contextModuleFactory;
    }
    

    用一张图来展示这部分流程:

    Webpack 中 enhanced-resolve 路径解析流程详解

    核心功能

    1、通过同步或异步的方式获取模块的绝对路径,并且可以判断模块是否存在。

    Webpack 中 enhanced-resolve 路径解析流程详解

    create 方法允许我们传入 options,用于自定义解析规则。

    Webpack 中 enhanced-resolve 路径解析流程详解

    2、继承 Tapable,对外暴露自定义插件功能,实现更灵活的模块解析规则。

    3、灵活的自定义文件系统,enhanced-resolve 自带 NodeJsInputFileSystem、CachedInputFileSystem。

    Webpack 中 enhanced-resolve 路径解析流程详解

    路径解析流程

    以 5.4.0 版本为例,enhanced-resolve 的原理可以简单理解成是一个管道pipeline进行解析,从最初的地方传入要解析的路径,经过一个个插件解析,最终返回文件路径或报错。

    在 Resolver.js 中,enhance-resolve 默认只有 4 个 hook:

    class Resolver {
      constructor(fileSystem, options) {
        this.fileSystem = fileSystem;
        this.options = options;
        this.hooks = {
          // 每执行一个插件都会调用
          resolveStep: new SyncHook(["hook", "request"], "resolveStep"),
          // 没有找到具体文件或目录
          noResolve: new SyncHook(["request", "error"], "noResolve"),
          // 开始解析
          resolve: new AsyncSeriesBailHook(
            ["request", "resolveContext"],
            "resolve"
          ),
          // 解析完成
          result: new AsyncSeriesHook(["result", "resolveContext"], "result")
        }
      }
    }
    

    可以看到,与解析流程相关的只有开始resolve结束result两个hook,其余都是在 ResolverFactory.js 中手动加入的。

    resolver.ensureHook("resolve");
    resolver.ensureHook("internalResolve");
    resolver.ensureHook("newInteralResolve");
    resolver.ensureHook("parsedResolve");
    resolver.ensureHook("describedResolve");
    resolver.ensureHook("internal");
    resolver.ensureHook("rawModule");
    resolver.ensureHook("module");
    resolver.ensureHook("resolveAsModule");
    resolver.ensureHook("undescribedResolveInPackage");
    resolver.ensureHook("resolveInPackage");
    resolver.ensureHook("resolveInExistingDirectory");
    resolver.ensureHook("relative");
    resolver.ensureHook("describedRelative");
    resolver.ensureHook("directory");
    resolver.ensureHook("undescribedExistingDirectory");
    resolver.ensureHook("existingDirectory");
    resolver.ensureHook("undescribedRawFile");
    resolver.ensureHook("rawFile");
    resolver.ensureHook("file");
    resolver.ensureHook("finalFile");
    resolver.ensureHook("existingFile");
    resolver.ensureHook("resolved");
    

    enhanced-resolve 允许我们通过传入配置和编写插件的形式非常灵活的自定义路径解析方式,以上 hooks 除了 resolve 和 result 两个钩子是固定的在开始和结束时被调用,其余的 hooks 可能没有固定的执行顺序

    对于以下 demo 来说,hook 调用顺序是固定的:

    const { CachedInputFileSystem, ResolverFactory } = require('enhanced-resolve')
    const path = require('path')
    
    const myResolver = ResolverFactory.createResolver({
      fileSystem: new CachedInputFileSystem(fs, 4000),
      extensions: ['.json', '.js', '.ts'],
      // ...更多配置
    })
    
    const context = {}
    const resolveContext = {}
    const lookupStartPath = path.resolve(__dirname)
    const request= './a'
    myResolver.resolve(context, lookupStartPath, request, resolveContext, (err, path, result) => {
    	if (err) {
        console.log('createResolve err: ', err)
      } else {
        console.log('createResolve path: ', path)
      }
    });
    

    在 ResultPlugin.js 中 debugger 看以上 demo 在解析路径的过程中调用了哪些 hooks:

    Webpack 中 enhanced-resolve 路径解析流程详解

    以上 demo 调用 myResolver.resolve 时,在 resolve 方法内部主动调用了 doResolve 方法,并且使用 resolve 钩子。

    class Resolver {
      resolve (context, path, request, resolveContext, callback) {
        // ...
        if (resolveContext.log) {
          const parentLog = resolveContext.log;
          const log = [];
          return this.doResolve(
            // ----------- 这里 -----------
            this.hooks.resolve,
            obj,
            message,
            {
              log: msg => {
                parentLog(msg);
                log.push(msg);
              },
              fileDependencies: resolveContext.fileDependencies,
              contextDependencies: resolveContext.contextDependencies,
              missingDependencies: resolveContext.missingDependencies,
              stack: resolveContext.stack
            },
            (err, result) => {
              if (err) return callback(err);
    
              if (result) return finishResolved(result);
    
              return finishWithoutResolve(log);
            }
          );
        } else {
          // ...
        }
      }
    }
    

    enhanced-resolve 通过不同的配置来初始化不同的插件,在插件内部注册一个 hook,然后使用 doResolve 方法调用下一个 hook 将整个解析流程串联起来。

    Webpack 中 enhanced-resolve 路径解析流程详解

    插件编写方式:

    一个插件依赖三个信息:

    1、上游hook:上一个 hook 处理完信息,轮到我来接着处理,所以需要注册一个 hook 的 tap。

    2、配置信息:在该 plugin 处理逻辑时,用到的参数。

    3、下游hook:该 plugin 处理完逻辑时,通知下游 hook, 来 call 它注册的tap。

    class ResolvePlugin {
      constructor (source, option, target) {
        this.source = source // 当前插件挂在哪个钩子下
        this.target = target // 触发的下一个钩子
        this.option = option
      }
      
      apply (resolver) {
        const target = resolver.ensureHook(this.target)
        
        resolver.getHook(this.source).tapAsync('ResolvePlugin', (request, resolveContext, callback) => {
          const resource = request.request
          const resourceExt = path.extname(request.request)
          const obj = Object.assign({}, request, {})
          const message = null
    
          // 触发下一个钩子
          resolver.doResolve(target, obj, message, resolveContext, callback)
        })
      }
    }
    

    在插件中可以自定义下一个要触发的钩子,所以 hooks 可能没有固定的执行顺序

    Mpx 通过 enhanced-resolve 插件实现文件维度的条件编译

    在团队自研的增强型跨端小程序框架 Mpx 中,也有对于enhanced-resolve的应用。

    Mpx 支持以微信小程序语法为基础,通过读取用户传入的 mode 和 srcMode 来构建输出其他平台的小程序代码。但是不同平台的部分组件或 API 可能差异比较大,通过简单的 if / else 无法抹平差异。例如在滴滴出行小程序中微信转支付宝的项目中存在一个业务地图组件map.mpx,由于微信和支付宝中的原生地图组件标准差异非常大,无法通过框架转译方式直接进行跨平台输出,这时我们可以在相同的位置新建一个 map.ali.mpx,在其中使用支付宝的技术标准进行开发,编译系统会根据当前编译的 mode 来加载对应模块,当 mode 为ali时,会优先加载 map.ali.mpx,反之则会加载 map.mpx。

    其原理就是通过自定义插件 AddModePlugin 实现对不同 mode 文件的优先匹配加载。

    总结

    webpack 使用 enhanced-resolve 模块进行路径解析,它是一个高度可配置的 require.resolve 路径解析器,使用对外暴露的选项和插件,实现自定义的路径查找规则。


    起源地下载网 » Webpack 中 enhanced-resolve 路径解析流程详解

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元