diff --git a/packages/vite-node/src/server.ts b/packages/vite-node/src/server.ts index 9b3f28a65f5b..69f848b9e65f 100644 --- a/packages/vite-node/src/server.ts +++ b/packages/vite-node/src/server.ts @@ -1,4 +1,5 @@ import type { TransformResult, ViteDevServer } from 'vite' +import type { FetchResult } from '..' import { shouldExternalize } from './externalize' import type { ViteNodeResolveId, ViteNodeServerOptions } from './types' import { toFilePath, withInlineSourcemap } from './utils' @@ -6,7 +7,12 @@ import { toFilePath, withInlineSourcemap } from './utils' export * from './externalize' export class ViteNodeServer { - promiseMap = new Map>() + private fetchPromiseMap = new Map>() + private transformPromiseMap = new Map>() + private fetchCache = new Map() constructor( public server: ViteDevServer, @@ -17,32 +23,37 @@ export class ViteNodeServer { return shouldExternalize(id, this.options.deps) } - async fetchModule(id: string) { - const externalize = await this.shouldExternalize(toFilePath(id, this.server.config.root)) - if (externalize) - return { externalize } - const r = await this.transformRequest(id) - return { code: r?.code } - } - async resolveId(id: string, importer?: string): Promise { return this.server.pluginContainer.resolveId(id, importer, { ssr: true }) } + async fetchModule(id: string): Promise { + // reuse transform for concurrent requests + if (!this.fetchPromiseMap.has(id)) { + this.fetchPromiseMap.set(id, + this._fetchModule(id) + .finally(() => { + this.fetchPromiseMap.delete(id) + }), + ) + } + return this.fetchPromiseMap.get(id)! + } + async transformRequest(id: string) { // reuse transform for concurrent requests - if (!this.promiseMap.has(id)) { - this.promiseMap.set(id, + if (!this.transformPromiseMap.has(id)) { + this.transformPromiseMap.set(id, this._transformRequest(id) .finally(() => { - this.promiseMap.delete(id) + this.transformPromiseMap.delete(id) }), ) } - return this.promiseMap.get(id) + return this.transformPromiseMap.get(id)! } - private getTransformMode(id: string) { + getTransformMode(id: string) { const withoutQuery = id.split('?')[0] if (this.options.transformMode?.web?.some(r => withoutQuery.match(r))) @@ -55,11 +66,37 @@ export class ViteNodeServer { return 'web' } + private async _fetchModule(id: string): Promise { + let result: FetchResult + + const timestamp = this.server.moduleGraph.getModuleById(id)?.lastHMRTimestamp + const cache = this.fetchCache.get(id) + if (timestamp && cache && cache.timestamp >= timestamp) + return cache.result + + const externalize = await this.shouldExternalize(toFilePath(id, this.server.config.root)) + if (externalize) { + result = { externalize } + } + else { + const r = await this._transformRequest(id) + result = { code: r?.code } + } + + if (timestamp) { + this.fetchCache.set(id, { + timestamp, + result, + }) + } + + return result + } + private async _transformRequest(id: string) { let result: TransformResult | null = null - const mode = this.getTransformMode(id) - if (mode === 'web') { + if (this.getTransformMode(id) === 'web') { // for components like Vue, we want to use the client side // plugins but then covert the code to be consumed by the server result = await this.server.transformRequest(id) @@ -73,10 +110,6 @@ export class ViteNodeServer { if (this.options.sourcemap !== false && result && !id.includes('node_modules')) withInlineSourcemap(result) - // if (result?.map && process.env.NODE_V8_COVERAGE) - // visitedFilesMap.set(toFilePath(id, config.root), result.map as any) - return result } } - diff --git a/packages/vite-node/src/types.ts b/packages/vite-node/src/types.ts index 31f67b09fb95..1554242b1e40 100644 --- a/packages/vite-node/src/types.ts +++ b/packages/vite-node/src/types.ts @@ -8,7 +8,12 @@ export interface DepsHandlingOptions { fallbackCJS?: boolean } -export type FetchFunction = (id: string) => Promise<{ code?: string; externalize?: string }> +export interface FetchResult { + code?: string + externalize?: string +} + +export type FetchFunction = (id: string) => Promise export type ResolveIdFunction = (id: string, importer?: string) => Promise export interface ModuleCache { diff --git a/packages/vite-node/src/utils.ts b/packages/vite-node/src/utils.ts index df13ec24703c..5a43570eef16 100644 --- a/packages/vite-node/src/utils.ts +++ b/packages/vite-node/src/utils.ts @@ -43,7 +43,6 @@ export function toFilePath(id: string, root: string): string { : absolute } - let SOURCEMAPPING_URL = 'sourceMa' SOURCEMAPPING_URL += 'ppingURL' diff --git a/packages/vitest/src/types/worker.ts b/packages/vitest/src/types/worker.ts index ab0a4aa18b41..ec5c79700e82 100644 --- a/packages/vitest/src/types/worker.ts +++ b/packages/vitest/src/types/worker.ts @@ -1,5 +1,5 @@ import type { MessagePort } from 'worker_threads' -import type { ViteNodeResolveId } from 'vite-node' +import type { FetchFunction, ViteNodeResolveId } from 'vite-node' import type { RawSourceMap } from '../types' import type { ResolvedConfig } from './config' import type { File, TaskResultPack } from './tasks' @@ -13,7 +13,6 @@ export interface WorkerContext { invalidates?: string[] } -export type FetchFunction = (id: string) => Promise<{ code?: string; externalize?: string }> export type ResolveIdFunction = (id: string, importer?: string) => Promise export interface WorkerRPC {