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

4 阅读4分钟

一、前文回顾

上文详细讲述了 webpack 中的 resolver 的由来及作用:

  1. webpack resolver 用于解析webpack 构建过程中的 request 对应的 模块及模块要对应的 loader 的资源路径用的,分为 normal resolver 和 loader resolver 两种类型;

  2. NMF.prototype.getResolver 负责获取 resolver,核心是通过 ResolverFactory.prototype.get 方法实现;

  3. 接着我们讨论了 ResolverFactory 类型的构造函数,重点在于声明钩子:

    • 3.1 resolveOptions: 用于修改定制 resolver 行为的配置对象;
    • 3.2 resolver:用于修改 resolver 默认行为;
  4. 详细讨论了 ResolverFactory.prototype.get 方法,内部会优先取用 typedCaches.direct/stringify 缓存,有则返回,没有则调用它 ResolverFactory.prototype._create 方法创建;

  5. ResolverFactory.prototype._create 方法内部主要是触发在 WebpackOptionsApply 中配置的 对应 type 的 resolver 选项对象,然后调用 ehanced-resolve 库提供的 Factory.create 方法创建 resolver;接着触发 resolverFactory.hooks.resolver 修改 resolver 的行为并且举例 ResolverCachePlugin;

以上是有关 webpack 中的 Resolver 相关的基础知识, resolve 是 webpack 中相当重要的一个组成部分,后面的几篇文章我们重点研究这个对象。今天这篇我们要研究 webpack 有关 resolver 的配置部分。

二、resolve 配置项和 resolver

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

二者的配置子项相同,这里我们以 resolve 的配置为例!

2.1 resolve.alias

这个配置大家很常用,一般在开发中用于简化模块的导入路径,比如我们 src/components/ 目录,为了简化我们把他设置为 @:

const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      @: path.resolve(__dirname, 'src/components/')
    },
  },
};

经过这个后,我们可以用如下的方式进行导入:

import someText from '@/someText.vue'

该特性由 enhanced-resolve 库中的 AliasPlugin 实现!

2.2resolve.aliasFields

译为"别名字段",也就是用着个指定某个构建目标对应的入口。当然这个特性也是由 AlisPlugin 实现的。

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

指定一个字段,例如 browser,根据 此规范进行解析。

我从这个规范中摘抄了一段原文:

The browser field is provided by a module author as a hint to javascript bundlers or component tools when packaging modules for client side use.

啥意思嘞?我翻译一波:

browser 字段是由模块的作者提供的,用作指示打包工具(js bundler)或者组件工具所构建产物为客户端使用时,使用该字段指向的模块;(该模块是用在浏览器环境的)

有点拗口是不是,具体的我们后面再介绍 AliasPlugin 时还会再行展开!

2.3 resolve.conditionNames

这个有点意思,但是对于没怎么写过 npm 包或者 SDK 的同学来说有点陌生,这个配置相当于给大家实现了一个条件,最常见的就是区分 ESModule 和 CommonJS;

exports 配置项 (定义一个库的入口)的 conditionName。

我从 node 文档上摘抄了一段:

Conditional exports provide a way to map to different paths depending on certain conditions. They are supported for both CommonJS and ES module imports.

翻译一下就是:conditionalExports 提供了一种根据不同条件映射到不同路径的方式。支持 CommonJS 和 ESM 规范;

比如说,我们有一个包想提供一个给 ESModule 的导出,这个时候我们的 package.json 中要加入这段声明:

// package.json
{
  "exports": {
    "import": "./index-module.js",
    "require": "./index-require.cjs"
  }
}

注意,以上是 Node.js 原生提供的能力,那对于 webpack 而言,也是需要拥有这个能力的,这个工作当然就需要 enhanced-resolve 来完成,因此有了这个配置:

module.exports = {
  //...
  resolve: {
    conditionNames: ['require', 'import'],
  },
};

当然,要注意,webpack 这个是有优先级的,如 requrire 写在前面,则优先匹配 exports.require;

2.4 resolve.descriptionFiles

这个不新鲜,描述文件,这个一般都是 package.json ,一般也不会去制定它。如果你有这方面的诉求,比如你写了一个 .someDescFile 这种,需要 enhanced-resolve 识别可以设置一下;

2.5 resolve.enforceExtension

如果设置为 true,强制必须写扩展名,不允许写这种:

import a from 'a';

2.6resolve.extensionAlias

一个将拓展名与拓展名别名映射的对象。

module.exports = {
  //...
  resolve: {
    extensionAlias: {
      '.js': ['.ts', '.js'],
      '.mjs': ['.mts', '.mjs'],
    },
  },
};

把 .ts 和 .js 都当 .js 解析;该特性由 ehanced-resolve 内部的 ExtensionAliasPlugin.js 实现。

三、总结

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

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