当前位置: 首页 > 工具软件 > Vitest > 使用案例 >

vitest支持cjs的workaround(TypeScript产物commonjs场景)

丌官和泰
2023-12-01

前言

开门见山,首先 vitest 是一个 esm first 的测试工具,所以他的定位应该是 纯 esm / 纯 cjs 的场景,不像 jest 可以通过一些 transform 插件支持混杂场景。

什么是混杂场景,比如我们使用 typescript 编写 esm 格式的 source ,但是把产物编译为 commonjs 格式,最后给 node 调用,这在 Native ESM 普及之前是 node 库的基本操作。

当然现在有 type: "module" 带来了 typescript 编译的 esm 格式包和真正的 Native ESM 包。

我们这里不就 esm 话题展开聊,关键在于这种源码 esm 产物 cjs 是一种 混杂 场景。

这就违背了 vitest 的设计,不在 vitest 的概念内,所以自然无法使用 vitest 。

在混杂场景下,我不建议使用 vitest ,而使用 jest ,但如果非要使用 vitest 怎么办呢?我们有一种 workaround 的方法。

该方法的灵感和现在社区热导入 ts 文件的做法一致,也是 esno / swno / esbuild-jest 等库的底层原理。

底层逻辑剖析

在了解解法前,我们先简单剖析一下 vitest 不支持的底层逻辑。

由于 vitest 在底层使用 vite 的 esbuild 能力 transform ts 文件,所以关键在 esbuild 的配置。

当使用 cjs 作为产物的场景时
// vitest.config.ts

import { defineConfig } from 'vitest/config'

export default defineConfig({
  esbuild: {
    target: 'node14',
    format: 'cjs'
  },
})

当我们把所有 transform 的 format 都定为 cjs 时,这将导致 *.test.ts 转换为 cjs 格式,这就意味着最终 *.test.ts 内会出现 ts 文件的导入:

// before: test file
// example.test.ts
import { method } from './some-file'

// after: transformed result
// example.test.js
const { method } = require('./some-file')

注:以上转换为伪代码描述,并非真实结果。

其中 methodsome-file.ts 文件的一个变量,关键在于 require() 是无法正常导入一个 .ts 文件的,由此将引发:

Error: Cannot find module ‘./some-file’
Require stack:
- …

当使用 esm 作为产物的场景时
// vitest.config.ts

import { defineConfig } from 'vitest/config'

export default defineConfig({
  esbuild: {
    target: 'node14',
  },
})

默认情况 vitest 是 esm 优先,所以我们的 test file *.test.ts 和相关导入的 .ts 文件会原模原样使用 esm 格式来处理,但别忘了我们使用 typescript 的产物是 cjs 格式,最终运行时是 cjs 而不是 esm ,这就导致了两者的概念发生冲突,将导致测试不符合预期,最终失败。

关于此处的错误可能是千人千面的,大多可能和 cjs 的导入报错相关,比如 TypeError: default is not a function 等。

解法

剖析完底层逻辑,我们来看解法。

在上文的剖析中,既然 require() 不能导入 .ts 文件,那么我们让他支持就好了。

在每次启动测试前人为 hook require.extensions 来支持 .ts 文件导入:

// hook.ts

import { EsbuildPhoenix } from '@xn-sakina/phoenix'

// 这个类实现了 hook 对 .ts 文件的 require 导入并进行热转换
new EsbuildPhoenix({
  target: 'es2019'
})

vitest 配置:

// vitest.config.ts

import { defineConfig } from https://github.com/vitest-dev/vitest

export default defineConfig({
  test: {
    setupFiles: ['./hook']
  },
  esbuild: {
    target: 'node14',
    format: 'cjs'
  },
})

由此,我们便可以 workaround 掉这个问题。

进一步思考,还有什么没考虑到的?

  • 性能和 ts-jest / esbuild-jest 等 jest transform 插件实现思路一致,那么速度和 vitest 谁快?

  • hook require 的一个边缘情况是存在 require.cache ,如何应对?

  • 如何应对 .tsx 的 jsx element 场景?

  • 其他 edge case 是否都能无伤应对?

在此解法下,我顺利跑通了两大组非 jsx 测试,还是比较满意的。

但综合来看,仍然存在一些谜点待验证,我们还不能认为他是万能的解法。

总结

Migrating from jest

虽然 vitest 在混合场景可圈可点,但在这一切之前,我们还需要注意 migrating from jest 的方法,关于这一点,官方文档 有所描述,可以看到需要应对的点还是有不少的。

React strategy

值得注意的是,我还是建议在业务 React Project 场景使用 RTL ,因为更成熟,但简单的 libs jsx 场景可以使用 react-test-renderer ,这也是 vitest 推荐的做法。

Use native esm

同时,当你准备好拥抱 native esm 时,vitest 将是你不二的选择,你也可以在官方仓库 examples 找到更多的用例和打开姿势。

 类似资料: