首页
/ Strapi 共享 Vitest 配置包 vitest-config:unitPreset 原理与 Jest 到 Vitest 的渐进式迁移实践

Strapi 共享 Vitest 配置包 vitest-config:unitPreset 原理与 Jest 到 Vitest 的渐进式迁移实践

2026-09-06 14:50:05作者:胡唯隽

Strapi 官方仓库正在用 Vitest 逐步接管后端单元测试,而 packages/utils/vitest-config 就是支撑这一迁移的私有共享配置包。本篇以该包的 README 及其预设源码为骨架,完整讲解 unitPreset 每一项参数的含义、各包接入该预设的标准姿势,以及 Strapi 如何通过 .vitest.test.ts 命名约定让 Jest 与 Vitest 两套测试框架在同一 monorepo 中互不干扰地并存运行。

一、包定位:一个明确不对外发布的内部工具包

vitest-config 的定义非常克制。从其 package.json 可以确认以下事实:

  • 包名为 vitest-config"private": true,版本 5.52.2(与 monorepo 其余包保持同步的版本策略);
  • 对外仅暴露一个入口:./presets/unit,同时映射类型与实现到 ./src/presets/unit.ts
  • peerDependencies 要求使用方必须自行安装 vitest ^4.0.0,配置包本身不锁定具体运行时版本(devDependencies 中通过 "vitest": "catalog:" 引用仓库统一的 Yarn catalog 版本);
  • engines 约束为 Node.js >=20.0.0 <=26.x.x、npm >=6.0.0,与仓库根 package.json 的全局 engines 约束一致。

README 开头用 IMPORTANT 提示框明确声明:这是一个私有包,不打算被 Strapi monorepo 之外的用户使用。因此下文所有用法都应以“在 Strapi 仓库内接入新测试”为适用前提,外部项目参考其思路时应自行落地等价配置。

二、unitPreset 源码逐项解析

预设的完整实现只有 32 行,位于 src/presets/unit.ts,通过 defineConfig 导出为 unitPreset

import { defineConfig } from 'vitest/config';

export const unitPreset = defineConfig({
  test: {
    // Require explicit imports from 'vitest' (no globals)
    globals: false,

    // Node environment for backend unit tests
    environment: 'node',

    // Only run tests with .vitest.test.ts suffix for incremental migration
    include: ['**/*.vitest.test.ts'],

    // Exclude common non-test files and directories
    exclude: [
      '**/node_modules/**',
      '**/dist/**',
      '**/.cache/**',
      '**/*.testdata.{js,ts}',
      '**/*.test.utils.{js,ts}',
      '**/*.d.ts',
      '**/__tests__/resources/**',
      '**/tests/resources/**',
    ],

    // Match Jest's default timeout
    testTimeout: 5000,

    // Watch mode settings
    watch: false,
  },
});

逐项说明其设计意图:

参数 取值 作用与设计考量
globals false 禁用 Vitest 全局注入,强制测试文件显式 import { describe, it, expect } from 'vitest'。好处是依赖关系在文件层面可见、易被静态检查发现
environment 'node' 该预设服务于后端单元测试(server 侧代码),运行在 Node 环境而非 jsdom
include ['**/*.vitest.test.ts'] 这是渐进式迁移的关键:只收集带 .vitest.test.ts 后缀的测试文件,源码注释也写明是 "for incremental migration"。普通 *.test.ts 文件仍归 Jest 管辖,两套框架按文件后缀自动分流
exclude 8 条 glob 排除 node_modulesdist.cache、类型声明文件 *.d.ts,以及两类“看起来像测试但并非测试”的文件:*.testdata.{js,ts}(测试数据)与 *.test.utils.{js,ts}(测试工具函数);同时排除 __tests__/resources/tests/resources/ 资源目录
testTimeout 5000 源码注释明确写着 "Match Jest's default timeout"——刻意对齐 Jest 的默认 5 秒超时,避免迁移到 Vitest 后原有测试因超时行为变化而失败
watch false 预设层面默认关闭 watch,保证 CI 等场景下的确定行为;需要监听模式时由上层脚本显式开启(见第四节)

值得注意的是,exclude 列表中的 *.testdata.{js,ts}*.test.utils.{js,ts}__tests__/resources 等规则与 Jest 侧的忽略规则(见下文 jest-preset.unit.js)几乎一一对应,可以推断这套预设是在把既有 Jest 的过滤口径平移到 Vitest 上,从而保证同一批“伪测试文件”在两套框架下都不被误收集。

该包的 tsconfig.json 继承自 packages/utils/tsconfig/base.json,仅声明 "types": ["node"] 并包含 src 目录,说明预设本身是纯 TypeScript 源码导出,不做独立构建。

三、标准接入姿势:mergeConfig 合并预设

README 给出的 Usage 示例(也是全仓库 16 个包实际采用的统一模板):

import { defineConfig, mergeConfig } from 'vitest/config';
import { unitPreset } from 'vitest-config/presets/unit';

export default mergeConfig(
  unitPreset,
  defineConfig({
    test: {
      root: __dirname,
    },
  })
);

要点拆解:

  1. 导入路径是子路径导出 vitest-config/presets/unit,对应 package.jsonexports 字段;包名没有 @strapi/ 前缀,是因为它属于内部工具包(AGENTS.mdpackages/utils/ 描述为 "Shared tooling: logger, eslint-config, tsconfig, vitest-config")。
  2. mergeConfig(unitPreset, defineConfig(...)) 的顺序:共享预设在前,包级覆盖在后。后一个配置中出现的同名字段会覆盖预设值。
  3. 包级仅需补充 root: __dirname:把测试根目录锚定到各包自身目录,使每个包的 vitest.config.ts 成为相互独立的测试工程,互不串扰。

仓库中已经落地该模板的配置文件均与 README 示例逐字一致,例如 packages/core/utils/vitest.config.tspackages/core/database/vitest.config.tspackages/providers/upload-local/vitest.config.tspackages/plugins/sentry/vitest.config.ts 等,全文如下(以 database 包为例):

import { defineConfig, mergeConfig } from 'vitest/config';
import { unitPreset } from 'vitest-config/presets/unit';

export default mergeConfig(
  unitPreset,
  defineConfig({
    test: {
      root: __dirname,
    },
  })
);

当前仓库中接入 vitest-config/presets/unit 的包共 16 个,覆盖核心层、Provider 层与插件层:

每个接入包的 package.json 还会声明对 vitest-config 的精确版本依赖(如 "vitest-config": "5.52.2")与 "vitest": "catalog:",并在其 tsconfig.jsoninclude 中加入 vitest.config.ts(例如 packages/core/core/tsconfig.json),保证配置文件本身也纳入类型检查。

四、运行入口:根工程级 projects 模式与包级脚本

monorepo 根目录的 vitest.config.ts 只有 7 行:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    projects: ['packages/**/vitest*.config.*'],
  },
});

这里使用 Vitest 的 projects(多工程)模式,通过 glob 一次性发现 packages/ 下所有 vitest*.config.* 文件,把 16 个包级配置聚合成一次运行。对应根 package.json 的两条脚本:

"test:unit:vitest": "vitest run",
"test:unit:vitest:watch": "vitest --watch"
  • 一次性执行(CI 友好):yarn test:unit:vitest,即 vitest run,此时预设中 watch: false 的默认值与一次性运行语义一致;
  • 本地开发监听:yarn test:unit:vitest:watch,即 vitest --watch,用命令行标志显式覆盖预设里的 watch: false,这解释了预设为什么要显式写死 watch: false——它是 run--watch 两种入口的公共基线。

包级别同样提供对称脚本(见 packages/cli/cloud/package.json):test:unit:vitesttest:unit:vitest:watch,允许单独对某一个包的 Vitest 用例做开发迭代。

五、渐进式迁移机制:后缀约定让 Jest 与 Vitest 并存

unitPresetinclude: ['**/*.vitest.test.ts'] 并非孤立的命名偏好,它与 Jest 侧配置构成一对镜像约束,共同实现“按文件逐个迁移、两套框架零冲突”:

  1. Vitest 侧include 只认 .vitest.test.ts 后缀,Jest 存量测试文件(*.test.ts__tests__/* 等)对 Vitest 完全不可见;
  2. Jest 侧:仓库根的 jest-preset.unit.jstestPathIgnorePatterns 中显式加入了 '.vitest.test.ts',源码注释写着 "Prevent Jest from running Vitest test files",防止 Jest 误跑新框架的测试;同时 modulePathIgnorePatternstestMatch 等规则维持原有 Jest 口径;
  3. 命名约定:迁移一个测试文件时,只需把文件名从 xxx.test.ts 重命名为 xxx.vitest.test.ts,并将 describe/it/expect 改为从 vitest 显式导入(因为预设强制 globals: false),该文件便自动从 Jest 流水线切换到 Vitest 流水线,其余文件不受影响。

从源码结构看,这套约定目前已在 43 个测试文件中落地,分布于 packages/core/database/src/fields/tests/string.vitest.test.tspackages/core/content-type-builder/server/src/services/schema-builder/tests/content-type-builder.vitest.test.tspackages/cli/cloud/src/create-project/utils/tests/get-project-name-from-pkg.vitest.test.ts 等位置,均保持“源码旁 __tests__ 目录”的既有组织方式,与 Jest 测试同目录共存。

这种“后缀即路由”的方案对大型 monorepo 的测试框架换代尤其实用:迁移粒度细化到单个文件,任何一步都可以随时停下,而不是一次性改写全仓库。

六、适用前提与使用边界小结

  • 版本前提vitest ^4.0.0(peer 依赖);Node.js >=20.0.0 <=26.x.x;仓库统一通过 Yarn 4(packageManager: yarn@4.12.0)的 workspace 与 catalog 机制管理依赖。
  • 范围边界:该包为私有内部包,仅暴露 ./presets/unit 一个子路径;README 明确声明不建议在 Strapi monorepo 之外使用。
  • 预设语义边界unitPreset 面向后端 Node 单元测试;仓库中前端单元测试仍走 Jest front 预设体系,二者不要混用。
  • 可复制要点:即便不能直接安装该包,仓库外的项目也可以照搬其三个核心手法——共享预设 + mergeConfig 包级覆盖、testTimeout 对齐旧框架默认值、以文件后缀约定实现测试框架的渐进式共存。
登录后查看全文
热门项目推荐
相关项目推荐