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: pending requests after module invalidation #7283

Merged
merged 2 commits into from Mar 13, 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
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