From 2470befec6b6deeab10422e1f033a657d5562dbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Tue, 20 Oct 2026 10:46:03 +0300 Subject: [PATCH 01/14] perf(coverage): istanbul to use `magic-string`+`rolldown` for instrumentation (#11648) --- packages/coverage-istanbul/package.json | 6 +- packages/coverage-istanbul/src/provider.ts | 22 +- pnpm-lock.yaml | 421 +++++++++++++++------ pnpm-workspace.yaml | 9 +- 4 files changed, 305 insertions(+), 153 deletions(-) diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index 6f9563ff4..1fc78d336 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -48,12 +48,10 @@ "vitest": "workspace:*" }, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.13", - "@jridgewell/trace-mapping": "catalog:", "@vitest/istanbul-lib-coverage": "catalog:", - "@vitest/istanbul-lib-instrument": "^1.0.2", + "@vitest/istanbul-lib-instrument": "^2.0.0", "@vitest/istanbul-lib-report": "catalog:", - "@vitest/istanbul-lib-source-maps": "^1.0.2", + "@vitest/istanbul-lib-source-maps": "^2.0.0", "magicast": "catalog:", "obug": "catalog:", "tinyrainbow": "catalog:" diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 0a5c5b1f5..c54caa34c 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -3,8 +3,6 @@ import type { Instrumenter } from '@vitest/istanbul-lib-instrument' import type { ProxifiedModule } from 'magicast' import type { CoverageProvider, ReportContext, Vite, Vitest } from 'vitest/node' import { existsSync, promises as fs } from 'node:fs' -import { addMapping, GenMapping, toEncodedMap } from '@jridgewell/gen-mapping' -import { eachMapping, TraceMap } from '@jridgewell/trace-mapping' import * as libCoverage from '@vitest/istanbul-lib-coverage' import { createInstrumenter } from '@vitest/istanbul-lib-instrument' import * as libReport from '@vitest/istanbul-lib-report' @@ -57,6 +55,7 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co coverageGlobalScopeFunc: false, ignoreClassMethods: this.options.ignoreClassMethods, ignoreLines: true, + combineSourceMaps: false, }) } } @@ -105,25 +104,6 @@ export class IstanbulCoverageProvider extends BaseCoverageProvider implements Co if (uncoveredKey) { this.uncoveredFileCoverages.set(uncoveredKey, this.instrumenter.lastFileCoverage()) - } else { - const transformMap = new GenMapping(sourceMap) - - eachMapping(new TraceMap(sourceMap as any), (mapping) => { - addMapping(transformMap, { - generated: { line: mapping.generatedLine, column: mapping.generatedColumn }, - original: { line: mapping.generatedLine, column: mapping.generatedColumn }, - content: sourceCode, - name: mapping.name || '', - source: mapping.source || '', - }) - }) - - const encodedMap = toEncodedMap(transformMap) - delete encodedMap.file - delete encodedMap.ignoreList - delete encodedMap.sourceRoot - - this.instrumenter.instrumentSync(sourceCode, id, encodedMap as any) } const map = this.instrumenter.lastSourceMap() as any diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f687e4001..656d47015 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,11 +55,11 @@ catalogs: specifier: ^6.0.10 version: 6.0.10 '@vitest/istanbul-lib-coverage': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^2.0.0 + version: 2.0.0 '@vitest/istanbul-lib-report': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^2.0.0 + version: 2.0.0 '@vueuse/core': specifier: ^14.4.0 version: 14.4.0 @@ -244,10 +244,10 @@ importers: version: 5.9.3 unplugin-isolated-decl: specifier: ^0.17.0 - version: 0.17.0(esbuild@0.28.2)(rollup@4.64.9)(typescript@5.9.3)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 0.17.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(typescript@5.9.3)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) unplugin-oxc: specifier: ^0.6.2 - version: 0.6.2(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 0.6.2(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) vite: specifier: 8.0.11 version: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -311,7 +311,7 @@ importers: version: 0.2.17 unocss: specifier: 'catalog:' - version: 66.11.0(@oxc-project/types@0.153.0)(cac@7.1.0)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 66.11.0(@oxc-project/types@0.153.0)(cac@7.1.0)(rolldown@1.2.14)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) vite: specifier: 8.0.11 version: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -459,7 +459,7 @@ importers: version: 19.3.0 '@vitejs/plugin-react': specifier: ^6.1.2 - version: 6.1.2(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.0.0-rc.18)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 6.1.2(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.2.14)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) '@vitest/ui': specifier: workspace:* version: link:../../packages/ui @@ -622,24 +622,18 @@ importers: packages/coverage-istanbul: dependencies: - '@jridgewell/gen-mapping': - specifier: ^0.3.13 - version: 0.3.13 - '@jridgewell/trace-mapping': - specifier: 'catalog:' - version: 0.3.31 '@vitest/istanbul-lib-coverage': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/istanbul-lib-instrument': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^2.0.0 + version: 2.0.0 '@vitest/istanbul-lib-report': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/istanbul-lib-source-maps': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^2.0.0 + version: 2.0.0 magicast: specifier: 'catalog:' version: 0.5.5 @@ -664,10 +658,10 @@ importers: version: 1.0.2 '@vitest/istanbul-lib-coverage': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/istanbul-lib-report': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 ast-v8-to-istanbul: specifier: ^1.0.7 version: 1.0.7 @@ -890,7 +884,7 @@ importers: version: 5.9.3 unocss: specifier: 'catalog:' - version: 66.11.0(@oxc-project/types@0.153.0)(cac@7.1.0)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 66.11.0(@oxc-project/types@0.154.0)(cac@7.1.0)(rolldown@1.2.14)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) vite: specifier: 8.0.11 version: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -902,7 +896,7 @@ importers: version: 3.5.44(typescript@5.9.3) vue-router: specifier: ^5.4.0 - version: 5.4.0(@vue/compiler-sfc@3.5.44)(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@5.9.3)) + version: 5.4.0(@vue/compiler-sfc@3.5.44)(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@5.9.3)) vue-tsc: specifier: ^3.3.13 version: 3.3.13(typescript@5.9.3) @@ -1033,10 +1027,10 @@ importers: version: link:../expect '@vitest/istanbul-lib-coverage': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/istanbul-lib-report': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/pretty-format': specifier: workspace:* version: link:../pretty-format @@ -1202,10 +1196,10 @@ importers: version: link:../../packages/coverage-v8 '@vitest/istanbul-lib-coverage': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/istanbul-lib-report': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 '@vitest/utils': specifier: workspace:* version: link:../../packages/utils @@ -1232,7 +1226,7 @@ importers: version: 1.105.2 unplugin-swc: specifier: ^1.6.0 - version: 1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) vite: specifier: 8.0.11 version: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -1361,7 +1355,7 @@ importers: version: 5.9.3 unplugin-swc: specifier: ^1.6.0 - version: 1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + version: 1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) vite: specifier: 8.0.11 version: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -1632,7 +1626,7 @@ importers: version: 2.3.5 '@vitest/istanbul-lib-coverage': specifier: 'catalog:' - version: 1.0.2 + version: 2.0.0 jsdom: specifier: ^30.1.3 version: 30.1.3(@noble/hashes@1.8.0) @@ -1711,10 +1705,6 @@ packages: resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@8.0.0': - resolution: {integrity: sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/compat-data@8.0.5': resolution: {integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==} engines: {node: ^22.18.0 || >=24.11.0} @@ -1723,10 +1713,6 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/core@8.0.1': - resolution: {integrity: sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/core@8.0.7': resolution: {integrity: sha512-ZIsZWBG6Y1oa+8fkQVp0xeCdjCHBkXFCCIWrmg9SvejVvfcVQgKKRmGrBDWfM7rilhvnRoEuaqa5dkK7KB3pHA==} engines: {node: ^22.18.0 || >=24.11.0} @@ -1755,10 +1741,6 @@ packages: resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@8.0.0': - resolution: {integrity: sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helper-compilation-targets@8.0.7': resolution: {integrity: sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg==} engines: {node: ^22.18.0 || >=24.11.0} @@ -1900,10 +1882,6 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/helpers@8.0.0': - resolution: {integrity: sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==} - engines: {node: ^22.18.0 || >=24.11.0} - '@babel/helpers@8.0.7': resolution: {integrity: sha512-5dMj5kFwS2AY7Dt4PZp4px3c5ThMbbEa7tZfuHTncCGSFYq8uoA1ufDE1IS/lYpQedE+3+GQ3cPZUiplIu195w==} engines: {node: ^22.18.0 || >=24.11.0} @@ -2899,9 +2877,6 @@ packages: '@iconify-json/carbon@1.2.28': resolution: {integrity: sha512-dog92E0fGszJSrmeikPQmFrd4ptbdapogEhtZ9MCA9KiCNacYb2/nDi0euwsK0kTevsFiTy7Mte6QvRq3LDyHQ==} - '@iconify-json/logos@1.2.15': - resolution: {integrity: sha512-87zwqEFatvpcK7lIIp0RnCbDAPWcG8bTK5hZI5SuD71za+opSuqtGvFUiSNt6wiyuY+nolrW/JW1K4/mBczWhQ==} - '@iconify-json/logos@1.2.16': resolution: {integrity: sha512-J7uewTzmJchaWkc8f/SW3aS14Y6H1bnLihMZaVccDao3TRsWFJvGfAhbkTzErKjSSbRJG8uzjY4yzMbGMELPIw==} @@ -4051,6 +4026,9 @@ packages: '@oxc-project/types@0.153.0': resolution: {integrity: sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==} + '@oxc-project/types@0.154.0': + resolution: {integrity: sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} cpu: [arm] @@ -4806,36 +4784,72 @@ packages: '@rive-app/canvas-lite@2.33.3': resolution: {integrity: sha512-SQ1S10FY4fUVsK0uYKn99hgUuP+BLpYJHeQoRztDPhLqG7W/aUa9fkem0jKA+fBCGFRFFcsBRqE9EFXZxU8crQ==} + '@rolldown/binding-android-arm-eabi@1.2.14': + resolution: {integrity: sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.18': resolution: {integrity: sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.2.14': + resolution: {integrity: sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.18': resolution: {integrity: sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.2.14': + resolution: {integrity: sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.18': resolution: {integrity: sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.2.14': + resolution: {integrity: sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.18': resolution: {integrity: sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.2.14': + resolution: {integrity: sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': resolution: {integrity: sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': + resolution: {integrity: sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4843,6 +4857,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.2.14': + resolution: {integrity: sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': resolution: {integrity: sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4850,6 +4871,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.2.14': + resolution: {integrity: sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4857,6 +4885,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.2.14': + resolution: {integrity: sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18': resolution: {integrity: sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4864,6 +4899,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.2.14': + resolution: {integrity: sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': resolution: {integrity: sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4871,6 +4913,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.2.14': + resolution: {integrity: sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': resolution: {integrity: sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4878,12 +4927,25 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.2.14': + resolution: {integrity: sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': resolution: {integrity: sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.2.14': + resolution: {integrity: sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': resolution: {integrity: sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4895,12 +4957,24 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.2.14': + resolution: {integrity: sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': resolution: {integrity: sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.2.14': + resolution: {integrity: sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/plugin-babel@0.2.4': resolution: {integrity: sha512-ygmm2j/mN+aCvstHo0x3IOu4uc6LqXT9SJ3q4kpfG/+Yhfz0B+E47FITcC0FljZADwUFARP6E2kiE+Lc26D8wg==} engines: {node: '>=22.12.0 || ^24.0.0'} @@ -5869,20 +5943,20 @@ packages: '@vitest/cjs-lib@file:test/browser/cjs-lib': resolution: {directory: test/browser/cjs-lib, type: directory} - '@vitest/istanbul-lib-coverage@1.0.2': - resolution: {integrity: sha512-9J/JMwOf9AoJhAywhrn7ScKTL38hsWQP/qPG60OtaAFcQ5OXPwKsxZFlbnuCKmZ61m8/lGgHYnFpdyQZUvG/iA==} + '@vitest/istanbul-lib-coverage@2.0.0': + resolution: {integrity: sha512-lrEG3C2XXDdROhFqMY1ykOdLlG6c5swv0B7VT46qH5erygXlTHvDYMVYrHQSjPk5R8HXrsYPM1UniXZakW8YOw==} engines: {node: '>=22'} - '@vitest/istanbul-lib-instrument@1.0.2': - resolution: {integrity: sha512-KFlvK0uYxYjdNoQJR+6F1B45u0ZOmYgOZndqfwLiGBjYHACc8q1Wyuw/ZQeGP3UKc7XsSZ51NKQDKt0IIbGMQg==} + '@vitest/istanbul-lib-instrument@2.0.0': + resolution: {integrity: sha512-K8x0LrS0NcqRVvtN2a/MVTMfMXQ0l3MiPQyCiHGBujkwz4vdI4eAeaaKjjrmpJ2ANmc83VBORTNP2C1NuFRVTQ==} engines: {node: '>=22'} - '@vitest/istanbul-lib-report@1.0.2': - resolution: {integrity: sha512-gUsfXZJbzPamoIY5TvHFiMMoXESBrUMo+xqaj+rYrWI69+EnvRlYBlP96ZnHPY4vX8kyUpgAnFUCg5wZG/HkDQ==} + '@vitest/istanbul-lib-report@2.0.0': + resolution: {integrity: sha512-TR6pOY2sYZpwzVGPKQ6ySTLrvaD/WdzFXBoY3eRxi6XJl1XZttVx1+BxQXEJu3zqosqmQeE+tenZPRRnLVgECg==} engines: {node: '>=22'} - '@vitest/istanbul-lib-source-maps@1.0.2': - resolution: {integrity: sha512-Ia3+9WDB5UKN940ztJ5LyhqDaS/It05Uh2VQ70KIPDWlOed2ZsJTJTyMb7x6OIW4eAwWSBFknpqJafJetTTXLw==} + '@vitest/istanbul-lib-source-maps@2.0.0': + resolution: {integrity: sha512-RSiRa3EwG+W8Y/ZW2l+X4u22GR89EvpDRwwZsfnunFGwhFokrGaXckrJcKaO6x+y5uyfqfCoPmGqDoOarksC9w==} engines: {node: '>=22'} '@vitest/test-dep-cjs@file:test/unit/deps/dep-cjs': @@ -9100,6 +9174,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.2.14: + resolution: {integrity: sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-dts@6.5.1: resolution: {integrity: sha512-jODTXp3H7MK/Ur/ErtsrQ0G1GvaCmc3du+y5pNrdBMf6d7HlL2Nd/N6TkEr+f75CkUj01zEoEd7y2elH0eHi1Q==} engines: {node: '>=20'} @@ -10723,8 +10802,6 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/compat-data@8.0.0': {} - '@babel/compat-data@8.0.5': {} '@babel/core@7.29.7(supports-color@10.2.2)': @@ -10747,25 +10824,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@8.0.1': - dependencies: - '@babel/code-frame': 8.0.0 - '@babel/generator': 8.0.0 - '@babel/helper-compilation-targets': 8.0.0 - '@babel/helpers': 8.0.0 - '@babel/parser': 8.0.4 - '@babel/template': 8.0.0 - '@babel/traverse': 8.0.4 - '@babel/types': 8.0.4 - '@types/gensync': 1.0.5 - convert-source-map: 2.0.0 - empathic: 2.0.1 - gensync: 1.0.0-beta.2 - import-meta-resolve: 4.2.0 - json5: 2.2.3 - obug: 2.1.4 - semver: 7.8.5 - '@babel/core@8.0.7': dependencies: '@babel/code-frame': 8.0.6 @@ -10827,14 +10885,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-compilation-targets@8.0.0': - dependencies: - '@babel/compat-data': 8.0.0 - '@babel/helper-validator-option': 8.0.0 - browserslist: 4.28.7 - lru-cache: 11.5.3 - semver: 7.8.5 - '@babel/helper-compilation-targets@8.0.7': dependencies: '@babel/compat-data': 8.0.5 @@ -10999,11 +11049,6 @@ snapshots: '@babel/template': 7.29.7 '@babel/types': 7.29.8 - '@babel/helpers@8.0.0': - dependencies: - '@babel/template': 8.0.0 - '@babel/types': 8.0.4 - '@babel/helpers@8.0.7': dependencies: '@babel/template': 8.0.0 @@ -11940,10 +11985,6 @@ snapshots: dependencies: '@iconify/types': 2.0.0 - '@iconify-json/logos@1.2.15': - dependencies: - '@iconify/types': 2.0.0 - '@iconify-json/logos@1.2.16': dependencies: '@iconify/types': 2.0.0 @@ -12855,6 +12896,8 @@ snapshots: '@oxc-project/types@0.153.0': {} + '@oxc-project/types@0.154.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': optional: true @@ -13287,42 +13330,81 @@ snapshots: '@rive-app/canvas-lite@2.33.3': {} + '@rolldown/binding-android-arm-eabi@1.2.14': + optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-android-arm64@1.2.14': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-darwin-arm64@1.2.14': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.18': optional: true + '@rolldown/binding-darwin-x64@1.2.14': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.18': optional: true + '@rolldown/binding-freebsd-x64@1.2.14': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.2.14': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm64-gnu@1.2.14': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-arm64-musl@1.2.14': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.2.14': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-s390x-gnu@1.2.14': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-x64-gnu@1.2.14': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.18': optional: true + '@rolldown/binding-linux-x64-musl@1.2.14': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.18': optional: true + '@rolldown/binding-openharmony-arm64@1.2.14': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.18': dependencies: '@emnapi/core': 1.10.0 @@ -13333,9 +13415,15 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18': optional: true + '@rolldown/binding-win32-arm64-msvc@1.2.14': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.18': optional: true + '@rolldown/binding-win32-x64-msvc@1.2.14': + optional: true + '@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.0.0-rc.18)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@babel/core': 8.0.7 @@ -13345,6 +13433,16 @@ snapshots: '@babel/runtime': 7.29.7 vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) + '@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.2.14)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))': + dependencies: + '@babel/core': 8.0.7 + picomatch: 4.0.7 + rolldown: 1.2.14 + optionalDependencies: + '@babel/runtime': 7.29.7 + vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) + optional: true + '@rolldown/pluginutils@1.0.0-rc.18': {} '@rolldown/pluginutils@1.0.1': {} @@ -14150,7 +14248,7 @@ snapshots: '@unocss/reset@66.11.0': {} - '@unocss/rollup@66.11.0(rollup@4.64.9)': + '@unocss/rollup@66.11.0(rolldown@1.2.14)(rollup@4.64.9)': dependencies: '@jridgewell/remapping': 2.3.5 '@unocss/config': 66.11.0 @@ -14161,6 +14259,7 @@ snapshots: tinyglobby: 0.2.17 unplugin-utils: 0.3.2 optionalDependencies: + rolldown: 1.2.14 rollup: 4.64.9 '@unocss/rule-utils@66.11.0': @@ -14168,11 +14267,20 @@ snapshots: '@unocss/core': 66.11.0 magic-string: 1.4.3 - '@unocss/transformer-attributify-jsx@66.11.0(@oxc-project/types@0.153.0)': + '@unocss/transformer-attributify-jsx@66.11.0(@oxc-project/types@0.153.0)(rolldown@1.2.14)': + dependencies: + '@unocss/core': 66.11.0 + oxc-parser: 0.153.0 + oxc-walker: 1.1.1(@oxc-project/types@0.153.0)(oxc-parser@0.153.0)(rolldown@1.2.14) + transitivePeerDependencies: + - '@oxc-project/types' + - rolldown + + '@unocss/transformer-attributify-jsx@66.11.0(@oxc-project/types@0.154.0)(rolldown@1.2.14)': dependencies: '@unocss/core': 66.11.0 oxc-parser: 0.153.0 - oxc-walker: 1.1.1(@oxc-project/types@0.153.0)(oxc-parser@0.153.0) + oxc-walker: 1.1.1(@oxc-project/types@0.154.0)(oxc-parser@0.153.0)(rolldown@1.2.14) transitivePeerDependencies: - '@oxc-project/types' - rolldown @@ -14230,12 +14338,12 @@ snapshots: dependencies: vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) - '@vitejs/plugin-react@6.1.2(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.0.0-rc.18)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))': + '@vitejs/plugin-react@6.1.2(@rolldown/plugin-babel@0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.2.14)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.0.0-rc.18)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.4(@babel/core@8.0.7)(@babel/runtime@7.29.7)(rolldown@1.2.14)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) '@vitejs/plugin-vue@6.0.10(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@5.9.3))': dependencies: @@ -14253,25 +14361,26 @@ snapshots: '@vitest/cjs-lib@file:test/browser/cjs-lib': {} - '@vitest/istanbul-lib-coverage@1.0.2': {} + '@vitest/istanbul-lib-coverage@2.0.0': {} - '@vitest/istanbul-lib-instrument@1.0.2': + '@vitest/istanbul-lib-instrument@2.0.0': dependencies: - '@babel/core': 8.0.1 - '@babel/parser': 8.0.4 '@istanbuljs/schema': 0.1.6 + '@jridgewell/remapping': 2.3.5 '@jridgewell/trace-mapping': 0.3.31 - '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 2.0.0 + magic-string: 1.4.3 + rolldown: 1.2.14 - '@vitest/istanbul-lib-report@1.0.2': + '@vitest/istanbul-lib-report@2.0.0': dependencies: - '@vitest/istanbul-lib-coverage': 1.0.2 + '@vitest/istanbul-lib-coverage': 2.0.0 - '@vitest/istanbul-lib-source-maps@1.0.2': + '@vitest/istanbul-lib-source-maps@2.0.0': dependencies: '@jridgewell/trace-mapping': 0.3.31 - '@vitest/istanbul-lib-coverage': 1.0.2 - obug: 2.1.4 + '@vitest/istanbul-lib-coverage': 2.0.0 + obug: 3.0.0 '@vitest/test-dep-cjs@file:test/unit/deps/dep-cjs': {} @@ -17520,10 +17629,17 @@ snapshots: '@oxc-transform/binding-win32-ia32-msvc': 0.152.0 '@oxc-transform/binding-win32-x64-msvc': 0.152.0 - oxc-walker@1.1.1(@oxc-project/types@0.153.0)(oxc-parser@0.153.0): + oxc-walker@1.1.1(@oxc-project/types@0.153.0)(oxc-parser@0.153.0)(rolldown@1.2.14): optionalDependencies: '@oxc-project/types': 0.153.0 oxc-parser: 0.153.0 + rolldown: 1.2.14 + + oxc-walker@1.1.1(@oxc-project/types@0.154.0)(oxc-parser@0.153.0)(rolldown@1.2.14): + optionalDependencies: + '@oxc-project/types': 0.154.0 + oxc-parser: 0.153.0 + rolldown: 1.2.14 oxfmt@0.73.0: dependencies: @@ -18055,6 +18171,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.18 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.18 + rolldown@1.2.14: + dependencies: + '@oxc-project/types': 0.154.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.14 + '@rolldown/binding-android-arm64': 1.2.14 + '@rolldown/binding-darwin-arm64': 1.2.14 + '@rolldown/binding-darwin-x64': 1.2.14 + '@rolldown/binding-freebsd-x64': 1.2.14 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.14 + '@rolldown/binding-linux-arm64-gnu': 1.2.14 + '@rolldown/binding-linux-arm64-musl': 1.2.14 + '@rolldown/binding-linux-ppc64-gnu': 1.2.14 + '@rolldown/binding-linux-s390x-gnu': 1.2.14 + '@rolldown/binding-linux-x64-gnu': 1.2.14 + '@rolldown/binding-linux-x64-musl': 1.2.14 + '@rolldown/binding-openharmony-arm64': 1.2.14 + '@rolldown/binding-win32-arm64-msvc': 1.2.14 + '@rolldown/binding-win32-x64-msvc': 1.2.14 + rollup-plugin-dts@6.5.1(rollup@4.64.9)(typescript@5.9.3): dependencies: '@jridgewell/remapping': 2.3.5 @@ -19136,7 +19273,37 @@ snapshots: universalify@2.0.1: {} - unocss@66.11.0(@oxc-project/types@0.153.0)(cac@7.1.0)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unocss@66.11.0(@oxc-project/types@0.153.0)(cac@7.1.0)(rolldown@1.2.14)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + dependencies: + '@unocss/cli': 66.11.0 + '@unocss/core': 66.11.0 + '@unocss/preset-attributify': 66.11.0 + '@unocss/preset-icons': 66.11.0 + '@unocss/preset-mini': 66.11.0 + '@unocss/preset-tagify': 66.11.0 + '@unocss/preset-typography': 66.11.0 + '@unocss/preset-uno': 66.11.0 + '@unocss/preset-web-fonts': 66.11.0 + '@unocss/preset-wind': 66.11.0 + '@unocss/preset-wind3': 66.11.0 + '@unocss/preset-wind4': 66.11.0 + '@unocss/rollup': 66.11.0(rolldown@1.2.14)(rollup@4.64.9) + '@unocss/transformer-attributify-jsx': 66.11.0(@oxc-project/types@0.153.0)(rolldown@1.2.14) + '@unocss/transformer-compile-class': 66.11.0 + '@unocss/transformer-directives': 66.11.0 + '@unocss/transformer-variant-group': 66.11.0 + '@unocss/vite': 66.11.0(cac@7.1.0)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + transitivePeerDependencies: + - '@devframes/agentic' + - '@oxc-project/types' + - cac + - ocache + - rolldown + - rollup + - srvx + - vite + + unocss@66.11.0(@oxc-project/types@0.154.0)(cac@7.1.0)(rolldown@1.2.14)(rollup@4.64.9)(srvx@1.0.5)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@unocss/cli': 66.11.0 '@unocss/core': 66.11.0 @@ -19150,8 +19317,8 @@ snapshots: '@unocss/preset-wind': 66.11.0 '@unocss/preset-wind3': 66.11.0 '@unocss/preset-wind4': 66.11.0 - '@unocss/rollup': 66.11.0(rollup@4.64.9) - '@unocss/transformer-attributify-jsx': 66.11.0(@oxc-project/types@0.153.0) + '@unocss/rollup': 66.11.0(rolldown@1.2.14)(rollup@4.64.9) + '@unocss/transformer-attributify-jsx': 66.11.0(@oxc-project/types@0.154.0)(rolldown@1.2.14) '@unocss/transformer-compile-class': 66.11.0 '@unocss/transformer-directives': 66.11.0 '@unocss/transformer-variant-group': 66.11.0 @@ -19168,13 +19335,13 @@ snapshots: unpipe@1.0.0: {} - unplugin-isolated-decl@0.17.0(esbuild@0.28.2)(rollup@4.64.9)(typescript@5.9.3)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unplugin-isolated-decl@0.17.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(typescript@5.9.3)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: magic-string: 1.2.3 obug: 2.1.4 oxc-parser: 0.147.0 oxc-transform: 0.141.0 - unplugin: 3.3.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 optionalDependencies: typescript: 5.9.3 @@ -19189,12 +19356,12 @@ snapshots: - vite - webpack - unplugin-oxc@0.6.2(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unplugin-oxc@0.6.2(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: oxc-minify: 0.152.0 oxc-resolver: 11.24.2 oxc-transform: 0.152.0 - unplugin: 3.4.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rsbuild/core' @@ -19206,12 +19373,12 @@ snapshots: - vite - webpack - unplugin-swc@1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unplugin-swc@1.6.0(@swc/core@1.4.1(@swc/helpers@0.5.17))(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@rollup/pluginutils': 5.4.0(rollup@4.64.9) '@swc/core': 1.4.1(@swc/helpers@0.5.17) load-tsconfig: 0.2.5 - unplugin: 3.3.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -19228,23 +19395,25 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.7 - unplugin@3.3.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unplugin@3.3.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: esbuild: 0.28.2 + rolldown: 1.2.14 rollup: 4.64.9 vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) - unplugin@3.4.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): + unplugin@3.4.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: esbuild: 0.28.2 + rolldown: 1.2.14 rollup: 4.64.9 vite: 8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0) @@ -19343,7 +19512,7 @@ snapshots: vitepress-plugin-group-icons@1.8.1(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)): dependencies: - '@iconify-json/logos': 1.2.15 + '@iconify-json/logos': 1.2.16 '@iconify-json/vscode-icons': 1.2.67 '@iconify/utils': 3.1.7 optionalDependencies: @@ -19471,7 +19640,7 @@ snapshots: dependencies: vue: 3.5.44(typescript@5.9.3) - vue-router@5.4.0(@vue/compiler-sfc@3.5.44)(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@5.9.3)): + vue-router@5.4.0(@vue/compiler-sfc@3.5.44)(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.44(typescript@5.9.3)): dependencies: '@vue-macros/common': 3.1.4(vue@3.5.44(typescript@5.9.3)) '@vue/devtools-api': 8.2.1 @@ -19487,7 +19656,7 @@ snapshots: picomatch: 4.0.7 scule: 1.3.0 tinyglobby: 0.2.17 - unplugin: 3.4.0(esbuild@0.28.2)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.4.0(esbuild@0.28.2)(rolldown@1.2.14)(rollup@4.64.9)(vite@8.0.11(@types/node@24.19.2)(esbuild@0.28.2)(jiti@2.7.0)(sass-embedded@1.105.2)(sass@1.105.2)(terser@5.48.0)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 vue: 3.5.44(typescript@5.9.3) optionalDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f81a8ab91..ee5403bd7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -61,8 +61,8 @@ catalog: '@types/yauzl': ^3.4.0 '@unocss/reset': ^66.11.0 '@vitejs/plugin-vue': ^6.0.10 - '@vitest/istanbul-lib-coverage': ^1.0.2 - '@vitest/istanbul-lib-report': ^1.0.2 + '@vitest/istanbul-lib-coverage': ^2.0.0 + '@vitest/istanbul-lib-report': ^2.0.0 '@vueuse/core': ^14.4.0 acorn-walk: ^8.3.5 ansivision: ^0.4.0 @@ -115,3 +115,8 @@ allowBuilds: sharp: true vue-demi: true workerd: true +minimumReleaseAgeExclude: + - '@vitest/istanbul-lib-coverage@2.0.0' + - '@vitest/istanbul-lib-instrument@2.0.0' + - '@vitest/istanbul-lib-report@2.0.0' + - '@vitest/istanbul-lib-source-maps@2.0.0' -- 2.54.0 From e955415841b9e439e09507616b1754a36f49e020 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 20 Oct 2026 09:47:14 +0200 Subject: [PATCH 02/14] fix: detect mock factory cycles that form later (#11642) --- .../src/runtime/moduleRunner/moduleMocker.ts | 48 ++++++++++--- .../src/runtime/moduleRunner/moduleRunner.ts | 6 +- test/e2e/test/mocking.test.ts | 72 +++++++++++++++++++ 3 files changed, 114 insertions(+), 12 deletions(-) diff --git a/packages/vitest/src/runtime/moduleRunner/moduleMocker.ts b/packages/vitest/src/runtime/moduleRunner/moduleMocker.ts index b30ae8c7c..83cede027 100644 --- a/packages/vitest/src/runtime/moduleRunner/moduleMocker.ts +++ b/packages/vitest/src/runtime/moduleRunner/moduleMocker.ts @@ -32,6 +32,7 @@ export class VitestMocker extends BareModuleMocker { // factories that run in the current async context, the innermost is last private factoryContexts = new AsyncLocalStorage() private pendingFactories = new Map() + private waitListeners = new Set<() => void>() constructor( public moduleRunner: VitestModuleRunner, @@ -105,10 +106,8 @@ export class VitestMocker extends BareModuleMocker { if (!this.pendingFactories.has(mockId)) { return false } - const contexts = this.factoryContexts.getStore() ?? [] - return ( - contexts.some((context) => context.mockId === mockId) || this.waitsForCurrentFactory(mockId) - ) + const factories = this.currentFactories() + return factories.includes(mockId) || this.waitsForFactories(mockId, factories) } /** @@ -116,22 +115,50 @@ export class VitestMocker extends BareModuleMocker { * that runs in the current async context. */ public isWaitingForFactory(mod: EvaluatedModuleNode): boolean { - return !!mod.promise && !mod.evaluated && this.waitsForCurrentFactory(mod.id) + return !!mod.promise && !mod.evaluated && this.waitsForFactories(mod.id) + } + + /** + * Waits for the module evaluation. If the module starts to wait + * for a factory that waits for it, the factory gets the partial exports. + */ + public awaitEvaluation(mod: EvaluatedModuleNode, request: () => Promise): Promise { + if (!mod.promise || mod.evaluated) { + return request() + } + const factories = this.currentFactories() + if (!factories.length) { + return request() + } + let onWait!: () => void + const cycle = new Promise((resolve) => { + onWait = () => { + if (this.waitsForFactories(mod.id, factories)) { + resolve(mod.exports as T) + } + } + }) + this.waitListeners.add(onWait) + return Promise.race([request(), cycle]).finally(() => this.waitListeners.delete(onWait)) + } + + private currentFactories(): string[] { + return (this.factoryContexts.getStore() ?? []).map((context) => context.mockId) } - private waitsForCurrentFactory(id: string): boolean { - const factories = (this.factoryContexts.getStore() ?? []).map((context) => context.mockId) + private waitsForFactories(id: string, factories = this.currentFactories()): boolean { + const queue = [...factories] const visited = new Set(factories) // a factory that waits for the current one makes everything that waits for it wait too - while (factories.length) { - const waiting = this.pendingFactories.get(factories.pop()!)?.waiting + while (queue.length) { + const waiting = this.pendingFactories.get(queue.pop()!)?.waiting if (waiting?.has(id)) { return true } waiting?.forEach((waiter) => { if (this.pendingFactories.has(waiter) && !visited.has(waiter)) { visited.add(waiter) - factories.push(waiter) + queue.push(waiter) } }) } @@ -168,6 +195,7 @@ export class VitestMocker extends BareModuleMocker { const pending = this.pendingFactories.get(id) if (pending) { waiting.forEach((waiter) => pending.waiting.add(waiter)) + this.waitListeners.forEach((onWait) => onWait()) return pending.promise } const factory: PendingFactory = { diff --git a/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts b/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts index e3d380ea1..d8c644aa8 100644 --- a/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts +++ b/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts @@ -149,8 +149,10 @@ export class VitestModuleRunner extends viteModuleRunner.ModuleRunner implements metadata?: SSRImportMetadata, ) { this.vitestOptions.getWorkerState().dependencies?.add(module.id) - // @ts-expect-error "cachedRequest" is private - return super.cachedRequest(url, module, callstack, metadata) + return this.mocker.awaitEvaluation(module, () => + // @ts-expect-error "cachedRequest" is private + super.cachedRequest(url, module, callstack, metadata), + ) } /** diff --git a/test/e2e/test/mocking.test.ts b/test/e2e/test/mocking.test.ts index 77bc0e570..59d64930b 100644 --- a/test/e2e/test/mocking.test.ts +++ b/test/e2e/test/mocking.test.ts @@ -215,6 +215,36 @@ vi.mock('./dep.js', async () => { test('another module waits for the factory', async () => { const [dep, { label }] = await Promise.all([import('./dep.js'), import('./static.js')]) expect([dep.value, label, seen.label]).toEqual(['mocked', 'mocked', undefined]) +}) + `, + './signal.js': ` +export let start +export const started = new Promise(resolve => start = resolve) + `, + './slow.js': ` +import { start } from './signal.js' +start() +await new Promise(resolve => setTimeout(resolve, 100)) + `, + './slow-static.js': ` +import './slow.js' +import { value } from './dep.js' +export const label = value + `, + './late-wait.test.js': ` +import { expect, test, vi } from 'vitest' + +const seen = vi.hoisted(() => ({})) +vi.mock('./dep.js', async () => { + const { started } = await import('./signal.js') + await started + seen.label = (await import('./slow-static.js')).label + return { value: 'mocked' } +}) + +test('a module starts to wait after the factory imports it', async () => { + const [dep, { label }] = await Promise.all([import('./dep.js'), import('./slow-static.js')]) + expect([dep.value, label, seen.label]).toEqual(['mocked', 'mocked', undefined]) }) `, }) @@ -222,6 +252,9 @@ test('another module waits for the factory', async () => { expect(stderr).toBe('') expect(errorTree()).toMatchInlineSnapshot(` { + "late-wait.test.js": { + "a module starts to wait after the factory imports it": "passed", + }, "other-module.test.js": { "another module waits for the factory": "passed", }, @@ -432,6 +465,42 @@ test('the original module imports a module that waits for the automock', async ( expect(vi.isMockFunction(waits.fn)).toBe(true) expect(other.label).toBe('other') expect(other.callDep()).toBe(undefined) +}) + `, + './signal.js': ` +export let start +export const started = new Promise(resolve => start = resolve) + `, + './slow.js': ` +import { start } from './signal.js' +start() +await new Promise(resolve => setTimeout(resolve, 100)) + `, + './late-waits.js': ` +import { started } from './signal.js' +await started +const { label } = await import('./late-other.js') +export const fromOther = label +export function fn() { + return 'original' +} + `, + './late-other.js': ` +import './slow.js' +import { fn } from './late-waits.js' +export const label = 'other' +export const callDep = () => fn() + `, + './late-wait.test.js': ` +import { expect, test, vi } from 'vitest' + +vi.mock('./late-waits.js') + +test('a module starts to wait after the original module imports it', async () => { + const [waits, other] = await Promise.all([import('./late-waits.js'), import('./late-other.js')]) + expect(vi.isMockFunction(waits.fn)).toBe(true) + expect(other.label).toBe('other') + expect(other.callDep()).toBe(undefined) }) `, }) @@ -445,6 +514,9 @@ test('the original module imports a module that waits for the automock', async ( "cycle.test.js": { "a cycle through the original module gets the partial automock": "passed", }, + "late-wait.test.js": { + "a module starts to wait after the original module imports it": "passed", + }, "spy.test.js": { "imports share one spied module": "passed", }, -- 2.54.0 From 973dc84beb2f7ce92e15f7f41e166cb4fa3c4fc0 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 20 Oct 2026 16:48:34 +0900 Subject: [PATCH 03/14] feat(browser): show trace steps timeline on terminal on test failure (#11594) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- docs/config/browser/traceview.md | 2 +- .../guide/browser/snippets/trace-failure.ansi | 24 ++ docs/guide/browser/trace-view.md | 13 +- packages/vitest/src/node/reporters/base.ts | 11 + .../reporters/renderers/trace-timeline.ts | 207 ++++++++++++++++++ test/browser/specs/trace-timeline.test.ts | 164 ++++++++++++++ 6 files changed, 418 insertions(+), 3 deletions(-) create mode 100644 docs/guide/browser/snippets/trace-failure.ansi create mode 100644 packages/vitest/src/node/reporters/renderers/trace-timeline.ts create mode 100644 test/browser/specs/trace-timeline.test.ts diff --git a/docs/config/browser/traceview.md b/docs/config/browser/traceview.md index a186eded7..fb8b38b1c 100644 --- a/docs/config/browser/traceview.md +++ b/docs/config/browser/traceview.md @@ -9,7 +9,7 @@ outline: deep - **CLI:** `--browser.traceView` - **Default:** `false` -Enable trace-view collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. +Enable [trace view](/guide/browser/trace-view) collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. When a test fails, the terminal output also includes a [timeline of its recorded steps](/guide/browser/trace-view#failure-output). ```ts export default defineConfig({ diff --git a/docs/guide/browser/snippets/trace-failure.ansi b/docs/guide/browser/snippets/trace-failure.ansi new file mode 100644 index 000000000..eebc87e5c --- /dev/null +++ b/docs/guide/browser/snippets/trace-failure.ansi @@ -0,0 +1,24 @@ + FAIL src/sign-in.test.ts > signs in +Error: expect(element).toHaveTextContent() + +Expected element to have text content: + Welcome +Received: + Sign in + + ❯ src/sign-in.test.ts:11:51 +  9| }) +  10| await page.getByRole('button', { name: 'Sign in' }).click() +  11| await expect.element(page.getByRole('heading')).toHaveTextContent('Welcome') +  | ^ +  12| }) + +Caused by: Error: Matcher did not succeed in time. + +Trace: + step locator start duration location + fill form -  +1ms 58ms src/sign-in.test.ts:6:14 + ├── fill getByLabel('Email')  +2ms 31ms src/sign-in.test.ts:7:40 + └── fill getByLabel('Password')  +34ms 24ms src/sign-in.test.ts:8:43 + click getByRole('button', { name: 'Sign in' }) +61ms 18ms src/sign-in.test.ts:10:55 + toHaveTextContent [ERROR] getByRole('heading')  +80ms 1003ms src/sign-in.test.ts:11:16 diff --git a/docs/guide/browser/trace-view.md b/docs/guide/browser/trace-view.md index e4863f101..bbe0a3ee7 100644 --- a/docs/guide/browser/trace-view.md +++ b/docs/guide/browser/trace-view.md @@ -2,7 +2,7 @@ `browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. -Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. +Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. When a test fails, the terminal output also includes a text timeline of its recorded steps. ::: tip Trace view, browser UI, and HTML reports @@ -44,7 +44,7 @@ vitest --browser.traceView ::: -When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). +When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). Failed tests also print their recorded steps in the terminal, as described in [Failure Output](#failure-output). With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. @@ -81,6 +81,7 @@ TODO: The browser UI / Vitest UI / browser driver combinations are not specific | Debug locally with a headless browser | `vitest --browser.traceView --browser.headless --ui` | The browser runs headless, while Vitest UI shows recorded trace steps and snapshots. | | Debug locally with a visible browser window and Vitest UI | `vitest --browser.traceView --browser.headless=false --browser.ui=false --ui` | Vitest UI shows recorded trace steps and snapshots, while tests run in a separate headed browser window. | | Generate a static report for CI or run mode | `vitest run --browser.traceView --reporter=html` | The HTML report includes the trace viewer for recorded tests. | +| See failure steps in CI logs or from a coding agent | `vitest run --browser.traceView` | The failure summary in the terminal includes a timeline of the steps that led to each failure. | ## Relation to Playwright Traces @@ -107,6 +108,14 @@ Each entry captures the DOM state at that point, along with timing information, In Vitest UI, trace entries are streamed as the test runs, so you can inspect recorded steps before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries appear as in-progress steps first, then update with their final status and duration. +## Failure Output + +When a test fails, the error in the terminal is followed by a timeline of its recorded steps: + +<<< ./snippets/trace-failure.ansi [Error report] + +These are the same steps the trace viewer lists, without DOM snapshots. Only the last 10 steps are printed, so open the trace viewer to see the full trace. + ## Custom Trace Entries You can insert your own named entries with `page.mark()` and `locator.mark()`: diff --git a/packages/vitest/src/node/reporters/base.ts b/packages/vitest/src/node/reporters/base.ts index 97c739b9e..d790443bf 100644 --- a/packages/vitest/src/node/reporters/base.ts +++ b/packages/vitest/src/node/reporters/base.ts @@ -44,6 +44,7 @@ import { renderBenchmarkRow, } from './renderers/benchmark-table' import { F_CHECK, F_DOWN_RIGHT, F_POINTER } from './renderers/figures' +import { formatTraceTimeline } from './renderers/trace-timeline' import { countTestErrors, divider, @@ -1529,6 +1530,16 @@ export abstract class BaseReporter implements Reporter { screenshotPaths, }) + // TODO: print the trace of the attempt that produced this error instead of the last attempt, + // using `task.result.attempts` (https://github.com/vitest-dev/vitest/pull/11232) + for (const task of tasks) { + const timeline = + task.type === 'test' && formatTraceTimeline(task.artifacts, this.ctx.config.root) + if (timeline) { + this.error(`${timeline}\n`) + } + } + if (tasks[0].type === 'test' && tasks[0].annotations.length) { const test = this.ctx.state.getReportedEntity(tasks[0]) as TestCase this.printAnnotations(test, 'error', 1) diff --git a/packages/vitest/src/node/reporters/renderers/trace-timeline.ts b/packages/vitest/src/node/reporters/renderers/trace-timeline.ts new file mode 100644 index 000000000..a8680b9b8 --- /dev/null +++ b/packages/vitest/src/node/reporters/renderers/trace-timeline.ts @@ -0,0 +1,207 @@ +import type { TestArtifact } from '../../../runtime/runner/types' +import { relative } from 'pathe' +import c from 'tinyrainbow' +import { F_TREE_NODE_END, F_TREE_NODE_MIDDLE } from './figures' + +interface TimelineRow { + cells: [name: string, locator: string, start: string, duration: string, location: string] + status?: 'pass' | 'fail' +} + +const MAX_STEPS = 10 + +export function formatTraceTimeline(artifacts: TestArtifact[], root: string): string | undefined { + const attempt = [...getTraceAttemptMap(artifacts).values()].at(-1) + if (!attempt) { + return + } + + // the test end is already shown by the error location + const visible = attempt.entries.filter((step) => step.name !== 'vitest:onAfterRetryTask') + if (!visible.length) { + return + } + const shown = visible.slice(-MAX_STEPS) + const prefixes = getTreePrefixes(shown) + const headerRow: TimelineRow = { cells: ['step', 'locator', 'start', 'duration', 'location'] } + const rows = [headerRow, ...shown.map((step, i) => formatStep(step, prefixes[i], root))] + + const widths = headerRow.cells.map((_, i) => Math.max(...rows.map((row) => row.cells[i].length))) + const lines = rows.map((row) => { + // the last column is left unpadded so rows have no trailing spaces inside its color codes + const [name, locator, start, duration, location] = row.cells.map((cell, i) => + i === 4 ? cell : i === 2 || i === 3 ? cell.padStart(widths[i]) : cell.padEnd(widths[i]), + ) + if (row === headerRow) { + return c.dim(` ${name} ${locator} ${start} ${duration} ${location}`) + } + const text = ` ${name} ${colorCell(locator, c.cyan)} ${start} ${colorCell(duration)} ${colorCell(location, c.dim)}` + return row.status === 'fail' ? c.red(text) : text + }) + const omitted = visible.length - shown.length + if (omitted) { + lines.splice(1, 0, c.dim(` ... ${omitted} earlier ${omitted === 1 ? 'step' : 'steps'}`)) + } + + const header: string[] = [] + if (attempt.retry) { + header.push(`retry ${attempt.retry}`) + } + if (attempt.repeats) { + header.push(`repeat ${attempt.repeats}`) + } + const title = header.length ? `Trace (${header.join(', ')}):` : 'Trace:' + return [`\n${title}`, ...lines].join('\n') +} + +function getTreePrefixes(steps: NormalizedBrowserTraceEntry[]): string[] { + const hasNextSibling = steps.map((step, i) => { + const next = steps.slice(i + 1).find((other) => other.depth <= step.depth) + return next?.depth === step.depth + }) + const lastAtDepth: number[] = [] + return steps.map((step, i) => { + lastAtDepth[step.depth] = i + if (step.depth === 0) { + return '' + } + let prefix = '' + for (let level = 1; level < step.depth; level++) { + prefix += hasNextSibling[lastAtDepth[level]] ? '│ ' : ' ' + } + return `${prefix}${hasNextSibling[i] ? F_TREE_NODE_MIDDLE : F_TREE_NODE_END} ` + }) +} + +function formatStep(step: NormalizedBrowserTraceEntry, prefix: string, root: string): TimelineRow { + const location = + step.location && + `${relative(root, step.location.file)}:${step.location.line}:${step.location.column}` + return { + cells: [ + `${prefix}${formatStepName(step.name)}`, + step.element?.locator || '-', + `+${formatMs(step.startTime)}`, + step.duration == null ? '-' : formatMs(step.duration), + location || '-', + ], + status: step.status, + } +} + +function colorCell(cell: string, format = (text: string) => text): string { + return cell.trim() === '-' ? c.dim(cell) : format(cell) +} + +function formatMs(ms: number): string { + return `${Math.round(ms)}ms` +} + +function formatStepName(name: string): string { + return name.startsWith('vitest:') ? name.slice('vitest:'.length) : name +} + +// TODO: everything below is copied from `BrowserTraceData`/`BrowserTraceEntry` in +// packages/browser/src/client/tester/trace.ts (only the fields used here) and +// `getTraceAttemptMap` in packages/ui/client/composables/trace-view.ts. +// share them instead (https://github.com/vitest-dev/vitest/pull/11604) + +interface BrowserTraceData { + retry: number + repeats: number + entries: BrowserTraceEntry[] +} + +interface BrowserTraceEntry { + name: string + range?: { id: string; phase: 'start' | 'end' } + status?: 'pass' | 'fail' + startTime: number + duration?: number + location?: { file: string; line: number; column: number } + element?: { locator: string } +} + +interface NormalizedBrowserTraceData extends BrowserTraceData { + entries: NormalizedBrowserTraceEntry[] +} + +interface NormalizedBrowserTraceEntry extends BrowserTraceEntry { + depth: number +} + +function getTraceAttemptKey(trace: BrowserTraceData): string { + return `${trace.repeats}:${trace.retry}` +} + +function normalizeTraceEntries(entries: BrowserTraceEntry[]): NormalizedBrowserTraceEntry[] { + const merged: NormalizedBrowserTraceEntry[] = [] + const startMap = new Map() + const parents: string[] = [] + + for (const entry of entries) { + const range = entry.range + if (!range) { + merged.push({ + ...entry, + depth: parents.length, + }) + continue + } + + if (range.phase === 'start') { + startMap.set(range.id, merged.length) + merged.push({ + ...entry, + depth: parents.length, + }) + parents.push(range.id) + continue + } + + // when range.phase === 'end' + const index = startMap.get(range.id) + if (index == null) { + // unpaired range shouldn't happen but just leave it there + merged.push({ ...entry, depth: 0 }) + continue + } + + // Keep the start timestamp for positioning, but derive display duration + // from the end event timestamp. + const start = merged[index] + merged[index] = { + ...start, + ...entry, + startTime: start.startTime, + duration: entry.startTime - start.startTime, + } + parents.pop() + } + + return merged +} + +function getTraceAttemptMap(artifacts: TestArtifact[]): Map { + const grouped: Record = {} + for (const artifact of artifacts) { + if (artifact.type !== 'internal:browserTrace') { + continue + } + const trace = artifact.data as BrowserTraceData + const key = getTraceAttemptKey(trace) + grouped[key] ??= [] + grouped[key].push(trace) + } + + const merged = new Map() + for (const [key, traces] of Object.entries(grouped)) { + const trace = traces[0] + const entries = traces.flatMap((trace) => trace.entries) + merged.set(key, { + ...trace, + entries: normalizeTraceEntries(entries), + }) + } + return merged +} diff --git a/test/browser/specs/trace-timeline.test.ts b/test/browser/specs/trace-timeline.test.ts new file mode 100644 index 000000000..2d75d79eb --- /dev/null +++ b/test/browser/specs/trace-timeline.test.ts @@ -0,0 +1,164 @@ +import type { TestFsStructure } from '../../test-utils' +import { expect, test } from 'vitest' +import { instances, runInlineBrowserTests } from './utils' + +async function runBrowserTests(structure: TestFsStructure) { + return runInlineBrowserTests(structure, { + reporters: 'default', + browser: { + ui: false, + instances: instances.slice(0, 1), + traceView: true, + screenshotFailures: false, + }, + }) +} + +function extractTimelines(stderr: string) { + return [...stderr.matchAll(/\nTrace\b.*?\n\n/gs)].map((match) => { + const [title, ...lines] = match[0].trim().split('\n') + const rows = lines.map((line) => + line + .trim() + .replace(/\d+(\.\d+)?m?s\b/g, '