Skip to content

Commit

Permalink
fix: pending requests after module invalidation (#7283)
Browse files Browse the repository at this point in the history
  • Loading branch information
patak-dev committed Mar 13, 2022
1 parent 8b945f5 commit a1044d7
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 27 deletions.
9 changes: 8 additions & 1 deletion packages/vite/src/node/server/index.ts
Expand Up @@ -297,7 +297,14 @@ export interface ViteDevServer {
/**
* @internal
*/
_pendingRequests: Map<string, Promise<TransformResult | null>>
_pendingRequests: Map<
string,
{
request: Promise<TransformResult | null>
timestamp: number
abort: () => void
}
>
}

export async function createServer(
Expand Down
91 changes: 65 additions & 26 deletions packages/vite/src/node/server/transformRequest.ts
Expand Up @@ -44,22 +44,80 @@ export function transformRequest(
options: TransformOptions = {}
): Promise<TransformResult | null> {
const cacheKey = (options.ssr ? 'ssr:' : options.html ? 'html:' : '') + url
let request = server._pendingRequests.get(cacheKey)
if (!request) {
request = doTransform(url, server, options)
server._pendingRequests.set(cacheKey, request)
const done = () => server._pendingRequests.delete(cacheKey)
request.then(done, done)

// 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.
//
// We save the timestap when we start processing and compare it with the
// last time this module is invalidated
const timestamp = Date.now()

const pending = server._pendingRequests.get(cacheKey)
if (pending) {
return server.moduleGraph
.getModuleByUrl(removeTimestampQuery(url), options.ssr)
.then((module) => {
if (!module || pending.timestamp > module.lastInvalidationTimestamp) {
// The pending request is still valid, we can safely reuse its result
return pending.request
} else {
// Request 1 for module A (pending.timestamp)
// Invalidate module A (module.lastInvalidationTimestamp)
// Request 2 for module A (timestamp)

// First request has been invalidated, abort it to clear the cache,
// then perform a new doTransform.
pending.abort()
return transformRequest(url, server, options)
}
})
}

const request = doTransform(url, server, options, timestamp)

// Avoid clearing the cache of future requests if aborted
let cleared = false
const clearCache = () => {
if (!cleared) {
server._pendingRequests.delete(cacheKey)
cleared = true
}
}

// Cache the request and clear it once processing is done
server._pendingRequests.set(cacheKey, {
request,
timestamp,
abort: clearCache
})
request.then(clearCache, clearCache)

return request
}

async function doTransform(
url: string,
server: ViteDevServer,
options: TransformOptions
options: TransformOptions,
timestamp: number
) {
url = removeTimestampQuery(url)

const { config, pluginContainer, moduleGraph, watcher } = server
const { root, logger } = config
const prettyUrl = isDebug ? prettifyUrl(url, root) : ''
Expand All @@ -81,25 +139,6 @@ 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

0 comments on commit a1044d7

Please sign in to comment.