diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts index f09e741f430983..44e76deef98b6f 100644 --- a/packages/vite/src/node/server/moduleGraph.ts +++ b/packages/vite/src/node/server/moduleGraph.ts @@ -32,6 +32,7 @@ export class ModuleNode { ssrTransformResult: TransformResult | null = null ssrModule: Record | null = null lastHMRTimestamp = 0 + lastInvalidationTimestamp = 0 constructor(url: string) { this.url = url @@ -94,17 +95,26 @@ export class ModuleGraph { } } - invalidateModule(mod: ModuleNode, seen: Set = new Set()): void { - mod.info = undefined + invalidateModule( + mod: ModuleNode, + seen: Set = new Set(), + timestamp: number = Date.now() + ): void { + // Save the timestamp for this invalidation, so we can avoid caching the result of possible already started + // processing being done for this module + mod.lastInvalidationTimestamp = timestamp + // Don't invalidate mod.info and mod.meta, as they are part of the processing pipeline + // Invalidating the transform result is enough to ensure this module is re-processed next time it is requested mod.transformResult = null mod.ssrTransformResult = null invalidateSSRModule(mod, seen) } invalidateAll(): void { + const timestamp = Date.now() const seen = new Set() this.idToModuleMap.forEach((mod) => { - this.invalidateModule(mod, seen) + this.invalidateModule(mod, seen, timestamp) }) } diff --git a/packages/vite/src/node/server/transformRequest.ts b/packages/vite/src/node/server/transformRequest.ts index 2433470cbf4b33..100c9a80b6691d 100644 --- a/packages/vite/src/node/server/transformRequest.ts +++ b/packages/vite/src/node/server/transformRequest.ts @@ -81,6 +81,25 @@ async function doTransform( return cached } + // This module may get invalidated while we are processing it. For example + // when a full page reload is needed after the re-processing of pre-bundled + // dependencies when a missing dep is discovered. We save the current time + // to compare it to the last invalidation performed to know if we should + // cache the result of the transformation or we should discard it as stale. + // + // A module can be invalidated due to: + // 1. A full reload because of pre-bundling newly discovered deps + // 2. A full reload after a config change + // 3. The file that generated the module changed + // 4. Invalidation for a virtual module + // + // For 1 and 2, a new request for this module will be issued after + // the invalidation as part of the browser reloading the page. For 3 and 4 + // there may not be a new request right away because of HMR handling. + // In all cases, the next time this module is requested, it should be + // re-processed. + const timestamp = Date.now() + // resolve const id = (await pluginContainer.resolveId(url, undefined, { ssr }))?.id || url @@ -179,17 +198,20 @@ async function doTransform( } } - if (ssr) { - return (mod.ssrTransformResult = await ssrTransform( - code, - map as SourceMap, - url - )) - } else { - return (mod.transformResult = { - code, - map, - etag: getEtag(code, { weak: true }) - } as TransformResult) + const result = ssr + ? await ssrTransform(code, map as SourceMap, url) + : ({ + code, + map, + etag: getEtag(code, { weak: true }) + } as TransformResult) + + // Only cache the result if the module wasn't invalidated while it was + // being processed, so it is re-processed next time if it is stale + if (timestamp > mod.lastInvalidationTimestamp) { + if (ssr) mod.ssrTransformResult = result + else mod.transformResult = result } + + return result }