diff --git a/packages/vite/src/node/__tests__/optimizer/discoverBeforeListen.spec.ts b/packages/vite/src/node/__tests__/optimizer/discoverBeforeListen.spec.ts index 22fa56ee1ba8b2..df883c9fb6ed09 100644 --- a/packages/vite/src/node/__tests__/optimizer/discoverBeforeListen.spec.ts +++ b/packages/vite/src/node/__tests__/optimizer/discoverBeforeListen.spec.ts @@ -90,3 +90,35 @@ test('does not crash when a dep is discovered before the server starts listening await ssr.depsOptimizer?.scanProcessing expect(errors).toStrictEqual([]) }) + +// regression test for https://github.com/vitejs/vite/issues/23143 +// When a module importing a bare specifier is transformed without calling `server.listen()`, +// calling `server.close()` should not hang waiting for optimizer processing promises. +test('server.close() resolves promptly when deps were discovered without calling listen()', async () => { + server = await createServer({ + configFile: false, + root: path.join( + import.meta.dirname, + '../fixtures/optimizer-discover-before-listen', + ), + environments: { + client: { + optimizeDeps: { + force: true, + }, + }, + }, + }) + + await server.environments.client.transformRequest('/entry.js') + + const closePromise = server.close() + const timeoutPromise = setTimeout(2000).then(() => { + throw new Error('server.close() timed out') + }) + + await expect( + Promise.race([closePromise, timeoutPromise]), + ).resolves.toBeUndefined() + server = undefined +}) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 9289c5327feb69..26719ded616a86 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -68,6 +68,7 @@ export interface DepsOptimizer { getOptimizedDepId: (depInfo: OptimizedDepInfo) => string close: () => Promise + isClosed?: () => boolean options: DepOptimizationOptions } diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index b6a63c038d3614..02d8a5687c88a4 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -66,6 +66,7 @@ export function createDepsOptimizer( getOptimizedDepId: (depInfo: OptimizedDepInfo) => `${depInfo.file}?v=${depInfo.browserHash}`, close, + isClosed: () => closed, options, } @@ -147,6 +148,16 @@ export function createDepsOptimizer( async function close() { closed = true + if (debounceProcessingHandle) { + clearTimeout(debounceProcessingHandle) + debounceProcessingHandle = undefined + } + if (newDepsToLogHandle) { + clearTimeout(newDepsToLogHandle) + newDepsToLogHandle = undefined + } + resolveEnqueuedProcessingPromises() + depOptimizationProcessing.resolve() await Promise.allSettled([ discover?.cancel(), depsOptimizer.scanProcessing, @@ -753,6 +764,7 @@ export function createDepsOptimizer( export function createExplicitDepsOptimizer( environment: DevEnvironment, ): DepsOptimizer { + let closed = false const depsOptimizer = { metadata: initDepsOptimizerMetadata(environment), isOptimizedDepFile: createIsOptimizedDepFile(environment), @@ -770,7 +782,10 @@ export function createExplicitDepsOptimizer( // the optimizer blocks the server start run: () => {}, - close: async () => {}, + close: async () => { + closed = true + }, + isClosed: () => closed, options: environment.config.optimizeDeps, } diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index cde478bf2c6839..2e68267b31da8a 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -100,6 +100,9 @@ export function optimizedDepsPlugin(): Plugin { } return code } catch { + if (depsOptimizer?.isClosed?.()) { + return null + } if ( browserHash && !environment.config.optimizeDeps.ignoreOutdatedRequests