From 3d01484b9a9fb947521c213c5a369707aa689cda Mon Sep 17 00:00:00 2001 From: patak Date: Thu, 13 Apr 2023 23:20:15 +0200 Subject: [PATCH 1/8] fix: ensure single crawl end call in optimizer --- packages/vite/src/node/optimizer/index.ts | 5 + packages/vite/src/node/optimizer/optimizer.ts | 119 +++++++++++++----- playground/worker/classic-shared-worker.js | 2 +- playground/worker/classic-worker.js | 2 +- 4 files changed, 97 insertions(+), 31 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index ce06da9feef970..91727064d7b89a 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -504,6 +504,11 @@ export function runOptimizeDeps( metadata, cancel: cleanUp, commit: async () => { + if (cleaned) { + throw new Error( + 'Can not commit a Deps Optimization run as it was cancelled', + ) + } // Ignore clean up requests after this point so the temp folder isn't deleted before // we finish commiting the new deps cache files to the deps folder committed = true diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 1a5ce1169c5ec5..17751dcec370d3 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -174,6 +174,7 @@ async function createDepsOptimizer( async function close() { closed = true + crawlEndFinder.cancel() await Promise.allSettled([ discover?.cancel(), depsOptimizer.scanProcessing, @@ -582,14 +583,22 @@ async function createDepsOptimizer( }, timeout) } + // During dev, onCrawlEnd is called once when the server starts and all static + // imports after the first request have been crawled (dynamic imports may also + // be crawled if the browser requests them right away). + // During build, onCrawlEnd will be called once after each buildStart (so in + // watch mode it will be called after each rebuild has processed every module). + // All modules are transformed first in this case (both static and dynamic). + let crawlEndCalled = false async function onCrawlEnd() { + const wasCalledBefore = crawlEndCalled + crawlEndCalled = true + debug?.(colors.green(`✨ static imports crawl ended`)) - if (firstRunCalled) { + if (closed || firstRunCalled || wasCalledBefore) { return } - currentlyProcessing = false - const crawlDeps = Object.keys(metadata.discovered) // Await for the scan+optimize step running in the background @@ -599,6 +608,7 @@ async function createDepsOptimizer( if (!isBuild && optimizationResult) { const result = await optimizationResult.result optimizationResult = undefined + currentlyProcessing = false const scanDeps = Object.keys(result.metadata.optimized) @@ -650,6 +660,8 @@ async function createDepsOptimizer( runOptimizer(result) } } else { + currentlyProcessing = false + if (crawlDeps.length === 0) { debug?.( colors.green( @@ -664,30 +676,65 @@ async function createDepsOptimizer( } } - const runOptimizerIfIdleAfterMs = 50 + let crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + + // Called during buildStart at build time, when build --watch is used. + // On dev, onCrawlEnd is called once. On build, we call it when all the + // user modules are processed for every rebuild. + function resetRegisteredIds() { + crawlEndCalled = false + crawlEndFinder.cancel() + crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + } + + function registerWorkersSource(id: string) { + crawlEndFinder.registerWorkersSource(id) + } + function delayDepsOptimizerUntil(id: string, done: () => Promise) { + if (!crawlEndCalled && !depsOptimizer.isOptimizedDepFile(id)) { + crawlEndFinder.delayDepsOptimizerUntil(id, done) + } + } + function ensureFirstRun() { + if (!firstRunCalled) crawlEndFinder.ensureFirstRun() + } +} + +const runOptimizerIfIdleAfterMs = 50 +function setupOnCrawlEnd(onCrawlEnd: () => void): { + ensureFirstRun: () => void + registerWorkersSource: (id: string) => void + delayDepsOptimizerUntil: (id: string, done: () => Promise) => void + cancel: () => void +} { let registeredIds: { id: string; done: () => Promise }[] = [] - let seenIds = new Set() - let workersSources = new Set() - const waitingOn = new Set() + const seenIds = new Set() + const workersSources = new Set() + const waitingOn = new Map void>() let firstRunEnsured = false + let crawlEndCalled = false - function resetRegisteredIds() { - registeredIds = [] - seenIds = new Set() - workersSources = new Set() - waitingOn.clear() - firstRunEnsured = false + let cancelled = false + function cancel() { + cancelled = true + } + + function callOnCrawlEnd() { + if (!cancelled && !crawlEndCalled) { + crawlEndCalled = true + onCrawlEnd() + } } // If all the inputs are dependencies, we aren't going to get any // delayDepsOptimizerUntil(id) calls. We need to guard against this // by forcing a rerun if no deps have been registered function ensureFirstRun() { - if (!firstRunEnsured && !firstRunCalled && registeredIds.length === 0) { + if (!firstRunEnsured && seenIds.size === 0) { setTimeout(() => { - if (!closed && registeredIds.length === 0) { - onCrawlEnd() + if (seenIds.size === 0) { + callOnCrawlEnd() } }, runOptimizerIfIdleAfterMs) } @@ -699,37 +746,44 @@ async function createDepsOptimizer( // Avoid waiting for this id, as it may be blocked by the rollup // bundling process of the worker that also depends on the optimizer registeredIds = registeredIds.filter((registered) => registered.id !== id) - if (waitingOn.has(id)) { - waitingOn.delete(id) - runOptimizerWhenIdle() - } + + const resolve = waitingOn.get(id) + // Forced resolve to avoid waiting for the bundling of the worker to finish + resolve?.() } function delayDepsOptimizerUntil(id: string, done: () => Promise): void { - if (!depsOptimizer.isOptimizedDepFile(id) && !seenIds.has(id)) { + if (!seenIds.has(id)) { seenIds.add(id) registeredIds.push({ id, done }) - runOptimizerWhenIdle() + callOnCrawlEndWhenIdle() } } - async function runOptimizerWhenIdle() { - if (waitingOn.size > 0) return + async function callOnCrawlEndWhenIdle() { + if (cancelled || waitingOn.size > 0) return const processingRegisteredIds = registeredIds registeredIds = [] const donePromises = processingRegisteredIds.map(async (registeredId) => { - waitingOn.add(registeredId.id) + // During build, we need to cancel workers + let resolve: () => void + const waitUntilDone = new Promise((_resolve) => { + resolve = _resolve + registeredId.done().finally(() => resolve()) + }) + waitingOn.set(registeredId.id, () => resolve()) + try { - await registeredId.done() + await waitUntilDone } finally { waitingOn.delete(registeredId.id) } }) const afterLoad = () => { - if (closed) return + if (cancelled) return if ( registeredIds.length > 0 && registeredIds.every((registeredId) => @@ -740,9 +794,9 @@ async function createDepsOptimizer( } if (registeredIds.length > 0) { - runOptimizerWhenIdle() + callOnCrawlEndWhenIdle() } else { - onCrawlEnd() + callOnCrawlEnd() } } @@ -756,6 +810,13 @@ async function createDepsOptimizer( setTimeout(afterLoad, runOptimizerIfIdleAfterMs) } } + + return { + ensureFirstRun, + registerWorkersSource, + delayDepsOptimizerUntil, + cancel, + } } async function createDevSsrDepsOptimizer( diff --git a/playground/worker/classic-shared-worker.js b/playground/worker/classic-shared-worker.js index eed61e028f1e01..6f838d1951f360 100644 --- a/playground/worker/classic-shared-worker.js +++ b/playground/worker/classic-shared-worker.js @@ -1,5 +1,5 @@ let base = `/${self.location.pathname.split('/')[1]}` -if (base === `/worker-entries`) base = '' // relative base +if (base.endsWith('.js') || base === `/worker-entries`) base = '' // for dev importScripts(`${base}/classic.js`) diff --git a/playground/worker/classic-worker.js b/playground/worker/classic-worker.js index 55486818a8ada7..fdc5ec10c9ccf8 100644 --- a/playground/worker/classic-worker.js +++ b/playground/worker/classic-worker.js @@ -1,5 +1,5 @@ let base = `/${self.location.pathname.split('/')[1]}` -if (base === `/worker-entries`) base = '' // relative base +if (base.endsWith('.js') || base === `/worker-entries`) base = '' // for dev importScripts(`${base}/classic.js`) From 959e0cf02f706b82b50d8782777603a5ea342933 Mon Sep 17 00:00:00 2001 From: patak Date: Thu, 13 Apr 2023 23:39:52 +0200 Subject: [PATCH 2/8] fix: handle done() error --- packages/vite/src/node/optimizer/optimizer.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 17751dcec370d3..f8e13af68716bf 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -771,15 +771,17 @@ function setupOnCrawlEnd(onCrawlEnd: () => void): { let resolve: () => void const waitUntilDone = new Promise((_resolve) => { resolve = _resolve - registeredId.done().finally(() => resolve()) + registeredId + .done() + .catch(() => { + // Ignore errors + }) + .finally(() => resolve()) }) waitingOn.set(registeredId.id, () => resolve()) - try { - await waitUntilDone - } finally { - waitingOn.delete(registeredId.id) - } + await waitUntilDone + waitingOn.delete(registeredId.id) }) const afterLoad = () => { From b57c8852aef23245bc4d5c9347c466acb3345b4e Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 08:02:08 +0200 Subject: [PATCH 3/8] chore: donePromises never fail now --- packages/vite/src/node/optimizer/optimizer.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index f8e13af68716bf..bac801a09c1669 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -802,11 +802,8 @@ function setupOnCrawlEnd(onCrawlEnd: () => void): { } } - const results = await Promise.allSettled(donePromises) - if ( - registeredIds.length > 0 || - results.some((result) => result.status === 'rejected') - ) { + await Promise.allSettled(donePromises) + if (registeredIds.length > 0) { afterLoad() } else { setTimeout(afterLoad, runOptimizerIfIdleAfterMs) From 9ae3e9898f69d35e4a43ef7d68019ed80fcdcc82 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 08:47:05 +0200 Subject: [PATCH 4/8] fix: separate crawlEndFinder lifecyle from firstRunCalled flag --- packages/vite/src/node/optimizer/optimizer.ts | 63 ++++++++++++------- .../vite/src/node/plugins/optimizedDeps.ts | 8 ++- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index bac801a09c1669..1ab91b457c70a6 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -99,7 +99,7 @@ async function createDepsOptimizer( const cachedMetadata = await loadCachedDepOptimizationMetadata(config, ssr) - let handle: NodeJS.Timeout | undefined + let debounceProcessingHandle: NodeJS.Timeout | undefined let closed = false @@ -155,7 +155,15 @@ async function createDepsOptimizer( let enqueuedRerun: (() => void) | undefined let currentlyProcessing = false - // If there wasn't a cache or it is outdated, we need to prepare a first run + // During build, we wait for every module to be scanned before resolving + // optimized deps loading for rollup on each rebuild. + // During dev, if this is a cold run, we wait for static imports discovered + // from the first request before resolving to minimize full page reloads. + // On warm start or after the first optimization is run, we use a simpler + // debounce strategy each time a new dep is discovered. + // Initialized by resetRegisteredIds, called at buildStart + let crawlEndFinder: CrawlEndFinder | undefined + let firstRunCalled = !!cachedMetadata let optimizationResult: @@ -174,7 +182,7 @@ async function createDepsOptimizer( async function close() { closed = true - crawlEndFinder.cancel() + crawlEndFinder?.cancel() await Promise.allSettled([ discover?.cancel(), depsOptimizer.scanProcessing, @@ -291,7 +299,7 @@ async function createDepsOptimizer( enqueuedRerun = undefined // Ensure that a rerun will not be issued for current discovered deps - if (handle) clearTimeout(handle) + if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) if (closed || Object.keys(metadata.discovered).length === 0) { currentlyProcessing = false @@ -530,7 +538,12 @@ async function createDepsOptimizer( // we can get a list of every missing dependency before giving to the // browser a dependency that may be outdated, thus avoiding full page reloads - if (firstRunCalled) { + if (!crawlEndFinder) { + if (isBuild) { + logger.error( + 'Vite Internal Error: Missing dependency found after crawling ended', + ) + } // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps debouncedProcessing() @@ -571,11 +584,11 @@ async function createDepsOptimizer( // Debounced rerun, let other missing dependencies be discovered before // the running next optimizeDeps enqueuedRerun = undefined - if (handle) clearTimeout(handle) + if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) if (newDepsToLogHandle) clearTimeout(newDepsToLogHandle) newDepsToLogHandle = undefined - handle = setTimeout(() => { - handle = undefined + debounceProcessingHandle = setTimeout(() => { + debounceProcessingHandle = undefined enqueuedRerun = rerun if (!currentlyProcessing) { enqueuedRerun() @@ -589,13 +602,13 @@ async function createDepsOptimizer( // During build, onCrawlEnd will be called once after each buildStart (so in // watch mode it will be called after each rebuild has processed every module). // All modules are transformed first in this case (both static and dynamic). - let crawlEndCalled = false async function onCrawlEnd() { - const wasCalledBefore = crawlEndCalled - crawlEndCalled = true + // On build time, a missing dep appearing after onCrawlEnd is an internal error + // On dev, switch after this point to a simple debounce strategy + crawlEndFinder = undefined debug?.(colors.green(`✨ static imports crawl ended`)) - if (closed || firstRunCalled || wasCalledBefore) { + if (closed) { return } @@ -676,38 +689,42 @@ async function createDepsOptimizer( } } - let crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) - // Called during buildStart at build time, when build --watch is used. - // On dev, onCrawlEnd is called once. On build, we call it when all the - // user modules are processed for every rebuild. + // Once during dev mode. function resetRegisteredIds() { - crawlEndCalled = false - crawlEndFinder.cancel() + crawlEndFinder?.cancel() crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + + // Cancel on-fly queued reruns. This shouldn't currently happen but makes + // the API of the optimizer more robust in case resetRegisteredIds wouldn't + // be called before the first request in dev mode + if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) + enqueuedRerun = undefined } function registerWorkersSource(id: string) { - crawlEndFinder.registerWorkersSource(id) + crawlEndFinder?.registerWorkersSource(id) } function delayDepsOptimizerUntil(id: string, done: () => Promise) { - if (!crawlEndCalled && !depsOptimizer.isOptimizedDepFile(id)) { + if (crawlEndFinder && !depsOptimizer.isOptimizedDepFile(id)) { crawlEndFinder.delayDepsOptimizerUntil(id, done) } } function ensureFirstRun() { - if (!firstRunCalled) crawlEndFinder.ensureFirstRun() + crawlEndFinder?.ensureFirstRun() } } const runOptimizerIfIdleAfterMs = 50 -function setupOnCrawlEnd(onCrawlEnd: () => void): { +interface CrawlEndFinder { ensureFirstRun: () => void registerWorkersSource: (id: string) => void delayDepsOptimizerUntil: (id: string, done: () => Promise) => void cancel: () => void -} { +} + +function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder { let registeredIds: { id: string; done: () => Promise }[] = [] const seenIds = new Set() const workersSources = new Set() diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 93c53f82400cb2..eeb4d2226187f6 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -16,6 +16,12 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps', + buildStart() { + if (!config.isWorker) { + getDepsOptimizer(config)?.resetRegisteredIds() + } + }, + resolveId(id, source, { ssr }) { if (getDepsOptimizer(config, ssr)?.isOptimizedDepFile(id)) { return id @@ -82,8 +88,6 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { buildStart() { if (!config.isWorker) { - // This will be run for the current active optimizer, during build - // it will be the SSR optimizer if config.build.ssr is defined getDepsOptimizer(config)?.resetRegisteredIds() } }, From 1efca7c81d9b8146ed752069f0831377873086b4 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 09:26:37 +0200 Subject: [PATCH 5/8] fix: setup crawl end finder in dev only for cold start --- packages/vite/src/node/optimizer/optimizer.ts | 7 +++++-- packages/vite/src/node/plugins/optimizedDeps.ts | 6 ------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 1ab91b457c70a6..c2e091cc9f1985 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -155,6 +155,8 @@ async function createDepsOptimizer( let enqueuedRerun: (() => void) | undefined let currentlyProcessing = false + let firstRunCalled = !!cachedMetadata + // During build, we wait for every module to be scanned before resolving // optimized deps loading for rollup on each rebuild. // During dev, if this is a cold run, we wait for static imports discovered @@ -163,8 +165,9 @@ async function createDepsOptimizer( // debounce strategy each time a new dep is discovered. // Initialized by resetRegisteredIds, called at buildStart let crawlEndFinder: CrawlEndFinder | undefined - - let firstRunCalled = !!cachedMetadata + if (!isBuild && !cachedMetadata) { + crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) + } let optimizationResult: | { diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index eeb4d2226187f6..9c250e0acbf816 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -86,12 +86,6 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps-build', - buildStart() { - if (!config.isWorker) { - getDepsOptimizer(config)?.resetRegisteredIds() - } - }, - resolveId(id, importer, { ssr }) { if (getDepsOptimizer(config, ssr)?.isOptimizedDepFile(id)) { return id From 475131f6ecf90f59ce625a87ceb59e61a311a5f1 Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 09:35:00 +0200 Subject: [PATCH 6/8] chore: clean up --- packages/vite/src/node/optimizer/optimizer.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index c2e091cc9f1985..7bbd8c50d643fd 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -693,16 +693,9 @@ async function createDepsOptimizer( } // Called during buildStart at build time, when build --watch is used. - // Once during dev mode. function resetRegisteredIds() { crawlEndFinder?.cancel() crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) - - // Cancel on-fly queued reruns. This shouldn't currently happen but makes - // the API of the optimizer more robust in case resetRegisteredIds wouldn't - // be called before the first request in dev mode - if (debounceProcessingHandle) clearTimeout(debounceProcessingHandle) - enqueuedRerun = undefined } function registerWorkersSource(id: string) { From 13085c3a2fa8ae6498c924d559a2945f3610925a Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 10:00:30 +0200 Subject: [PATCH 7/8] fix: reset crawler on build, not during dev --- packages/vite/src/node/plugins/optimizedDeps.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 9c250e0acbf816..29eed85fa44420 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -16,12 +16,6 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps', - buildStart() { - if (!config.isWorker) { - getDepsOptimizer(config)?.resetRegisteredIds() - } - }, - resolveId(id, source, { ssr }) { if (getDepsOptimizer(config, ssr)?.isOptimizedDepFile(id)) { return id @@ -86,6 +80,12 @@ export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps-build', + buildStart() { + if (!config.isWorker) { + getDepsOptimizer(config)?.resetRegisteredIds() + } + }, + resolveId(id, importer, { ssr }) { if (getDepsOptimizer(config, ssr)?.isOptimizedDepFile(id)) { return id From c7c9e6c60e0d2b3a8d8bef8273f9f58978ade41a Mon Sep 17 00:00:00 2001 From: patak Date: Fri, 14 Apr 2023 13:39:40 +0200 Subject: [PATCH 8/8] fix: no missing deps when optimizing during build --- packages/vite/src/node/optimizer/optimizer.ts | 6 +-- packages/vite/src/node/plugins/index.ts | 16 ++++---- .../vite/src/node/plugins/optimizedDeps.ts | 40 +++++++++++++------ packages/vite/src/node/plugins/preAlias.ts | 3 +- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index 7bbd8c50d643fd..a7131db778cf1c 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -158,14 +158,14 @@ async function createDepsOptimizer( let firstRunCalled = !!cachedMetadata // During build, we wait for every module to be scanned before resolving - // optimized deps loading for rollup on each rebuild. + // optimized deps loading for rollup on each rebuild. It will be recreated + // after each buildStart. // During dev, if this is a cold run, we wait for static imports discovered // from the first request before resolving to minimize full page reloads. // On warm start or after the first optimization is run, we use a simpler // debounce strategy each time a new dep is discovered. - // Initialized by resetRegisteredIds, called at buildStart let crawlEndFinder: CrawlEndFinder | undefined - if (!isBuild && !cachedMetadata) { + if (isBuild || !cachedMetadata) { crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) } diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 1205befbc69b4b..fc2a80d51fbb61 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -40,6 +40,14 @@ export async function resolvePlugins( const { modulePreload } = config.build return [ + ...(isDepsOptimizerEnabled(config, false) || + isDepsOptimizerEnabled(config, true) + ? [ + isBuild + ? optimizedDepsBuildPlugin(config) + : optimizedDepsPlugin(config), + ] + : []), isWatch ? ensureWatchPlugin() : null, isBuild ? metadataPlugin() : null, watchPackageDataPlugin(config.packageCache), @@ -50,14 +58,6 @@ export async function resolvePlugins( (typeof modulePreload === 'object' && modulePreload.polyfill) ? modulePreloadPolyfillPlugin(config) : null, - ...(isDepsOptimizerEnabled(config, false) || - isDepsOptimizerEnabled(config, true) - ? [ - isBuild - ? optimizedDepsBuildPlugin(config) - : optimizedDepsPlugin(config), - ] - : []), resolvePlugin({ ...config.resolve, root: config.root, diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 29eed85fa44420..a91c961fddba6c 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -77,31 +77,45 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { } export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { + let buildStartCalled = false + return { name: 'vite:optimized-deps-build', buildStart() { - if (!config.isWorker) { + // Only reset the registered ids after a rebuild during build --watch + if (!config.isWorker && buildStartCalled) { getDepsOptimizer(config)?.resetRegisteredIds() } + buildStartCalled = true }, - resolveId(id, importer, { ssr }) { - if (getDepsOptimizer(config, ssr)?.isOptimizedDepFile(id)) { + async resolveId(id, importer, options) { + const depsOptimizer = getDepsOptimizer(config) + if (!depsOptimizer) return + + if (depsOptimizer.isOptimizedDepFile(id)) { return id + } else { + if (options?.custom?.['vite:pre-alias']) { + // Skip registering the id if it is being resolved from the pre-alias plugin + // When a optimized dep is aliased, we need to avoid waiting for it before optimizing + return + } + const resolved = await this.resolve(id, importer, { + ...options, + skipSelf: true, + }) + if (resolved) { + depsOptimizer.delayDepsOptimizerUntil(resolved.id, async () => { + await this.load(resolved) + }) + } } }, - transform(_code, id, options) { - const ssr = options?.ssr === true - getDepsOptimizer(config, ssr)?.delayDepsOptimizerUntil(id, async () => { - await this.load({ id }) - }) - }, - - async load(id, options) { - const ssr = options?.ssr === true - const depsOptimizer = getDepsOptimizer(config, ssr) + async load(id) { + const depsOptimizer = getDepsOptimizer(config) if (!depsOptimizer?.isOptimizedDepFile(id)) { return } diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 3a4e6c2ac0fe44..7aa33ef2fdc7c2 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -51,8 +51,9 @@ export function preAliasPlugin(config: ResolvedConfig): Plugin { } const resolved = await this.resolve(id, importer, { - skipSelf: true, ...options, + custom: { ...options.custom, 'vite:pre-alias': true }, + skipSelf: true, }) if (resolved && !depsOptimizer.isOptimizedDepFile(resolved.id)) { const optimizeDeps = depsOptimizer.options