如何在 TypeScript + ESM + Mocha + tsconfig-paths 下运行 Mocha 测试用例

作者 happyWang 日期 2024-02-29 Views
如何在 TypeScript + ESM + Mocha + tsconfig-paths 下运行 Mocha 测试用例

我有一个基于 TypeScript 的项目,使用 Mocha 来运行测试用例,一直都正常工资,直到我将项目改成了 ESM 模块,然后就报错了。

在一番搜索之后,我发现了解决方案,记录一下。

TLDR:
使用 tsx 来替代 ts-node 作为 TypeScript 的运行时,就可以解决所有问题,而且性能更好。

现在让我们一步一步的来看看如何解决这个问题。

在开始之前,我们也许要声明一下,以下的所有问题和解决方案,都是基于这些相关库的特定版本的,在未来的版本中,可能会有所不同。

{
  "mocha": "^10.1.0",
  "ts-node": "^10.9.2",
  "tsconfig-paths": "^4.2.0",
  "tsx": "^4.7.1"
}

官方初始示例

Mocha 的官方仓库里面提供了一个项目支持 Typescript 的示例:https://github.com/mochajs/mocha-examples/tree/master/packages/typescript

这个示例实现了 CommonJs 下的 TypeScript 支持,示例里面的 .mocharc.json 配置如下:

{
  "extension": ["ts"],
  "spec": "src/**/*.spec.ts",
  "require": "ts-node/register"
}

require 配置了 ts-node/register,这个配置是用来在运行测试用例的时候,在 nodejs 调用时执行类似 node -r ts-node/register 的命令。从而让 ts-node 扩展了 nodejs 的模块加载器,使得 nodejs 可以直接加载 TypeScript 文件。

node -r <module> 是 nodejs 的一个命令行参数,用来在启动 nodejs 时加载一个模块。

通过这种方式,我们可以直接在 nodejs 中加载 TypeScript 文件,而不需要先编译成 JavaScript 文件。

这是我基于官方示例配置的一个样例项目:https://stackblitz.com/edit/node-qidqck?file=src%2Fbasic.ts&view=editor

但是上述有一个问题就是不支持我们在 tsconfig.json 里面配置的 paths,会报类似 Error: Cannot find module ‘@utils/add’ 的错误。

因为我们的 ts-node/register 只是扩展了 nodejs 的模块加载起,使得 TypeScript 文件在引用时,会被 ts-node 编译成 JavaScript 文件,然后再加载。但是并没有处理 import 的路径

这也是为什么错误是 Cannot find module 的原因了。

这个时候我们就需要 https://www.npmjs.com/package/tsconfig-paths 来做这一个功能。

tsconfig-paths

我们调整刚刚的 .mocharc.json 配置,改成如下:

{
  "extension": ["ts"],
  "spec": "src/**/*.spec.ts",
  "require": ["ts-node/register", "tsconfig-paths/register"]
}

require 可以是一个 string[],这样我们就可以在启动 nodejs 时,先加载 ts-node/register,然后再加载 tsconfig-paths/register

tsconfig-paths/register 会读取 tsconfig.json 里面的 paths 配置,然后在加载模块时,会根据 paths 配置来解析模块的路径。

同样,我也搭建了一个样例项目:https://stackblitz.com/edit/node-wnq7ku?file=.mocharc.json&view=editor

ESM

但是,当我们的项目启用了 ESM 模块时,又有新的错误发生了:TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for

为什么 .ts 后缀的文件会无法识别呢?

因为开启 esm 之后,ts-node/register 就无法正常工作了,这个时候需要使用 ts-node/esm 来替代 ts-node/register

如果我们的项目是 Typescript + ESM 的话,我们就可以用如下配置:

{
  "extension": ["ts"],
  "spec": "src/**/*.spec.ts",
  "loader": "ts-node/esm"
}

这里面 loader,是等同于在 nodejs 启动时,执行 node --loader ts-node/esm 或者 node --import ts-node/esm 的效果。

但是我们还是遇到最开始的问题,ts-node 无法处理 paths 的问题。

只是这次我们遇到另外一个问题,tsconfig-paths/register 也无法正常工作了。tsconfig-paths/register 目前只支持 CommonJs 模块。

事情到这一步似乎陷入僵局了。

tsx

这个时候,我们的 tsx 出场了。

tsx 是对 nodejs 的强化,使之可以支持 ESM 模块,同时也支持 TypeScript。

按照官方的说明,它有这些特性:

  • Blazing fast on-demand TypeScript & ESM compilation
  • Works in both CommonJS and ESM packages
  • Supports next-gen TypeScript extensions (.cts & .mts)
  • Hides experimental feature warnings
  • TypeScript REPL
  • Resolves tsconfig.json paths

这就很好的满足了我们在 ESM 模块下,同时支持 TypeScript 和 paths 的需求。

基于 tsx,我们的 mocha 配置就可以改成如下:

{
  "extension": ["ts"],
  "spec": "src/**/*.spec.ts",
  "import": "tsx"
}

同样的,这里提供了一个样例项目:https://stackblitz.com/edit/node-xexp1j?file=.mocharc.json&view=editor

但是上述配置适用于Node.js v20.6.0及以上版本,如果你的项目需要支持更低版本的 Node.js 那么配置需要调整为:

{
  "extension": ["ts"],
  "spec": "src/**/*.spec.ts",
  "loader": "tsx"
}

原因是在 Node.js v20.6.0 之后,Node.js 新增了 --import 参数用来在 Node.js 启动前配置 register

具体变更参考:https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V20.md#new-nodemodule-api-register-for-module-customization-hooks-new-initialize-hook