Vitest Test Projects 完全指南:用 projects 配置管理 Monorepo 与多环境测试
发布时间:2026/9/14 10:23:22 作者:尧图编辑部 阅读量:1,286

Vitest Test Projects 完全指南用 projects 配置管理 Monorepo 与多环境测试【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitestVitest 的 Test Projects测试项目特性允许你在单个 Vitest 进程中定义多份项目配置是 monorepo 场景下统一管理测试、或为同一代码库运行不同配置如resolve.alias、plugins、test.browser的首选方案。读完本文你将掌握test.projects的三种定义方式glob、配置文件、内联对象、继承与合并规则、--project过滤、嵌套项目Nested Projects以及项目解析调试技巧并了解其背后的源码实现原理。::: tip 该功能此前被称为workspace自 Vitest 3.2 起workspace已被弃用并替换为projects配置二者在功能上完全等价。迁移时只需把test.workspace改名为test.projects参见 docs/guide/examples/projects-workspace.md 中的对照示例。 :::定义 Projects基础用法glob 模式引用目录在根配置文件中声明test.projects其值为一个TestProjectConfiguration 数组类型见 docs/config/projects.md默认值为[]。数组元素可以是内联配置、配置文件路径或 glob 通配模式import { defineConfig } from vitest/config export default defineConfig({ test: { projects: [packages/*], }, })当你的项目结构是packages目录下包含多个子包时Vitest 会把packages下的每一个文件夹都当作一个独立项目——即使该文件夹内没有配置文件也可以正常工作。这正是仓库自带示例 examples/projects/vitest.config.ts 的用法其目录结构为packages/client与packages/server两个独立项目各自拥有自己的vitest.config.ts、测试文件和package.json。文件型项目的命名校验规则如果某个项目条目解析为文件无论来自 glob 还是直接路径Vitest 会校验文件名必须满足以下任一条件以vitest.config或vite.config开头例如vitest.config.unit.ts或匹配vitest.name.config.*/vite.name.config.*其中name只能包含字母、数字、_和-。以下是合法的配置文件名称示例vitest.config.tsvite.config.jsvitest.unit.config.tsvitest.e2e-node.config.tsvite.e2e.config.jsvitest.config.unit.jsvite.config.e2e.js该校验规则在源码中由正则CONFIG_REGEXP /^vite(?:st)?(?:\.[\w-])?\.config\./实现位于 packages/vitest/src/node/projects/resolveProjects.ts。若 glob 匹配到的文件不符合该命名规范或直接引用了不存在的文件/目录Vitest 会抛出明确错误。排除目录否定模式使用!前缀的否定模式可以排除指定目录或文件import { defineConfig } from vitest/config export default defineConfig({ test: { // include all folders inside packages except excluded projects: [ packages/*, !packages/excluded ], }, })嵌套目录结构花括号模式当目录存在嵌套结构、部分文件夹需要作为项目、而部分文件夹自身又含有子文件夹时必须使用花括号模式避免把父文件夹误匹配为项目import { defineConfig } from vitest/config // 例如这将创建以下项目 // packages/a // packages/b // packages/business/c // packages/business/d // 注意packages/business 本身不会成为项目 export default defineConfig({ test: { projects: [ // 匹配 packages 内除 business 外的每个文件夹 packages/!(business), // 匹配 packages/business 内的每个文件夹 packages/business/*, ], }, })根配置文件的角色::: warning Vitest不会把根vitest.config当作一个项目除非你在配置中显式指定它。因此根配置只影响全局选项如reporters和coverage。不过根配置文件中声明的部分插件钩子如apply、config、configResolved、configureServer始终会被执行Vitest 也使用相同的插件来运行 global setups 与自定义 coverage provider。 :::通过配置文件引用项目也可以直接通过各项目自己的配置文件来引用它们import { defineConfig } from vitest/config export default defineConfig({ test: { projects: [packages/*/vitest.config.{e2e,unit}.ts], }, })该模式只会包含那些带有vitest.config文件、且文件名在扩展名前包含e2e或unit的项目即vitest.config.e2e.ts与vitest.config.unit.ts。内联配置对象与 glob 混用内联配置与字符串模式可以同时使用。内联项目默认继承声明它们的配置文件的选项可通过extends: false取消继承import { defineConfig } from vitest/config export default defineConfig({ test: { projects: [ // 匹配 packages 文件夹内的每个文件夹与文件 packages/*, { // 内联项目默认继承 // 此配置文件中的选项 test: { include: [tests/**/*.{browser}.test.{ts,js}], // 使用内联配置时建议定义 name name: happy-dom, environment: happy-dom, } }, { // 添加 extends: false 以忽略 // 此配置文件中定义的选项 extends: false, test: { include: [tests/**/*.{node}.test.{ts,js}], // name 标签的颜色也可以修改 name: { label: node, color: green }, environment: node, } } ] } })项目命名规则::: warning 所有项目必须具有唯一名称否则 Vitest 会抛出错误。内联配置若未提供nameVitest 会分配一个数字编号通过 glob 语法定义的项目配置Vitest 默认取最近的package.json中的name字段若不存在则使用文件夹名。 :::这一命名逻辑对应源码中的resolveProjectName函数resolveProjects.ts它依次尝试name字段、最近的package.json的name、文件夹名并为容器声明的项目追加前缀。名称唯一性校验则通过seenNames映射完成冲突时会报出 Project name ... is not unique 错误并列出匹配到的所有文件。使用 defineProject 获得类型安全项目配置并不支持全部配置属性。在项目配置文件中请使用defineProject而非defineConfig以获得更好的类型检查两者都定义在 packages/vitest/src/public/config.tsimport { defineProject } from vitest/config export default defineProject({ test: { environment: jsdom, // 项目配置不支持 reporters // 因此这里会报类型错误 reporters: [json] } })运行测试在根package.json中定义脚本{ scripts: { test: vitest } }然后使用你喜欢的包管理器运行npm run testyarn testpnpm run testbun run test使用 --project 过滤单个项目只需要运行某个项目内的测试时使用--projectCLI 选项npm run test --project e2eyarn test --project e2epnpm run test --project e2ebun run test --project e2e--project可以多次使用以同时过滤出多个项目npm run test --project e2e --project unityarn test --project e2e --project unitpnpm run test --project e2e --project unitbun run test --project e2e --project unit通配符与排除语法--project过滤支持*通配符与!排除。一个项目运行的判定规则是不匹配任何否定模式并且在存在常规模式时至少匹配一个常规模式# 运行除 e2e 外的所有项目 vitest --project !e2e # 运行所有以 unit 开头的项目但排除 unit (browser) vitest --project unit* --project !unit (browser)该过滤逻辑在源码中由matchesEntryFilter/isEntryExcludedByFilter实现resolveProjects.ts*通配符会被转换为正则wildcardPatternToRegExp。注意过滤是在浏览器实例与 benchmark 变体展开之后应用的因此所有候选名称包括实例派生名都在过滤范围内。配置继承与合并extends内联项目的继承机制使用内联配置定义的项目默认继承根级配置的所有选项。这一行为由extends选项控制自 Vitest 5.0 起默认开启import { defineConfig } from vitest/config import react from vitejs/plugin-react export default defineConfig({ plugins: [react()], test: { pool: threads, projects: [ { // 继承此配置中的选项如 plugins 与 pool // extends: true 是默认行为 test: { name: unit, include: [**/*.unit.test.ts], }, }, { // 不继承此配置中的任何选项 extends: false, test: { name: integration, include: [**/*.integration.test.ts], }, }, ], }, })extends选项也可以接受另一个配置文件的路径以便从非根配置文件继承选项import { defineConfig } from vitest/config export default defineConfig({ test: { projects: [ { extends: ./vitest.shared.ts, test: { name: unit, include: [**/*.unit.test.ts], }, }, ], }, })合并规则与特殊选项被扩展配置中的所有选项都会与项目自身选项合并。注意数组类选项如setupFiles是拼接而非覆盖。以下选项被特殊处理name与projects永远不会被继承。globalSetup不从根配置继承根级globalSetup每次测试运行只执行一次若被继承则每个项目都会重复执行同样的文件。但当扩展非根配置文件时globalSetup仍然会被继承。项目自身的tags替换继承到的数组而不是与之合并。如果你通过 advanced API 运行 Vitest可参考其中 Project Configuration Resolution 一节了解编程式配置如何参与继承。配置文件型项目手动合并共享配置以配置文件或目录形式引用的项目不会从根配置继承任何选项。你可以创建一份共享配置文件再用mergeConfig手动合并进项目配置import { defineProject, mergeConfig } from vitest/config import configShared from ../vitest.shared.js export default mergeConfig( configShared, defineProject({ test: { environment: jsdom, } }) )项目中不支持的配置选项::: danger Unsupported Options 部分配置选项不允许出现在项目配置中最主要的有coverage覆盖率是针对整个进程统计的reporters只支持根级 reporterresolveSnapshotPath只尊重根级的 resolverattachmentsDir附件统一存储在根级共享目录中所有其他不影响测试运行器的选项所有在项目配置内部不受支持的配置选项其名称旁都会带有CRoot /图标标记且只能在根配置文件中定义一次。 :::共享 Vite 服务器sharedViteServer与继承机制相关的一个重要性能特性是sharedViteServer默认true见 docs/config/sharedviteServer.md不修改 Vite 配置的内联项目会复用声明它们的配置的 Vite 服务器从而共享转换缓存公共源码文件只被转换一次测试更快。该选项仅对内联项目生效——以配置文件或目录引用的项目总是解析自己的 Vite 配置并创建自己的服务器。项目在以下情况仍会获得独立的 Vite 服务器定义了改变服务器的 Vite 级选项plugins、resolve等extends不指向声明它的配置extends: true与解析到声明配置文件的路径等价或定义了影响 Vite 配置的测试选项——包括alias、browser、css、deps.moduleDirectories、deps.optimizer、mode、root。而env、setupFiles、server.deps、environment等选项不阻止共享每个项目在共享服务器之上仍拥有自己的模块解析规则、模块运行器与模块实例。该判定逻辑对应源码中的getOwnServerReason函数resolveProjects.ts。嵌套项目Nested Projects以配置文件或包含配置文件的目录引用的项目可以自行声明projects。这样的配置表现得像根配置它自身不运行任何测试只提供真正运行测试的项目。这使得引用一个已定义自身项目的 workspace 成为可能import { defineConfig } from vitest/config export default defineConfig({ test: { projects: [./packages/app/vitest.config.ts], }, })import { defineProject } from vitest/config export default defineProject({ test: { name: app, projects: [ { test: { name: unit, include: [**/*.unit.test.ts], }, }, { test: { name: e2e, include: [**/*.e2e.test.ts], }, }, ], }, })嵌套项目与根配置中定义的项目行为一致内联配置扩展声明它们的那个配置此例中是app配置而非根配置extends路径相对于该配置解析其自身的globalSetup会像其他非根配置一样被扩展项目继承。嵌套项目的名称会以声明它们的配置的名称作为前缀因此上述示例会创建app (unit)与app (e2e)两个项目。--project过滤同样匹配此前缀--project app运行app配置的所有项目而--project app (unit)只运行其中一个。前缀命名在源码resolveProjectName中通过${containerLabel} (${label})实现resolveProjects.ts。若希望同时运行声明了projects的那个配置自身的测试可以在其projects中引用它自己的配置文件import { defineProject } from vitest/config export default defineProject({ test: { name: app, include: [**/*.test.ts], projects: [ // app 项目会运行自己的 include // 同时运行 app (unit) ./vitest.config.ts, { test: { name: unit, include: [**/*.unit.test.ts], }, }, ], }, })注意只有配置文件可以定义嵌套项目内联配置内部的projects选项不受支持。在源码中这种容器配置由flattenContainerEntries递归展开resolveProjects.ts容器自身不运行测试、不创建 Vite 服务器并且该函数内置了循环引用检测——若两个配置文件互相声明对方为项目会抛出 Found a circular projects definition 错误并打印完整的引用链。调试项目解析Debugging Project Resolution如果项目没有按预期解析可以通过DEBUGvitest:projects环境变量运行 VitestDEBUGvitest:projects vitestVitest 会记录每个项目的解析过程glob 模式匹配了哪些文件、浏览器实例与 benchmark 项目如何展开、某个项目为何被--project过滤丢弃、以及某个项目是创建了自己的 Vite 服务器还是与其他项目共享一个vitest:projects resolving 3 project definitions declared by root/vitest.config.ts vitest:projects projects glob packages/* matched 2 paths vitest:projects inline project unit shares the Vite server of root/vitest.config.ts vitest:projects project e2e is dropped by the --project filter: unit vitest:projects resolved projects: unit, pkg-a, pkg-b vitest:projects creating a Vite server for project pkg-a这些调试输出来自 resolveProjects.ts 中createDebugger(vitest:projects)创建的调试器。从日志中你可以看到内联项目在满足条件时会复用声明配置的 Vite 服务器shares the Vite server of以文件/目录引用的项目总是独立解析并创建自己的服务器creating a Vite server for project浏览器项目会展开为实例expands into instancesbenchmark 项目会追加(bench)变体源码中由expandBrowserInstancesInEntries与expandBenchmarksInEntries实现见 resolveProjects.ts。从源码看 Projects 的解析流程综合 resolveProjects.ts 的完整实现一次项目解析大致经历以下阶段分类定义resolveTestProjectConfigs遍历projects数组把字符串定义分为直接路径文件/目录与glob 模式两类内联对象与函数直接进入配置解析glob 使用 tinyglobby 展开默认忽略node_modules、*.timestamp-*临时配置与.DS_Store。逐项目解析resolveSingleProjectEntry为每个项目通过 Vite 的resolveConfig解析独立配置并注入TestConfigPlugin、WorkspaceVitestPlugin、BrowserLoaderPlugin等插件可复用服务器时走resolveSharedServerEntry直接在父配置原始test选项上合并。展开浏览器实例与 benchmark 变体浏览器项目按browser.instances展开为每个实例一个条目共享父级 Vite 配置benchmark 项目追加独立的(bench)条目。应用--project过滤在全部候选名称已知之后执行保证错误信息能列出所有被考虑过的名称。扁平化容器项目flattenContainerEntries递归展开声明了projects的配置文件并做循环引用检测与名称前缀处理。附加 TestProjectattachProjectsFromEntries为每个唯一 Vite 配置创建/复用服务器把条目挂载为TestProject实例。此外PROJECT_CLI_OVERRIDES列表resolveProjects.ts限定了哪些 CLI 选项可以逐项目覆盖测试配置如testTimeout、pool、retry、bail、isolate等并非所有 CLI 选项都能覆盖项目级配置。实战小结快速上手在根配置写入test: { projects: [packages/*] }即可把packages下每个文件夹变成独立项目完整可运行示例见 examples/projects。细分场景同一仓库内同时跑 jsdom 与 node 环境、browser 测试与单元测试、unit 与 e2e用内联对象 nameextends组合即可无需拆散目录结构。按需过滤CI 中通过vitest --project unit只跑指定项目配合*通配与!排除实现灵活调度。排查问题项目解析不符预期时DEBUGvitest:projects vitest能给出每一步的决策依据是定位命名冲突、过滤误伤与服务器复用问题的第一工具。【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考