涨薪面技:如何写 webpack 路径解析插件(3)

258 阅读5分钟

一、前文回顾

从今天这篇小作文开始我们系统的学习 webpack 内部有关 resolve 的有关配置项目,本文包含以下配置:

  1. alias: 设置路径别名;
  2. aliasFields:设置别名字段;
  3. conditionalName:条件名;
  4. descriptionFiles:描述文件列表;
  5. enforceExtension:强制拓展名;
  6. extensionAlias:扩展名别名;

今天这篇小作文继续学习 webpack 有关 resolve 和 resolveLoader 的相关配置和 enhanced-resolve 内部的实现信息!

二、resovle 配置项

在 webpack 中,我们通过 webpack.config.js.resolve、resolverLoader 两个配置对象,其中 resolve 属性用于配置常规的模块的解析器行为,resolveLoader 用于设置 loader 的解析器行为。

2.1 resolve.extensions

这个玩意大家也不陌生,用于为不写扩展名的路径 request 尝试添加扩展名,注意这个配置存在优先级,拍在前面的更先解析,如果解析到则不会再解析剩下的;

module.exports = {
  //...
  resolve: {
    extensions: ['.js', '.json']
  },
};

比如你写了以下代码:

import a from 'some/dir/a'

ehanced-resolve 会尝试优先解析 some/dir/a.js,如果解析到则返回结果,如果没有才会再尝试解析 some/dir/a.json;

2.2 resolve.fallback

当正常解析失败时,重定向模块请求。这个玩意儿在 webpack5 中十分有用,这是因为 webpack 在v4及以前是自动 pollyfill Node.js 的核心模块的,但是在 v5 放弃了这个特性。

因此,如果你项目中需要这些 Node.js 的特性模块,你需要手动指定,此时用到了这个特性。

module.exports = {
  //...
  resolve: {
    fallback: {
      assert: require.resolve('assert'),
      buffer: require.resolve('buffer'),
      console: require.resolve('console-browserify'),
      constants: require.resolve('constants-browserify'),
      zlib: require.resolve('browserify-zlib'),
      // .....
    },
  },
};

enhanced-resolve 中由 AliasPlugin.js 插件是现在这个特性;

2.3 resolve.mainFields

当从 npm 包中导入模块时(例如,import * as D3 from 'd3'),mainFields 字段决定使用 package.json 中哪个字段声明的模块。

根据 webpack 配置中指定的 target 不同,默认值也会有所不同。

module.exports = {
  //...
  resolve: {
    mainFields: ['browser', 'module', 'main'],
  },
};

比如,target 为 web 时,优先取用 browser 声明的模块;该特性由 MainFieldPlugin 插件实现;

2.4 resolve.mainFiles

request 写目录时自动映射到目录下的兜底文件,一般都是 index.js。这个特性也是 enhanced-resolve 实现的,当然可以通过这个声明修改;

module.exports = {
  //...
  resolve: {
    mainFiles: ['nb'],
  },
};

这样,当你写 import a from 'a/' 时,就不会解析 a/index.js 了,而是解析 a/nb.js。 该特性由 UseFilePlugin 插件实现,后面讲插件时咱们展开讲这部分;

2.5 resolve.exportsFields

解析某一个包内的模块请求的,这个玩意儿可以改写 main 字段,还可以支持 some-pkg/sub/pkg 这种解析的映射。

module.exports = {
  //...
  resolve: {
    exportsFields: ['exports', 'myCompanyExports'],
  },
};

该特性由 SelfReferencePlugin 插件实现;

2.6 resolve.modules

告知 webpack 解析模块的目录,默认就是 node_modules。如果手动指定这样的一个目录,则你指定的这个目录的优先级高于默认的 node_modules 目录。

该特性内部有 ModulesInHierarchicalDirectoriesPlugin 和 PnpPlugin 插件实现;

2.7 resolve.unsafeCache

缓存表示,若设置为 true 则默认缓存解析到的模块结果。该特性内部由 UnsafeCachePlugin 插件实现。

2.8 resolve.useSyncFileSystemCalls

该选项置为 true 则使用同步的文件系统调用,一般我们不会这么干!

2.9 resolve.plugins

用户创建的 Resolver 插件,和 webpack 插件系统一样,也是一个有 apply 的对象;resolver 的插件自然是订阅 resolver 的各种钩子,然后接入到整个 resolve 的过程中的。

在 Resolver 创建时,需要等到 Resolver 完成内部的 hook 注册之后应用用户定义的插件。

2.10 ### resolve.preferRelative

当启用此选项时,webpack 更倾向于将模块 request 理解析为相对于当前目录的相对请求,而不是去 node_modules 目录下查找。

module.exports = {
  //...
  resolve: {
    preferRelative: true,
  },
};

比如我们导入 vue

import Vue from 'vue'

启用 preferRelative 之后,'vue' 等效于 './vue'。当然,一般我们并不需要这个特性。在 enhanced-resolve 内部由 JoinRequestPlugin 插件实现该特性。

2.11 resolve.preferAbsolute

启用该特性时,解析时 偏好的的绝对路径为 resolve.roots 选项指定的目录。

同样也是由 JoinRequestPlugin 实现,一般不会用这个玩意儿。

2.12 resolve.symlinks

是否把解析到的符号链接(软链)解析为其真正的路径,若设置,则会将符号链接解析到源路径。

2.13 resolve.cachePredicate

缓存的断言函数,用于决定一个 request 是否应该被缓存,函数返回 true 标识则缓存;

module.exports = {
  //...
  resolve: {
    cachePredicate: ({ path, request }) => {
      // additional logic
      return true;
    },
  },
};

注意,该函数必须返回一个布尔值!

2.14 resolve.restrictions

解析限制条件数组,用于限制被解析的 request 符合一定的规则。

module.exports = {
  //...
  resolve: {
    restrictions: [/.(sass|scss|css)$/],
  },
};

比如说我们想限制被解析的 request 需要是 CSS 样式文件,就可以加入这些限制,该特性在 enhanced-reesolve 内部由 RestrictionsPlugin 插件实现。

2.15 resolve.roots

该选项对应一个目录数组,用于解析服务器相对 URL(以“/”开头),默认为 context 配置选项。在非 Windows 系统上,这些请求首先被解析为绝对路径。

2.16 resolve.importsFields

一个字段,用于提供给 某些包的内部 request(以 # 开头的叫做内部请求)。这个值可坑爹了,但是没啥卵用。后面会详细介绍这个东东。

注意,这个选项在 webpack 中文网站上有错误,该字段的值不是 [browser, module, node]

module.exports = {
  //...
  resolve: {
    importsFields: ['imports'],
  },
};

那你的包的 package.json 就应该声明这个内容:

    {
      "name": "qiang-sheng-group",
      "main": "./lib/gaoqiqiang.js",
      "imports": {
        "#xiaohu": "./lib/tangxiaohu.js",
        "#xiaolong": "./lib/tangxiaolong.js"
      }
    }

该特性由 ImportsFieldPlugin 插件实现!

2.17 resolve.byDependency

通过 module 的 request 类型来配置 resolve 选项,比如我们可以有以下方式:

module.exports = {
  // ...
  resolve: {
    byDependency: {
      // ...
      esm: { // request 是 ESM
        mainFields: ['browser', 'module'],
      },
      commonjs: { // request 是 CommonJS
        aliasFields: ['browser'],
      },

    },
  },
};

三、总结

算上前面的一篇介绍 resolve 配置项的文章,一共两篇文章介绍了 webpack.config.js.resolve 和 resolveLoader 选项对象的各个配置项及其作用,期间我们还穿插了一些 ehanced-resolve 的内部实现细节。

下一篇我们正式进入到 createResolver 的重点环节 —— 流水线钩子的注册!