Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid caching transform result of invalidated module #7254

Merged
merged 1 commit into from Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 13 additions & 3 deletions packages/vite/src/node/server/moduleGraph.ts
Expand Up @@ -32,6 +32,7 @@ export class ModuleNode {
ssrTransformResult: TransformResult | null = null
ssrModule: Record<string, any> | null = null
lastHMRTimestamp = 0
lastInvalidationTimestamp = 0

constructor(url: string) {
this.url = url
Expand Down Expand Up @@ -94,17 +95,26 @@ export class ModuleGraph {
}
}

invalidateModule(mod: ModuleNode, seen: Set<ModuleNode> = new Set()): void {
mod.info = undefined
invalidateModule(
mod: ModuleNode,
seen: Set<ModuleNode> = 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<ModuleNode>()
this.idToModuleMap.forEach((mod) => {
this.invalidateModule(mod, seen)
this.invalidateModule(mod, seen, timestamp)
})
}

Expand Down
46 changes: 34 additions & 12 deletions packages/vite/src/node/server/transformRequest.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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
}