Vitest experimental 配置完全指南:OpenTelemetry、importDurations、viteModuleRunner 等实验性特性的开启与实战
2026/9/14 15:15:19 网站建设 项目流程

Vitest experimental 配置完全指南:OpenTelemetry、importDurations、viteModuleRunner 等实验性特性的开启与实战

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

本篇技术指南以 Vitest 官方配置文档 docs/config/experimental.md 为骨架,系统讲解test.experimental配置对象下 7 个实验性特性——openTelemetryimportDurationsviteModuleRunnervcsProvidernodeLoaderpreParsediagnostics的类型定义、默认值、CLI 等价写法、适用场景与已知限制。读完本文,你将能够在实际项目中安全地开启这些实验特性,并借助它们完成链路追踪、导入耗时分析、跳过 Vite 转换、自定义变更检测与性能瓶颈诊断等任务。

所有配置项均定义在 Vitest 的类型声明 packages/vitest/src/node/types/config.ts 中,并通过 packages/vitest/src/node/config/resolveConfig.ts 在启动时完成默认值填充与路径解析,同时每个子命令都在 packages/vitest/src/node/cli/cli-config.ts 中有对应的 CLI 参数声明。需要注意的是,实验性 API 的签名与行为可能在后续版本中发生变化,升级 Vitest 时请留意各特性的版本标记。

概览:experimental 配置对象速查表

配置项类型默认值引入版本一句话作用
experimental.openTelemetryOpenTelemetryOptions{ enabled: false }4.0.11在主线程与每个测试文件前导入 OpenTelemetry SDK,输出链路追踪数据
experimental.importDurationsImportDurationsOptions{ print: false, failOnDanger: false, limit: 0, thresholds: { warn: 100, danger: 500 } }4.1.0采集并展示每个模块的导入耗时(limitprint或 UI 启用时为 10)
experimental.viteModuleRunnerbooleantrue4.1.0是否使用 Vite 的 Module Runner 沙箱运行代码,关闭则回退到原生import
experimental.vcsProviderVCSProvider \| string'git'4.1.1自定义--changed的变更文件检测提供者
experimental.nodeLoaderbooleantrue4.1.0模块运行器关闭时,是否用 Node Loader 转换文件以支持vi.mock等特性
experimental.preParsebooleanfalse4.1.3运行前静态解析测试规格,全局应用.only-t--tags-filter
experimental.diagnosticsboolean \| DiagnosticsOptionstrue5.0.0运行结束后输出可让测试显著加速的配置改进建议

需要特别说明的是experimental.diagnostics的默认值:配置解析代码在 resolveConfig.ts 中将其归一化为四个子开关全部为true的对象,即{ isolate: true, environment: true, import: true, transform: true }

experimental.openTelemetry:接入 OpenTelemetry 链路追踪

版本标记:4.0.11(实验性)

openTelemetry用于控制 Vitest 的 OpenTelemetry 支持。当enabledtrue时,Vitest 会在主线程中以及每一个测试文件执行前导入你指定的 SDK 文件。

interface OpenTelemetryOptions { enabled: boolean /** * 指向暴露 Node.js 版 OpenTelemetry SDK 的文件路径。 */ sdkPath?: string /** * 指向暴露浏览器版 OpenTelemetry SDK 的文件路径。 */ browserSdkPath?: string }

基本用法

sdkPath相对于项目的root解析,必须指向一个以默认导出暴露"已启动 SDK 实例"的模块。一个最小可用的示例:

import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node' import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' import { NodeSDK } from '@opentelemetry/sdk-node' const sdk = new NodeSDK({ serviceName: 'vitest', traceExporter: new OTLPTraceExporter(), instrumentations: [getNodeAutoInstrumentations()], }) sdk.start() export default sdk
import { defineConfig } from 'vitest/config' export default defineConfig({ test: { experimental: { openTelemetry: { enabled: true, sdkPath: './otel.js', }, }, }, })

从源码看,Vitest 在配置解析阶段会调用 Node 的resolvesdkPath拼接为绝对路径,再通过pathToFileURL转为file://URL(见 resolveConfig.ts);browserSdkPath同样会相对root解析,但保留为普通路径字符串(见 resolveConfig.ts)。

性能与使用注意事项

  • 性能警告:OpenTelemetry 可能显著拖慢 Vitest,建议仅用于本地调试(文档原文为 PERFORMANCE CONCERNS 警告)。
  • 未经 Vite 转换sdkPath指向的文件不会经过 Vitest 的转换管线,Node 必须能直接处理其内容,因此不要在其中使用 Vite 特有的语法或特性。完整的使用方式请参阅 docs/guide/open-telemetry.md。
  • 浏览器模式:浏览器模式的接入请参考 OpenTelemetry 指南中的 Browser Mode 一节,对应配置项为browserSdkPath
  • 典型场景:将 Vitest 与自定义上报服务配合,定位是哪些测试或文件拖慢了测试套件。仓库中examples/opentelemetry目录提供了完整的接入示例(含otel.jsotel-browser.jsdocker-compose.yaml与 Jaeger 配置),可直接对照参考。

experimental.importDurations:定位慢导入的"模块加载耗时分析"

版本标记:4.1.0(实验性)

importDurations控制模块导入耗时的采集与展示,用于定位"哪个文件导入最慢"。类型定义如下:

interface ImportDurationsOptions { /** * 何时在 CLI 终端打印导入耗时明细。 * - false: 从不打印(默认) * - true: 总是打印 * - 'on-warn': 仅当某个导入超过 warn 阈值时打印 */ print?: boolean | 'on-warn' /** * 若任何导入超过 danger 阈值则测试运行失败。 * 启用后一旦超阈值,无论 print 设置如何都会打印明细。 * @default false */ failOnDanger?: boolean /** * 采集并展示的导入数量上限。 */ limit?: number /** * 用于着色与告警的耗时阈值(毫秒)。 */ thresholds?: { /** 黄色/告警颜色阈值。@default 100 */ warn?: number /** 红色/danger 颜色阈值,同时驱动 failOnDanger。@default 500 */ danger?: number } }

默认值为{ print: false, failOnDanger: false, limit: 0, thresholds: { warn: 100, danger: 500 } };其中limitprintfailOnDanger或 UI 启用时会自动变为 10——这一逻辑由 resolveConfig.ts 实现:只要三者任一为真,shouldCollect即为 true,limit默认取 10。

耗时口径:Self 与 Total

导入明细区分两个口径:

  • Self(自身耗时):导入该模块自身花费的时间,不含其静态导入;
  • Total(总耗时):导入该模块花费的总时间,包含其静态导入,但不包含当前模块自身的transform时间。

从运行时代码看,采集逻辑位于 packages/vitest/src/runtime/runners/test.ts:getImportDurations()workerState.moduleExecutionInfo中取出每个模块的执行时长,按duration降序排序后截取前limit条,得到包含selfTimetotalTimeexternalimporter的记录;limit为 0 时直接返回空对象跳过采集。报告器端(packages/vitest/src/node/reporters/base.ts)只有在limit > 0时才认为启用了模块耗时采集。

print:控制 CLI 打印时机

  • 类型boolean | 'on-warn'
  • 默认false

控制测试结束后是否在 CLI 终端打印导入明细。仅在defaultverbosetree三种 reporter 下生效(相关 reporter 见 docs/guide/reporters.md):

  • false:从不打印;
  • true:总是打印;
  • 'on-warn':仅当有导入超过thresholds.warn时打印。

Vitest UI 的"Module Graph"标签页可以始终切换导入明细展示,不受print设置影响,详情见 docs/guide/ui.md。CLI 中还支持布尔简写:--experimental.importDurations会被转换为{ print: true }(见 cli-config.ts 的transform逻辑)。

failOnDanger:在 CI 中强制执行导入性能预算

  • 类型boolean
  • 默认false

若任一导入超过thresholds.danger,则整个测试运行以失败告终;启用后一旦触发,明细必定打印。这非常适合在 CI 中为导入性能设下硬性预算:

vitest --experimental.importDurations.failOnDanger

limit:采集展示数量上限

  • 类型number
  • 默认0(当printfailOnDanger或 UI 启用时为10

限制 CLI 输出、Vitest UI 以及第三方 reporter 中采集与展示的导入条目数量。文件路径过长时,Vitest 会从开头截断,直到符合 45 字符的显示上限。

thresholds:着色与告警阈值

  • 类型{ warn?: number; danger?: number }
  • 默认{ warn: 100, danger: 500 }

以毫秒为单位的耗时阈值:

  • warn:黄色/告警颜色阈值(默认 100ms);
  • danger:红色/danger 颜色阈值,同时作为failOnDanger的判据(默认 500ms)。

另外,Vitest UI 会在至少一个文件加载耗时超过danger阈值时自动展示导入明细。命令行完整写法示例:

vitest --experimental.importDurations.print --experimental.importDurations.limit 20 --experimental.importDurations.thresholds.warn 80 --experimental.importDurations.thresholds.danger 400

仓库中的端到端测试 test/e2e/test/reporters/import-durations.test.ts 覆盖了该特性的 CLI 展示与着色行为,可作为验证参考。

experimental.viteModuleRunner:跳过 Vite 转换直接运行

版本标记:4.1.0(实验性)

  • 类型boolean
  • 默认true

控制 Vitest 是使用 Vite 的 Module Runner 会自动继承该值。

从源码可见,viteModuleRunner: false会走完全不同的执行路径:worker 端在 packages/vitest/src/runtime/workers/base.ts 中跳过 Vite 模块运行器的初始化;主线程侧在 packages/vitest/src/node/core.ts 与 packages/vitest/src/node/project.ts 中选择原生 runner;同时 resolveConfig.ts 会直接拒绝"istanbul覆盖率 +viteModuleRunner: false"的组合并抛出明确错误。

何时应该关闭它

如果测试运行环境与代码运行环境一致(例如服务端后端代码或简单脚本),可以考虑关闭模块运行器。不过文档仍然建议jsdom/happy-dom测试继续使用 Vite Module Runner 或直接在 browser 模式中运行,因为后者无需额外配置。

关闭该选项将禁用全部文件转换,具体影响:

  • 测试文件与源码不再经过 Vite 处理;
  • 全局 setup 文件不再被处理;
  • 自定义 runner/pool/environment 文件不再被处理;
  • 配置文件仍由 Vite 处理(因为这在 Vitest 知晓viteModuleRunner标志之前就已发生)。

此外需要注意:

  • 当前 Vitest 仍依赖 Vite 提供模块图、watch 模式等功能;
  • 该选项仅对forksthreads两种 pool 生效——packages/vitest/src/runtime/workers/vm.ts 会在vm系列 pool 下直接抛出错误提示改用threadsforks

Module Runner:inline 与 external 模块的划分

默认情况下,Vitest 在由 Vite Environment API 驱动的、非常宽松的模块运行器沙箱中执行测试。每个文件被划分为两类:

  • inline(内联)模块:由 Module Runner 执行,提供import.meta.envrequire__dirname__filename、静态import以及自己的模块解析机制,几乎零配置即可运行纯 JS 逻辑;
  • external(外部)模块:以原生模式运行,脱离模块运行器沙箱。在 Node.js 中,这些文件通过原生import关键字导入并由 Node 直接处理。

文档给出的权衡是:在宽松的伪环境下跑 jsdom/happy-dom 测试可以理解,但如果在非 Node.js 环境里跑 Node.js 测试,可能会掩盖生产环境才暴露的错误——尤其是当你的代码不依赖 Vite 插件的转换时。

已知限制与 mock 行为变化

一些 Vitest 特性依赖文件转换,Vitest 通过同步的 Node.js Loaders API 转换测试文件与 setup 文件来支持它们:

  • import.meta.vitest(内联测试);
  • vi.mock
  • vi.hoisted

这意味着上述特性至少需要 Node 22.15,且当前在 Deno 与 Bun 中不可用。Vitest 只会在测试文件内部检测vi.mockvi.hoisted,导入的模块中它们不会被提升。若不用这些特性,可设置experimental.nodeLoader: false关闭转换以提升性能;若在非测试文件中使用它们则不会提升,可能引发意外行为。

由于viteModuleRunner取消了转换阶段,以下特性会失效:

  • import.meta.env:改用process.env
  • 无 plugins:没有转换阶段,插件不生效,可改用 customization hooks 注册;
  • 无 alias:别名不生效,原因同上;
  • istanbul 覆盖率 provider 不可用:改用v8(配置解析时也会直接报错拦截);
  • vi.resetModules()不可用:没有 API 使 ES 模块从模块缓存中失效。

覆盖率支持现状:当前只有文件能转换为 JavaScript 时,v8provider 才可用。转换 TypeScript 时,Vitest 使用module.stripTypeScriptTypes(Node 22.13+ 提供)。如果使用了自定义 module loader 中对viteModuleRunner === falseisTransformedByVite的判断。

Mock 相关行为变化:ES 模块不支持属性覆盖,因此如下写法会失效:

import * as fs from 'node:fs' import { vi } from 'vitest' vi.spyOn(fs, 'readFileSync').mockImplementation(() => '42') // ❌

但 Vitest 支持不覆盖实现的自动间谍化。当vi.mock传入spy: true时,模块以保留原始实现的方式被 mock,同时所有导出函数被包装进vi.fn()间谍:

import * as fs from 'node:fs' import { vi } from 'vitest' vi.mock('node:fs', { spy: true }) fs.readFileSync.mockImplementation(() => '42') // ✅

此外,工厂 mock 基于顶层 await 实现,因此被 mock 的模块无法在源码中通过require()加载:

vi.mock('node:fs', async (importOriginal) => { return { ...await importOriginal(), readFileSync: vi.fn(), } }) const fs = require('node:fs') // throws an error

这是因为工厂可以是异步的。实际影响有限:与 Vitest 默认行为一致,node_modules内的内置模块不会被 mock。

TypeScript 支持矩阵

  • Node.js 22.18 / 23.6 及以上:TypeScript 由 Node 原生转换,无需额外配置;
  • Node.js 22.6–22.18:可通过--experimental-strip-types标志启用原生 TS 支持:
NODE_OPTIONS="--experimental-strip-types" vitest
  • Node.js 低于 22.6:二选一——
    • 先构建测试文件与源码,直接运行构建产物;
    • 通过execArgv导入自定义 loader,例如接入tsx
import { defineConfig } from 'vitest/config' const tsxApi = import.meta.resolve('tsx/esm/api') export default defineConfig({ test: { execArgv: [ `--import=data:text/javascript,import * as tsx from "${tsxApi}";tsx.register()`, ], experimental: { viteModuleRunner: false, }, }, })
  • 在 Deno 中运行时,TypeScript 文件由运行时直接处理,无需额外配置。

仓库中的端到端测试 test/e2e/test/no-module-runner.test.ts 及其 fixture test/e2e/fixtures/no-module-runner/vitest.config.ts 是该模式可运行配置的现成参考。

experimental.vcsProvider:自定义变更文件检测

版本标记:4.1.1(实验性)

  • 类型VCSProvider | string
  • 默认'git'

--changed标志提供自定义的变更文件检测 provider。默认情况下 Vitest 使用 Git 检测变更文件;实现VCSProvider接口即可接入其他版本控制系统:

interface VCSProvider { findChangedFiles(options: VCSProviderOptions): Promise<string[]> } interface VCSProviderOptions { root: string changedSince?: string | boolean }

内联对象写法

import { defineConfig } from 'vitest/config' export default defineConfig({ test: { experimental: { vcsProvider: { async findChangedFiles({ root, changedSince }) { // return paths of changed files return [] }, }, }, }, })

模块路径写法

也可以传入一个指向"默认导出实现了VCSProvider接口的模块"的字符串路径:

import { defineConfig } from 'vitest/config' export default defineConfig({ test: { experimental: { vcsProvider: './my-vcs-provider.js', }, }, })
export default { async findChangedFiles({ root, changedSince }) { // return paths of changed files return [] }, }

配置解析时,非'git'的字符串路径会相对root解析为绝对路径(见 resolveConfig.ts)。仓库中的端到端测试 test/e2e/test/vcs-provider.test.ts 验证了自定义 provider 的接入流程。CLI 等价写法为--experimental.vcsProvider <path>

experimental.nodeLoader:按需关闭 Node Loader 转换

版本标记:4.1.0(实验性)

  • 类型boolean
  • 默认true

当模块运行器被禁用(viteModuleRunner: false)时,Vitest 使用原生 Node.js module loader 转换文件,以支持import.meta.vitestvi.mockvi.hoisted

如果你的代码不使用这些特性,可以关闭它以提升性能。类型注释(见 packages/vitest/src/node/types/config.ts)特别说明:该选项只影响loader.load方法,Vitest 始终会定义loader.resolve来填充模块图。CLI 等价写法为--experimental.nodeLoader false

experimental.preParse:运行前的静态规格解析

版本标记:4.1.3(实验性)

  • 类型boolean
  • 默认false

在运行测试之前解析测试规格,从而在不执行的情况下,跨所有文件应用.only修饰符、-t测试名模式、--tags-filter、test lines 与 test IDs。例如,若只有一个测试被标记.only,Vitest 将跳过所有文件中其余测试。

主线程中的实现位于 packages/vitest/src/node/core.ts:开启后先调用parseSpecifications填充每个 specification 的testModule,再过滤掉task.mode === 'skip'的规格,从而在真正执行前完成筛选。

使用建议

  • 推荐在搭配使用.only-t--tags-filter时开启;
  • 无条件开启可能因额外的解析步骤而拖慢测试运行。

静态分析的局限(重要警告):预解析使用静态分析(AST 解析)而非执行测试文件,因此测试名、标签与修饰符(.only.skip.todo)必须能被静态分析识别。动态测试名(存于变量或由函数调用返回的名称)与非字面量标签无法被正确解析:

// ✅ 可用 —— 静态字符串字面量 test('adds numbers', () => {}) // ✅ 可用 —— 静态标签 test('my test', { tags: ['unit'] }, () => {}) // ❌ 无法正确匹配 —— 动态名称 const name = getName() test(name, () => {}) // ❌ 无法正确匹配 —— 动态标签 const tags = getTags() test('my test', { tags }, () => {})

experimental.diagnostics:运行后的性能改进建议

版本标记:5.0.0(实验性)

  • 类型
interface DiagnosticsOptions { /** * 当 `isolate: true` 为每个测试文件生成新 worker(并重建环境) * 消耗大量时间时给出提示,估算 `isolate: false` 可节省的时间。 * @default true */ isolate?: boolean /** * 当为每个测试文件重建 DOM 环境占据主导、而 `vm` pool * 可在每个 worker 中只建一次时给出提示。 * @default true */ environment?: boolean /** * 当测试文件反复求值同一模块图(典型如 barrel 文件导入) * 且 `isolate: false` 可在每个 worker 中只求值一次时给出提示。 * @default true */ import?: boolean /** * 当模块转换占据主导且 `fsModuleCache` 可跨运行持久化结果时给出提示。 * @default true */ transform?: boolean }
  • 默认true(解析时归一化为四个子开关全部开启,见 resolveConfig.ts)

运行结束后,当采集到的耗时数据表明某项配置调整能显著加速测试时,Vitest 会打印性能提示,例如:

Environment jsdom was created 40 times · 23.80s total, 79% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments

重要行为约定

  • 提示永远不会建议修改显式设置的选项:若配置里显式定义了pool,则不会建议其他 pool;显式配置的isolate永远不会被建议关闭;
  • 提示在 CI 中同样会打印;
  • 设为false可关闭全部提示,也可单独关闭其中某一项;
  • 若想"实测"某项配置调整的影响而非估算,可运行vitest doctor

diagnostics.isolate

  • 类型boolean
  • 默认true

isolate: true为每个测试文件生成新 worker(并重建环境)消耗大量时间时给出提示,估算isolate: false可节省的时间。复用 worker 也会让已求值的模块保持存活,使文件不必重复求值共享的模块图。该估算的模块级耗时只在experimental.importDurations启用时才采集;未启用时,估算仅统计 worker 启动本身,并以下界("at least")形式报告。报告器端实现见 packages/vitest/src/node/reporters/base.ts:它先计算并行 lanes 下的 wall-clock 启动成本,再结合(启用了 importDurations 时)各模块的selfTime重复求值收益,最后通过isSavingWorthHinting判断是否值得提示。

diagnostics.environment

  • 类型boolean
  • 默认true

当为每个测试文件重建 DOM 环境占据主导、而vmpool 可在每个 worker 中只初始化一次环境时给出提示。

diagnostics.import

  • 类型boolean
  • 默认true

当测试文件反复求值同一模块图、而isolate: false可在每个 worker 中只求值一次时给出提示。这典型发生在 barrel 文件导入场景:每个测试文件都通过一个 index 文件导入少量符号,却求值了其背后整张模块图。重复程度按每个模块被提供给 worker 的次数衡量,因此测试文件导入的多是互不相交模块的测试套件会保持安静——复用 worker 并不会减少它们的导入工作量。

Import 837 modules were evaluated 16740 times · 15.69s total, 64% of tracked time ~850ms faster with isolate: false — shared modules are evaluated once per worker instead of once per file learn more: https://vitest.dev/guide/improving-performance#test-isolation

diagnostics.transform

  • 类型boolean
  • 默认true

当模块转换占据主导时给出提示。没有持久化缓存时,每次vitest run都会从零转换整张模块图;fsModuleCache会把结果存入磁盘,使重复运行跳过转换。该提示估算下次运行可节省的时间;在 CI 中提示会附带说明:缓存目录必须在多次运行之间持久化,缓存才能生效。

常见组合场景与最佳实践

综合上述 7 个实验特性,结合实际项目可以组合出如下典型用法:

  • 本地性能调优:开启experimental.importDurations(配合print或 Vitest UI)找出慢导入模块,再结合experimental.diagnostics给出的isolate/environment/import/transform建议,针对性调整 pool、isolatefsModuleCache
  • CI 性能预算vitest --experimental.importDurations.failOnDanger在 CI 中强制执行导入耗时上限,配合thresholds.danger自定义预算值;
  • 纯 Node 环境轻量运行:当测试代码不依赖 Vite 转换(无插件、无 alias、无import.meta.env)时,关闭viteModuleRunner走原生import路径,可显著减少环境开销,同时用nodeLoader: false在不用vi.mock时进一步提速;
  • 非 Git 仓库的增量测试:实现VCSProvider接口(内联对象或独立模块文件),让--changed在 Mercurial 等 VCS 下正常工作;
  • 大仓库精确筛选:频繁使用.only-t--tags-filter时开启preParse,在不执行的前提下全局应用筛选(注意测试名与标签必须静态可分析)。

以上配置均可在 vitest.config 中通过test.experimental对象声明,也可通过--experimental.<key>系列的 CLI 参数临时覆盖。由于这些特性仍处于实验阶段,建议在升级 Vitest 主版本后重新核对该文档(docs/config/experimental.md)中的版本标记与默认值变化。

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询