From 9e24a59f2c9dbd44439e38968c6c6eb216ebd50a Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 30 Oct 2025 17:11:45 +0100 Subject: [PATCH] fix(browser): support module tracking (#8877) --- .../src/client/public/esm-client-injector.js | 41 ++++++++++++------- packages/browser/src/client/tester/state.ts | 5 ++- packages/browser/src/client/utils.ts | 4 +- test/browser/src/dynamic.ts | 3 ++ test/browser/test/dynamic-module.test.ts | 9 ++++ 5 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 test/browser/src/dynamic.ts create mode 100644 test/browser/test/dynamic-module.test.ts diff --git a/packages/browser/src/client/public/esm-client-injector.js b/packages/browser/src/client/public/esm-client-injector.js index 790544f0f..0e0a0d9d2 100644 --- a/packages/browser/src/client/public/esm-client-injector.js +++ b/packages/browser/src/client/public/esm-client-injector.js @@ -1,26 +1,37 @@ (() => { - const moduleCache = new Map(); - - function wrapModule(module) { - if (typeof module === "function") { - const promise = new Promise((resolve, reject) => { - if (typeof __vitest_mocker__ === "undefined") - return module().then(resolve, reject); - __vitest_mocker__.prepare().finally(() => { - module().then(resolve, reject); - }); - }); - moduleCache.set(promise, { promise, evaluated: false }); - return promise.finally(() => moduleCache.delete(promise)); + function wrapModule(moduleCallback) { + if (typeof moduleCallback !== "function") { + return moduleCallback + } + + if (typeof __vitest_mocker__ === "undefined" || typeof __vitest_worker__ === 'undefined') { + return moduleCallback() } - return module; + + const { evaluatedModules } = __vitest_worker__ + const moduleId = crypto.randomUUID() + const viteModule = evaluatedModules.ensureModule(moduleId, moduleId) + + viteModule.evaluated = false + viteModule.promise = new Promise((resolve, reject) => { + __vitest_mocker__.prepare().finally(() => { + moduleCallback().then(resolve, reject) + }); + }); + return viteModule.promise.finally(() => { + viteModule.evaluated = true + viteModule.promise = undefined + + evaluatedModules.idToModuleMap.delete(viteModule.id) + evaluatedModules.fileToModulesMap.delete(viteModule.file) + evaluatedModules.urlToIdModuleMap.delete(viteModule.url) + }); } window.__vitest_browser_runner__ = { wrapModule, wrapDynamicImport: wrapModule, disposeExceptionTracker: () => {}, - moduleCache, cleanups: [], config: { __VITEST_CONFIG__ }, viteConfig: { __VITEST_VITE_CONFIG__ }, diff --git a/packages/browser/src/client/tester/state.ts b/packages/browser/src/client/tester/state.ts index ae33c85b6..7f02ed92e 100644 --- a/packages/browser/src/client/tester/state.ts +++ b/packages/browser/src/client/tester/state.ts @@ -1,5 +1,6 @@ import type { BrowserRPC } from '@vitest/browser/client' import type { WorkerGlobalState } from 'vitest' +import { EvaluatedModules } from 'vite/module-runner' import { getBrowserState } from '../utils' const config = getBrowserState().config @@ -30,8 +31,8 @@ const state: WorkerGlobalState = { }, }, onCleanup: fn => getBrowserState().cleanups.push(fn), - evaluatedModules: getBrowserState().evaluatedModules, - resolvingModules: getBrowserState().resolvingModules, + evaluatedModules: new EvaluatedModules(), + resolvingModules: new Set(), moduleExecutionInfo: new Map(), metaEnv: null as any, rpc: null as any, diff --git a/packages/browser/src/client/utils.ts b/packages/browser/src/client/utils.ts index ee72e4606..5c1f9501e 100644 --- a/packages/browser/src/client/utils.ts +++ b/packages/browser/src/client/utils.ts @@ -1,5 +1,5 @@ import type { VitestRunner } from '@vitest/runner' -import type { EvaluatedModules, SerializedConfig, WorkerGlobalState } from 'vitest' +import type { SerializedConfig, WorkerGlobalState } from 'vitest' import type { IframeOrchestrator } from './orchestrator' import type { CommandsManager } from './tester/tester-utils' @@ -65,8 +65,6 @@ export function ensureAwaited(promise: (error?: Error) => Promise): Promis export interface BrowserRunnerState { files: string[] runningFiles: string[] - resolvingModules: Set - evaluatedModules: EvaluatedModules config: SerializedConfig provider: string runner: VitestRunner diff --git a/test/browser/src/dynamic.ts b/test/browser/src/dynamic.ts new file mode 100644 index 000000000..f625bc94b --- /dev/null +++ b/test/browser/src/dynamic.ts @@ -0,0 +1,3 @@ +window.dispatchEvent(new CustomEvent('module:loaded')) + +export {} diff --git a/test/browser/test/dynamic-module.test.ts b/test/browser/test/dynamic-module.test.ts new file mode 100644 index 000000000..1e552e198 --- /dev/null +++ b/test/browser/test/dynamic-module.test.ts @@ -0,0 +1,9 @@ +import { expect, test, vi } from 'vitest' + +test('can await the end of the dynamic import', async () => { + const _promise = import('../src/dynamic') + const spy = vi.fn() + window.addEventListener('module:loaded', spy) + await vi.dynamicImportSettled() + expect(spy).toHaveBeenCalled() +}) -- 2.51.2