开门见山,首先 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 的配置。
// 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')
注:以上转换为伪代码描述,并非真实结果。
其中 method
是 some-file.ts
文件的一个变量,关键在于 require()
是无法正常导入一个 .ts
文件的,由此将引发:
Error: Cannot find module ‘./some-file’
Require stack:
- …
// 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 测试,还是比较满意的。
但综合来看,仍然存在一些谜点待验证,我们还不能认为他是万能的解法。
虽然 vitest 在混合场景可圈可点,但在这一切之前,我们还需要注意 migrating from jest 的方法,关于这一点,官方文档 有所描述,可以看到需要应对的点还是有不少的。
值得注意的是,我还是建议在业务 React Project 场景使用 RTL ,因为更成熟,但简单的 libs jsx 场景可以使用 react-test-renderer
,这也是 vitest 推荐的做法。
同时,当你准备好拥抱 native esm 时,vitest 将是你不二的选择,你也可以在官方仓库 examples 找到更多的用例和打开姿势。