diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 3cece89d1d7676..985685ab2f8fe3 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -62,6 +62,13 @@ export interface DepOptimizationOptions { * vite project root. This will overwrite default entries inference. */ entries?: string | string[] + /** + * Enable esbuild based scan phase, to get back to the optimized deps discovery + * strategy used in Vite v2 + * @default false + * @experimental + */ + devScan?: boolean /** * Force optimize listed dependencies (must be resolvable import paths, * cannot be globs). diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index ed950264684663..8ae4763af8a2fc 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -46,6 +46,8 @@ export async function initDepsOptimizer( const { logger } = config const isBuild = config.command === 'build' + const scan = config.command !== 'build' && config.optimizeDeps.devScan + const sessionTimestamp = Date.now().toString() const cachedMetadata = loadCachedDepOptimizationMetadata(config) @@ -100,7 +102,7 @@ export async function initDepsOptimizer( // If there wasn't a cache or it is outdated, we need to prepare a first run let firstRunCalled = !!cachedMetadata if (!cachedMetadata) { - if (isBuild) { + if (!scan) { // Initialize discovered deps with manually added optimizeDeps.include info const discovered = await initialProjectDependencies( config, @@ -460,9 +462,14 @@ export async function initDepsOptimizer( exportsData: extractExportsData(resolved, config) }) - // Debounced rerun, let other missing dependencies be discovered before - // the running next optimizeDeps - if (!isBuild) { + // Until the first optimize run is called, avoid triggering processing + // We'll wait until the user codebase is eagerly processed by Vite so + // 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 (scan || firstRunCalled) { + // Debounced rerun, let other missing dependencies be discovered before + // the running next optimizeDeps debouncedProcessing() } diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index b0edfa3606c30e..55bd988459c9f3 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -49,7 +49,10 @@ import { optimizedDepNeedsInterop } from '../optimizer' import { checkPublicFile } from './asset' -import { ERR_OUTDATED_OPTIMIZED_DEP } from './optimizedDeps' +import { + ERR_OUTDATED_OPTIMIZED_DEP, + delayDepsOptimizerUntil +} from './optimizedDeps' import { isCSSRequest, isDirectCSSRequest } from './css' const isDebug = !!process.env.DEBUG @@ -183,7 +186,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) const importedUrls = new Set() - const staticImportedUrls = new Set() + const staticImportedUrls = new Set<{ url: string; id: string }>() const acceptedUrls = new Set<{ url: string start: number @@ -462,7 +465,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { importedUrls.add(urlWithoutBase) if (!isDynamicImport) { // for pre-transforming - staticImportedUrls.add(urlWithoutBase) + staticImportedUrls.add({ url: urlWithoutBase, id: resolvedId }) } } else if (!importer.startsWith(clientDir) && !ssr) { // check @vite-ignore which suppresses dynamic import warning @@ -601,13 +604,14 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) // pre-transform known direct imports + // TODO: we should also crawl dynamic imports if (config.server.preTransformRequests && staticImportedUrls.size) { - staticImportedUrls.forEach((url) => { + staticImportedUrls.forEach(({ url, id }) => { url = unwrapId(removeImportQuery(url)).replace( NULL_BYTE_PLACEHOLDER, '\0' ) - transformRequest(url, server, { ssr }).catch((e) => { + const request = transformRequest(url, server, { ssr }).catch((e) => { if (e?.code === ERR_OUTDATED_OPTIMIZED_DEP) { // This are expected errors return @@ -615,6 +619,9 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // Unexpected error, log the issue but avoid an unhandled exception config.logger.error(e.message) }) + if (!config.optimizeDeps.devScan) { + delayDepsOptimizerUntil(config, id, () => request) + } }) } diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index db206ef7e319e1..b5efe35f2589fe 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -16,7 +16,7 @@ const debug = createDebugger('vite:optimize-deps') const runOptimizerIfIdleAfterMs = 100 interface RunProcessingInfo { - ids: { id: string; done: () => Promise }[] + ids: { id: string; done: () => Promise }[] seenIds: Set workersSources: Set waitingOn: string | undefined @@ -51,10 +51,10 @@ export function registerWorkersSource(config: ResolvedConfig, id: string) { } } -function delayDepsOptimizerUntil( +export function delayDepsOptimizerUntil( config: ResolvedConfig, id: string, - done: () => Promise + done: () => Promise ) { const info = getRunProcessingInfo(config) if ( @@ -98,6 +98,22 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { return { name: 'vite:optimized-deps', + buildStart() { + if (!config.isWorker) { + initRunProcessingInfo(config) + } + }, + + async resolveId(id) { + if (getDepsOptimizer(config)?.isOptimizedDepFile(id)) { + return id + } + }, + + // this.load({ id }) isn't implemented in PluginContainer + // The logic to register an id to wait until it is processed + // is in importAnalysis, see call to delayDepsOptimizerUntil + async load(id) { const depsOptimizer = getDepsOptimizer(config) if (depsOptimizer?.isOptimizedDepFile(id)) {