From 757844f0bd7e52a160cf3f0afe241c03d7414921 Mon Sep 17 00:00:00 2001 From: patak Date: Mon, 15 Jan 2024 14:29:54 +0100 Subject: [PATCH] refactor: remove build time pre-bundling (#15184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 翠 / green Co-authored-by: Bjorn Lu --- docs/config/dep-optimization-options.md | 9 +- package.json | 1 - packages/vite/src/node/config.ts | 73 +++++++--- packages/vite/src/node/optimizer/index.ts | 113 +++------------ packages/vite/src/node/optimizer/optimizer.ts | 102 ++----------- .../src/node/plugins/importAnalysisBuild.ts | 137 +----------------- packages/vite/src/node/plugins/index.ts | 20 ++- .../vite/src/node/plugins/optimizedDeps.ts | 65 --------- packages/vite/src/node/plugins/preAlias.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 2 +- packages/vite/src/node/plugins/worker.ts | 3 - .../src/node/plugins/workerImportMetaUrl.ts | 3 - .../node/ssr/__tests__/ssrLoadModule.spec.ts | 2 +- .../node/ssr/__tests__/ssrStacktrace.spec.ts | 2 +- packages/vite/src/node/ssr/index.ts | 2 +- .../optimize-deps-no-discovery/vite.config.js | 5 - .../__tests__/optimize-deps.spec.ts | 2 +- playground/optimize-deps/vite.config.js | 5 - playground/ssr-deps/server.js | 16 +- .../__tests__/ssr-noexternal.spec.ts | 4 +- playground/ssr-noexternal/vite.config.js | 8 +- 21 files changed, 123 insertions(+), 453 deletions(-) diff --git a/docs/config/dep-optimization-options.md b/docs/config/dep-optimization-options.md index ec499f4d9ccf16..68ecc247b27760 100644 --- a/docs/config/dep-optimization-options.md +++ b/docs/config/dep-optimization-options.md @@ -64,16 +64,17 @@ Set to `true` to force dependency pre-bundling, ignoring previously cached optim ## optimizeDeps.disabled +- **Deprecated** - **Experimental:** [Give Feedback](https://github.com/vitejs/vite/discussions/13839) - **Type:** `boolean | 'build' | 'dev'` - **Default:** `'build'` -Disables dependencies optimizations, `true` disables the optimizer during build and dev. Pass `'build'` or `'dev'` to only disable the optimizer in one of the modes. Dependency optimization is enabled by default in dev only. +This option is deprecated. As of Vite 5.1, pre-bundling of dependencies during build have been removed. Setting `optimizeDeps.disabled` to `true` or `'dev'` disables the optimizer, and configured to `false` or `'build'` leaves the optimizer during dev enabled. -:::warning -Optimizing dependencies in build mode is **experimental**. If enabled, it removes one of the most significant differences between dev and prod. [`@rollup/plugin-commonjs`](https://github.com/rollup/plugins/tree/master/packages/commonjs) is no longer needed in this case since esbuild converts CJS-only dependencies to ESM. +To disable the optimizer completely, use `optimizeDeps.noDiscovery: true` to disallow automatic discovery of dependencies and leave `optimizeDeps.include` undefined or empty. -If you want to try this build strategy, you can use `optimizeDeps.disabled: false`. `@rollup/plugin-commonjs` can be removed by passing `build.commonjsOptions: { include: [] }`. +:::warning +Optimizing dependencies during build time was an **experimental** feature. Projects trying out this strategy also removed `@rollup/plugin-commonjs` using `build.commonjsOptions: { include: [] }`. If you did so, a warning will guide you to re-enable it to support CJS only packages while bundling. ::: ## optimizeDeps.needsInterop diff --git a/package.json b/package.json index 1d0a7195048f12..c999fceee2e050 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "test": "run-s test-unit test-serve test-build", "test-serve": "vitest run -c vitest.config.e2e.ts", "test-build": "VITE_TEST_BUILD=1 vitest run -c vitest.config.e2e.ts", - "test-build-without-plugin-commonjs": "VITE_TEST_WITHOUT_PLUGIN_COMMONJS=1 pnpm test-build", "test-unit": "vitest run", "test-docs": "pnpm run docs-build", "debug-serve": "VITE_DEBUG_SERVE=1 vitest run -c vitest.config.e2e.ts", diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index c02dba5c3c417a..c96dae1b191ba8 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -467,20 +467,6 @@ export async function resolveConfig( const userPlugins = [...prePlugins, ...normalPlugins, ...postPlugins] config = await runConfigHook(config, userPlugins, configEnv) - // If there are custom commonjsOptions, don't force optimized deps for this test - // even if the env var is set as it would interfere with the playground specs. - if ( - !config.build?.commonjsOptions && - process.env.VITE_TEST_WITHOUT_PLUGIN_COMMONJS - ) { - config = mergeConfig(config, { - optimizeDeps: { disabled: false }, - ssr: { optimizeDeps: { disabled: false } }, - }) - config.build ??= {} - config.build.commonjsOptions = { include: [] } - } - // Define logger const logger = createLogger(config.logLevel, { allowClearScreen: config.clearScreen, @@ -780,7 +766,6 @@ export async function resolveConfig( packageCache, createResolver, optimizeDeps: { - disabled: 'build', ...optimizeDeps, esbuildOptions: { preserveSymlinks: resolveOptions.preserveSymlinks, @@ -816,6 +801,13 @@ export async function resolveConfig( .map((hook) => hook(resolved)), ) + optimizeDepsDisabledBackwardCompatibility(resolved, resolved.optimizeDeps) + optimizeDepsDisabledBackwardCompatibility( + resolved, + resolved.ssr.optimizeDeps, + 'ssr.', + ) + debug?.(`using resolved config: %O`, { ...resolved, plugins: resolved.plugins.map((p) => p.name), @@ -1242,11 +1234,48 @@ export function isDepsOptimizerEnabled( config: ResolvedConfig, ssr: boolean, ): boolean { - const { command } = config - const { disabled } = getDepOptimizationConfig(config, ssr) - return !( - disabled === true || - (command === 'build' && disabled === 'build') || - (command === 'serve' && disabled === 'dev') - ) + const optimizeDeps = getDepOptimizationConfig(config, ssr) + return !(optimizeDeps.noDiscovery && !optimizeDeps.include?.length) +} + +function optimizeDepsDisabledBackwardCompatibility( + resolved: ResolvedConfig, + optimizeDeps: DepOptimizationConfig, + optimizeDepsPath: string = '', +) { + const optimizeDepsDisabled = optimizeDeps.disabled + if (optimizeDepsDisabled !== undefined) { + if (optimizeDepsDisabled === true || optimizeDepsDisabled === 'dev') { + const commonjsOptionsInclude = resolved.build?.commonjsOptions?.include + const commonjsPluginDisabled = + Array.isArray(commonjsOptionsInclude) && + commonjsOptionsInclude.length === 0 + optimizeDeps.noDiscovery = true + optimizeDeps.include = undefined + if (commonjsPluginDisabled) { + resolved.build.commonjsOptions.include = undefined + } + resolved.logger.warn( + colors.yellow(`(!) Experimental ${optimizeDepsPath}optimizeDeps.disabled and deps pre-bundling during build were removed in Vite 5.1. + To disable the deps optimizer, set ${optimizeDepsPath}optimizeDeps.noDiscovery to true and ${optimizeDepsPath}optimizeDeps.include as undefined or empty. + Please remove ${optimizeDepsPath}optimizeDeps.disabled from your config. + ${ + commonjsPluginDisabled + ? 'Empty config.build.commonjsOptions.include will be ignored to support CJS during build. This config should also be removed.' + : '' + } + `), + ) + } else if ( + optimizeDepsDisabled === false || + optimizeDepsDisabled === 'build' + ) { + resolved.logger.warn( + colors.yellow(`(!) Experimental ${optimizeDepsPath}optimizeDeps.disabled and deps pre-bundling during build were removed in Vite 5.1. + Setting it to ${optimizeDepsDisabled} now has no effect. + Please remove ${optimizeDepsPath}optimizeDeps.disabled from your config. + `), + ) + } + } } diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 4c05b75af675b8..c085b5d10b23a3 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -8,11 +8,9 @@ import type { BuildContext, BuildOptions as EsbuildBuildOptions } from 'esbuild' import esbuild, { build } from 'esbuild' import { init, parse } from 'es-module-lexer' import glob from 'fast-glob' -import { createFilter } from '@rollup/pluginutils' import { getDepOptimizationConfig } from '../config' import type { ResolvedConfig } from '../config' import { - arraify, createDebugger, flattenId, getHash, @@ -59,9 +57,6 @@ export interface DepsOptimizer { isOptimizedDepUrl: (url: string) => boolean getOptimizedDepId: (depInfo: OptimizedDepInfo) => string delayDepsOptimizerUntil: (id: string, done: () => Promise) => void - registerWorkersSource: (id: string) => void - resetRegisteredIds: () => void - ensureFirstRun: () => void close: () => Promise @@ -120,10 +115,12 @@ export interface DepOptimizationConfig { */ extensions?: string[] /** - * Disables dependencies optimizations, true disables the optimizer during - * build and dev. Pass 'build' or 'dev' to only disable the optimizer in - * one of the modes. Deps optimization is enabled by default in dev only. + * Deps optimization during build was removed in Vite 5.1. This option is + * now redundant and will be removed in a future version. Switch to using + * `optimizeDeps.noDiscovery` and an empty or undefined `optimizeDeps.include`. + * true or 'dev' disables the optimizer, false or 'build' leaves it enabled. * @default 'build' + * @deprecated * @experimental */ disabled?: boolean | 'build' | 'dev' @@ -236,8 +233,7 @@ export async function optimizeDeps( asCommand = false, ): Promise { const log = asCommand ? config.logger.info : debug - - const ssr = config.command === 'build' && !!config.build.ssr + const ssr = false const cachedMetadata = await loadCachedDepOptimizationMetadata( config, @@ -258,7 +254,7 @@ export async function optimizeDeps( const depsInfo = toDiscoveredDependencies(config, deps, ssr) - const result = await runOptimizeDeps(config, depsInfo).result + const result = await runOptimizeDeps(config, depsInfo, ssr).result await result.commit() @@ -279,37 +275,13 @@ export async function optimizeServerSsrDeps( return cachedMetadata } - let alsoInclude: string[] | undefined - let noExternalFilter: ((id: unknown) => boolean) | undefined - - const { exclude } = getDepOptimizationConfig(config, ssr) - - const noExternal = config.ssr?.noExternal - if (noExternal) { - alsoInclude = arraify(noExternal).filter( - (ne) => typeof ne === 'string', - ) as string[] - noExternalFilter = - noExternal === true - ? (dep: unknown) => true - : createFilter(undefined, exclude, { - resolve: false, - }) - } - const deps: Record = {} - await addManuallyIncludedOptimizeDeps( - deps, - config, - ssr, - alsoInclude, - noExternalFilter, - ) + await addManuallyIncludedOptimizeDeps(deps, config, ssr) - const depsInfo = toDiscoveredDependencies(config, deps, true) + const depsInfo = toDiscoveredDependencies(config, deps, ssr) - const result = await runOptimizeDeps(config, depsInfo, true).result + const result = await runOptimizeDeps(config, depsInfo, ssr).result await result.commit() @@ -469,8 +441,7 @@ export function depsLogString(qualifiedIds: string[]): string { export function runOptimizeDeps( resolvedConfig: ResolvedConfig, depsInfo: Record, - ssr: boolean = resolvedConfig.command === 'build' && - !!resolvedConfig.build.ssr, + ssr: boolean, ): { cancel: () => Promise result: Promise @@ -733,7 +704,6 @@ async function prepareEsbuildOptimizerRun( context?: BuildContext idToExports: Record }> { - const isBuild = resolvedConfig.command === 'build' const config: ResolvedConfig = { ...resolvedConfig, command: 'build', @@ -774,13 +744,8 @@ async function prepareEsbuildOptimizerRun( if (optimizerContext.cancelled) return { context: undefined, idToExports } - // esbuild automatically replaces process.env.NODE_ENV for platform 'browser' - // But in lib mode, we need to keep process.env.NODE_ENV untouched const define = { - 'process.env.NODE_ENV': - isBuild && config.build.lib - ? 'process.env.NODE_ENV' - : JSON.stringify(process.env.NODE_ENV || config.mode), + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || config.mode), } const platform = @@ -788,26 +753,6 @@ async function prepareEsbuildOptimizerRun( const external = [...(optimizeDeps?.exclude ?? [])] - if (isBuild) { - let rollupOptionsExternal = config?.build?.rollupOptions?.external - if (rollupOptionsExternal) { - if (typeof rollupOptionsExternal === 'string') { - rollupOptionsExternal = [rollupOptionsExternal] - } - // TODO: decide whether to support RegExp and function options - // They're not supported yet because `optimizeDeps.exclude` currently only accepts strings - if ( - !Array.isArray(rollupOptionsExternal) || - rollupOptionsExternal.some((ext) => typeof ext !== 'string') - ) { - throw new Error( - `[vite] 'build.rollupOptions.external' can only be an array of strings or a string when using esbuild optimization at build time.`, - ) - } - external.push(...(rollupOptionsExternal as string[])) - } - } - const plugins = [...pluginsFromConfig] if (external.length) { plugins.push(esbuildCjsExternalPlugin(external, platform)) @@ -831,13 +776,13 @@ async function prepareEsbuildOptimizerRun( js: `import { createRequire } from 'module';const require = createRequire(import.meta.url);`, } : undefined, - target: isBuild ? config.build.target || undefined : ESBUILD_MODULES_TARGET, + target: ESBUILD_MODULES_TARGET, external, logLevel: 'error', splitting: true, sourcemap: true, outdir: processingCacheDir, - ignoreAnnotations: !isBuild, + ignoreAnnotations: true, metafile: true, plugins, charset: 'utf8', @@ -855,13 +800,11 @@ export async function addManuallyIncludedOptimizeDeps( deps: Record, config: ResolvedConfig, ssr: boolean, - extra: string[] = [], - filter?: (id: string) => boolean, ): Promise { const { logger } = config const optimizeDeps = getDepOptimizationConfig(config, ssr) const optimizeDepsInclude = optimizeDeps?.include ?? [] - if (optimizeDepsInclude.length || extra.length) { + if (optimizeDepsInclude.length) { const unableToOptimize = (id: string, msg: string) => { if (optimizeDepsInclude.includes(id)) { logger.warn( @@ -872,7 +815,7 @@ export async function addManuallyIncludedOptimizeDeps( } } - const includes = [...optimizeDepsInclude, ...extra] + const includes = [...optimizeDepsInclude] for (let i = 0; i < includes.length; i++) { const id = includes[i] if (glob.isDynamicPattern(id)) { @@ -887,7 +830,7 @@ export async function addManuallyIncludedOptimizeDeps( // normalize 'foo >bar` as 'foo > bar' to prevent same id being added // and for pretty printing const normalizedId = normalizeId(id) - if (!deps[normalizedId] && filter?.(normalizedId) !== false) { + if (!deps[normalizedId]) { const entry = await resolve(id) if (entry) { if (isOptimizable(entry, optimizeDeps)) { @@ -926,30 +869,17 @@ export function getOptimizedDepPath( ) } -function getDepsCacheSuffix(config: ResolvedConfig, ssr: boolean): string { - let suffix = '' - if (config.command === 'build') { - // Differentiate build caches depending on outDir to allow parallel builds - const { outDir } = config.build - const buildId = - outDir.length > 8 || outDir.includes('/') ? getHash(outDir) : outDir - suffix += `_build-${buildId}` - } - if (ssr) { - suffix += '_ssr' - } - return suffix +function getDepsCacheSuffix(ssr: boolean): string { + return ssr ? '_ssr' : '' } export function getDepsCacheDir(config: ResolvedConfig, ssr: boolean): string { - return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(config, ssr) + return getDepsCacheDirPrefix(config) + getDepsCacheSuffix(ssr) } function getProcessingDepsCacheDir(config: ResolvedConfig, ssr: boolean) { return ( - getDepsCacheDirPrefix(config) + - getDepsCacheSuffix(config, ssr) + - getTempSuffix() + getDepsCacheDirPrefix(config) + getDepsCacheSuffix(ssr) + getTempSuffix() ) } @@ -1224,7 +1154,6 @@ function getConfigHash(config: ResolvedConfig, ssr: boolean): string { mode: process.env.NODE_ENV || config.mode, root: config.root, resolve: config.resolve, - buildTarget: config.build.target, assetsInclude: config.assetsInclude, plugins: config.plugins.map((p) => p.name), optimizeDeps: { diff --git a/packages/vite/src/node/optimizer/optimizer.ts b/packages/vite/src/node/optimizer/optimizer.ts index f4a278bd54bcfd..477f42915e9a93 100644 --- a/packages/vite/src/node/optimizer/optimizer.ts +++ b/packages/vite/src/node/optimizer/optimizer.ts @@ -36,20 +36,14 @@ export function getDepsOptimizer( config: ResolvedConfig, ssr?: boolean, ): DepsOptimizer | undefined { - // Workers compilation shares the DepsOptimizer from the main build - const isDevSsr = ssr && config.command !== 'build' - return (isDevSsr ? devSsrDepsOptimizerMap : depsOptimizerMap).get( - config.mainConfig || config, - ) + return (ssr ? devSsrDepsOptimizerMap : depsOptimizerMap).get(config) } export async function initDepsOptimizer( config: ResolvedConfig, server?: ViteDevServer, ): Promise { - // Non Dev SSR Optimizer - const ssr = config.command === 'build' && !!config.build.ssr - if (!getDepsOptimizer(config, ssr)) { + if (!getDepsOptimizer(config, false)) { await createDepsOptimizer(config, server) } } @@ -87,9 +81,7 @@ async function createDepsOptimizer( server?: ViteDevServer, ): Promise { const { logger } = config - const isBuild = config.command === 'build' - const ssr = isBuild && !!config.build.ssr // safe as Dev SSR don't use this optimizer - + const ssr = false const sessionTimestamp = Date.now().toString() const cachedMetadata = await loadCachedDepOptimizationMetadata(config, ssr) @@ -108,11 +100,8 @@ async function createDepsOptimizer( isOptimizedDepFile: createIsOptimizedDepFile(config), isOptimizedDepUrl: createIsOptimizedDepUrl(config), getOptimizedDepId: (depInfo: OptimizedDepInfo) => - isBuild ? depInfo.file : `${depInfo.file}?v=${depInfo.browserHash}`, - registerWorkersSource, + `${depInfo.file}?v=${depInfo.browserHash}`, delayDepsOptimizerUntil, - resetRegisteredIds, - ensureFirstRun, close, options: getDepOptimizationConfig(config, ssr), } @@ -152,15 +141,12 @@ 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. It will be recreated - // after each buildStart. - // During dev, if this is a cold run, we wait for static imports discovered + // 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. let crawlEndFinder: CrawlEndFinder | undefined - if (isBuild || !cachedMetadata) { + if (!cachedMetadata) { crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) } @@ -216,7 +202,7 @@ async function createDepsOptimizer( // We don't need to scan for dependencies or wait for the static crawl to end // Run the first optimization run immediately runOptimizer() - } else if (!isBuild) { + } else { // Important, the scanner is dev only depsOptimizer.scanProcessing = new Promise((resolve) => { // Runs in the background in case blocking high priority tasks @@ -243,7 +229,7 @@ async function createDepsOptimizer( // run on the background, but we wait until crawling has ended // to decide if we send this result to the browser or we need to // do another optimize step - optimizationResult = runOptimizeDeps(config, knownDeps) + optimizationResult = runOptimizeDeps(config, knownDeps, ssr) } catch (e) { logger.error(e.stack || e.message) } finally { @@ -318,7 +304,7 @@ async function createDepsOptimizer( const knownDeps = prepareKnownDeps() startNextDiscoveredBatch() - optimizationResult = runOptimizeDeps(config, knownDeps) + optimizationResult = runOptimizeDeps(config, knownDeps, ssr) processingResult = await optimizationResult.result optimizationResult = undefined } @@ -541,11 +527,6 @@ async function createDepsOptimizer( // browser a dependency that may be outdated, thus avoiding full page reloads 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() @@ -595,15 +576,11 @@ async function createDepsOptimizer( }, timeout) } - // During dev, onCrawlEnd is called once when the server starts and all static + // 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). async function onCrawlEnd() { - // On build time, a missing dep appearing after onCrawlEnd is an internal error - // On dev, switch after this point to a simple debounce strategy + // switch after this point to a simple debounce strategy crawlEndFinder = undefined debug?.(colors.green(`✨ static imports crawl ended`)) @@ -615,7 +592,7 @@ async function createDepsOptimizer( // It normally should be over by the time crawling of user code ended await depsOptimizer.scanProcessing - if (!isBuild && optimizationResult && !config.optimizeDeps.noDiscovery) { + if (optimizationResult && !config.optimizeDeps.noDiscovery) { const result = await optimizationResult.result optimizationResult = undefined currentlyProcessing = false @@ -690,30 +667,16 @@ async function createDepsOptimizer( } } - // Called during buildStart at build time, when build --watch is used. - function resetRegisteredIds() { - crawlEndFinder?.cancel() - crawlEndFinder = setupOnCrawlEnd(onCrawlEnd) - } - - function registerWorkersSource(id: string) { - crawlEndFinder?.registerWorkersSource(id) - } function delayDepsOptimizerUntil(id: string, done: () => Promise) { if (crawlEndFinder && !depsOptimizer.isOptimizedDepFile(id)) { crawlEndFinder.delayDepsOptimizerUntil(id, done) } } - function ensureFirstRun() { - crawlEndFinder?.ensureFirstRun() - } } const callCrawlEndIfIdleAfterMs = 50 interface CrawlEndFinder { - ensureFirstRun: () => void - registerWorkersSource: (id: string) => void delayDepsOptimizerUntil: (id: string, done: () => Promise) => void cancel: () => void } @@ -721,7 +684,6 @@ interface CrawlEndFinder { function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder { const registeredIds = new Set() const seenIds = new Set() - const workersSources = new Set() let timeoutHandle: NodeJS.Timeout | undefined let cancelled = false @@ -737,40 +699,13 @@ function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder { } } - // 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 - let firstRunEnsured = false - function ensureFirstRun() { - if (!firstRunEnsured && seenIds.size === 0) { - setTimeout(() => { - if (seenIds.size === 0) { - callOnCrawlEnd() - } - }, 200) - } - firstRunEnsured = true - } - - function registerWorkersSource(id: string): void { - workersSources.add(id) - - // 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.delete(id) - - checkIfCrawlEndAfterTimeout() - } - function delayDepsOptimizerUntil(id: string, done: () => Promise): void { if (!seenIds.has(id)) { seenIds.add(id) - if (!workersSources.has(id)) { - registeredIds.add(id) - done() - .catch(() => {}) - .finally(() => markIdAsDone(id)) - } + registeredIds.add(id) + done() + .catch(() => {}) + .finally(() => markIdAsDone(id)) } } function markIdAsDone(id: string): void { @@ -793,8 +728,6 @@ function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder { } return { - ensureFirstRun, - registerWorkersSource, delayDepsOptimizerUntil, cancel, } @@ -820,10 +753,7 @@ async function createDevSsrDepsOptimizer( // noop, there is no scanning during dev SSR // the optimizer blocks the server start run: () => {}, - registerWorkersSource: (id: string) => {}, delayDepsOptimizerUntil: (id: string, done: () => Promise) => {}, - resetRegisteredIds: () => {}, - ensureFirstRun: () => {}, close: async () => {}, options: config.ssr.optimizeDeps, diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 07e6a5c39c444c..da43afbe668a09 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -6,28 +6,20 @@ import type { } from 'es-module-lexer' import { init, parse as parseImports } from 'es-module-lexer' import type { OutputChunk, SourceMap } from 'rollup' -import colors from 'picocolors' import type { RawSourceMap } from '@ampproject/remapping' import convertSourceMap from 'convert-source-map' import { - cleanUrl, combineSourcemaps, generateCodeFrame, - isDataUrl, - isExternalUrl, isInNodeModules, - moduleListContains, numberToPos, - withTrailingSlash, } from '../utils' import type { Plugin } from '../plugin' -import { getDepOptimizationConfig } from '../config' import type { ResolvedConfig } from '../config' import { toOutputFilePathInJS } from '../build' import { genSourceMapUrl } from '../server/sourcemap' -import { getDepsOptimizer, optimizedDepNeedsInterop } from '../optimizer' import { removedPureCssFilesCache } from './css' -import { createParseErrorInfo, interopNamedImports } from './importAnalysis' +import { createParseErrorInfo } from './importAnalysis' type FileDep = { url: string @@ -49,10 +41,6 @@ const preloadMarkerWithQuote = new RegExp(`['"]${preloadMarker}['"]`, 'g') const dynamicImportPrefixRE = /import\s*\(/ -// TODO: abstract -const optimizedDepChunkRE = /\/chunk-[A-Z\d]{8}\.js/ -const optimizedDepDynamicRE = /-[A-Z\d]{8}\.js/ - function toRelativePath(filename: string, importer: string) { const relPath = path.posix.relative(path.posix.dirname(importer), filename) return relPath[0] === '.' ? relPath : `./${relPath}` @@ -237,78 +225,15 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { return null } - const { root } = config - const depsOptimizer = getDepsOptimizer(config, ssr) - - const normalizeUrl = async ( - url: string, - pos: number, - ): Promise<[string, string]> => { - let importerFile = importer - - const optimizeDeps = getDepOptimizationConfig(config, ssr) - if (moduleListContains(optimizeDeps?.exclude, url)) { - if (depsOptimizer) { - await depsOptimizer.scanProcessing - - // if the dependency encountered in the optimized file was excluded from the optimization - // the dependency needs to be resolved starting from the original source location of the optimized file - // because starting from node_modules/.vite will not find the dependency if it was not hoisted - // (that is, if it is under node_modules directory in the package source of the optimized file) - for (const optimizedModule of depsOptimizer.metadata.depInfoList) { - if (!optimizedModule.src) continue // Ignore chunks - if (optimizedModule.file === importer) { - importerFile = optimizedModule.src - } - } - } - } - - const resolved = await this.resolve(url, importerFile, { - skipSelf: false, - }) - - if (!resolved) { - // in ssr, we should let node handle the missing modules - if (ssr) { - return [url, url] - } - return this.error( - `Failed to resolve import "${url}" from "${path.relative( - process.cwd(), - importerFile, - )}". Does the file exist?`, - pos, - ) - } - - // normalize all imports into resolved URLs - // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'` - if (resolved.id.startsWith(withTrailingSlash(root))) { - // in root: infer short absolute path from root - url = resolved.id.slice(root.length) - } else { - url = resolved.id - } - - if (isExternalUrl(url)) { - return [url, url] - } - - return [url, resolved.id] - } - let s: MagicString | undefined const str = () => s || (s = new MagicString(source)) let needPreloadHelper = false for (let index = 0; index < imports.length; index++) { const { - s: start, e: end, ss: expStart, se: expEnd, - n: specifier, d: dynamicIndex, a: attributeIndex, } = imports[index] @@ -332,66 +257,6 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { })`, ) } - - // static import or valid string in dynamic import - // If resolvable, let's resolve it - if (depsOptimizer && specifier) { - // skip external / data uri - if (isExternalUrl(specifier) || isDataUrl(specifier)) { - continue - } - - // normalize - const [url, resolvedId] = await normalizeUrl(specifier, start) - - if (url !== specifier) { - if ( - depsOptimizer.isOptimizedDepFile(resolvedId) && - !optimizedDepChunkRE.test(resolvedId) - ) { - const file = cleanUrl(resolvedId) // Remove ?v={hash} - - const needsInterop = await optimizedDepNeedsInterop( - depsOptimizer.metadata, - file, - config, - ssr, - ) - - let rewriteDone = false - - if (needsInterop === undefined) { - // Non-entry dynamic imports from dependencies will reach here as there isn't - // optimize info for them, but they don't need es interop. If the request isn't - // a dynamic import, then it is an internal Vite error - if (!optimizedDepDynamicRE.test(file)) { - config.logger.error( - colors.red( - `Vite Error, ${url} optimized info should be defined`, - ), - ) - } - } else if (needsInterop) { - // config.logger.info(`${url} needs interop`) - interopNamedImports( - str(), - imports[index], - url, - index, - importer, - config, - ) - rewriteDone = true - } - if (!rewriteDone) { - const rewrittenUrl = JSON.stringify(file) - const s = isDynamicImport ? start : start - 1 - const e = isDynamicImport ? end : end + 1 - str().update(s, e, rewrittenUrl) - } - } - } - } } if ( diff --git a/packages/vite/src/node/plugins/index.ts b/packages/vite/src/node/plugins/index.ts index 87f8aea70d630c..82fa469647db35 100644 --- a/packages/vite/src/node/plugins/index.ts +++ b/packages/vite/src/node/plugins/index.ts @@ -9,7 +9,7 @@ import { watchPackageDataPlugin } from '../packages' import { getFsUtils } from '../fsUtils' import { jsonPlugin } from './json' import { resolvePlugin } from './resolve' -import { optimizedDepsBuildPlugin, optimizedDepsPlugin } from './optimizedDeps' +import { optimizedDepsPlugin } from './optimizedDeps' import { esbuildPlugin } from './esbuild' import { importAnalysisPlugin } from './importAnalysis' import { cssPlugin, cssPostPlugin } from './css' @@ -39,16 +39,12 @@ export async function resolvePlugins( ? await (await import('../build')).resolveBuildPlugins(config) : { pre: [], post: [] } const { modulePreload } = config.build - + const depsOptimizerEnabled = + !isBuild && + (isDepsOptimizerEnabled(config, false) || + isDepsOptimizerEnabled(config, true)) return [ - ...(isDepsOptimizerEnabled(config, false) || - isDepsOptimizerEnabled(config, true) - ? [ - isBuild - ? optimizedDepsBuildPlugin(config) - : optimizedDepsPlugin(config), - ] - : []), + depsOptimizerEnabled ? optimizedDepsPlugin(config) : null, isBuild ? metadataPlugin() : null, !isWorker ? watchPackageDataPlugin(config.packageCache) : null, preAliasPlugin(config), @@ -69,7 +65,9 @@ export async function resolvePlugins( ssrConfig: config.ssr, asSrc: true, fsUtils: getFsUtils(config), - getDepsOptimizer: (ssr: boolean) => getDepsOptimizer(config, ssr), + getDepsOptimizer: isBuild + ? undefined + : (ssr: boolean) => getDepsOptimizer(config, ssr), shouldExternalize: isBuild && config.build.ssr ? (id, importer) => shouldExternalizeForSSR(id, importer, config) diff --git a/packages/vite/src/node/plugins/optimizedDeps.ts b/packages/vite/src/node/plugins/optimizedDeps.ts index 47f930fcac3f29..ae44dd8ecd4cd8 100644 --- a/packages/vite/src/node/plugins/optimizedDeps.ts +++ b/packages/vite/src/node/plugins/optimizedDeps.ts @@ -75,71 +75,6 @@ export function optimizedDepsPlugin(config: ResolvedConfig): Plugin { } } -export function optimizedDepsBuildPlugin(config: ResolvedConfig): Plugin { - let buildStartCalled = false - - return { - name: 'vite:optimized-deps-build', - - buildStart() { - // Only reset the registered ids after a rebuild during build --watch - if (!config.isWorker && buildStartCalled) { - getDepsOptimizer(config)?.resetRegisteredIds() - } - buildStartCalled = true - }, - - 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) - if (resolved && !resolved.external) { - depsOptimizer.delayDepsOptimizerUntil(resolved.id, async () => { - await this.load(resolved) - }) - } - return resolved - } - }, - - async load(id) { - const depsOptimizer = getDepsOptimizer(config) - if (!depsOptimizer?.isOptimizedDepFile(id)) { - return - } - - depsOptimizer?.ensureFirstRun() - - const file = cleanUrl(id) - // Search in both the currently optimized and newly discovered deps - // If all the inputs are dependencies, we aren't going to get any - const info = optimizedDepInfoFromFile(depsOptimizer.metadata, file) - if (info) { - await info.processing - debug?.(`load ${colors.cyan(file)}`) - } else { - throw new Error( - `Something unexpected happened while optimizing "${id}".`, - ) - } - - // Load the file from the cache instead of waiting for other plugin - // load hooks to avoid race conditions, once processing is resolved, - // we are sure that the file has been properly save to disk - return fsp.readFile(file, 'utf-8') - }, - } -} - function throwProcessingError(id: string): never { const err: any = new Error( `Something unexpected happened while optimizing "${id}". ` + diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 33df60b2d58b2e..d80a0a3429a5ee 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -31,7 +31,7 @@ export function preAliasPlugin(config: ResolvedConfig): Plugin { name: 'vite:pre-alias', async resolveId(id, importer, options) { const ssr = options?.ssr === true - const depsOptimizer = getDepsOptimizer(config, ssr) + const depsOptimizer = !isBuild && getDepsOptimizer(config, ssr) if ( importer && depsOptimizer && diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 71577872e5bed8..0a9a853747d11b 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -837,7 +837,7 @@ export function tryNodeResolve( } const skipOptimization = - depsOptimizer?.options.noDiscovery || + (!options.ssrOptimizeCheck && depsOptimizer?.options.noDiscovery) || !isJsType || (importer && isInNodeModules(importer)) || exclude?.includes(pkgId) || diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index f1a202d59623b0..4a4bb446a2b775 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -11,7 +11,6 @@ import { onRollupWarning, toOutputFilePathInJS, } from '../build' -import { getDepsOptimizer } from '../optimizer' import { fileToUrl } from './asset' interface WorkerCache { @@ -235,7 +234,6 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { }, async transform(raw, id, options) { - const ssr = options?.ssr === true const query = parseRequest(id) if (query && query[WORKER_FILE_ID] != null) { // if import worker by worker constructor will have query.type @@ -295,7 +293,6 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { }` if (isBuild) { - getDepsOptimizer(config, ssr)?.registerWorkersSource(id) if (query.inline != null) { const chunk = await bundleWorkerEntry(config, id, query) const encodedJs = `const encodedJs = "${Buffer.from( diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 90b3899eb29dcf..6b3bf7c0432506 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -12,7 +12,6 @@ import { slash, transformStableResult, } from '../utils' -import { getDepsOptimizer } from '../optimizer' import type { ResolveFn } from '..' import type { WorkerType } from './worker' import { WORKER_FILE_ID, workerFileToUrl } from './worker' @@ -131,7 +130,6 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { }, async transform(code, id, options) { - const ssr = options?.ssr === true if (!options?.ssr && isIncludeWorkerImportMetaUrl(code)) { const query = parseRequest(id) let s: MagicString | undefined @@ -176,7 +174,6 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { let builtUrl: string if (isBuild) { - getDepsOptimizer(config, ssr)?.registerWorkersSource(id) builtUrl = await workerFileToUrl(config, file, query) } else { builtUrl = await fileToUrl(cleanUrl(file), config, this) diff --git a/packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts index da7b79edfb4047..192b0b8cd3326f 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrLoadModule.spec.ts @@ -12,7 +12,7 @@ async function createDevServer() { root, logLevel: 'silent', optimizeDeps: { - disabled: true, + noDiscovery: true, }, }) server.pluginContainer.buildStart({}) diff --git a/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts b/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts index 384fbd6a428992..a334c5078abd79 100644 --- a/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts +++ b/packages/vite/src/node/ssr/__tests__/ssrStacktrace.spec.ts @@ -10,7 +10,7 @@ async function createDevServer() { root, logLevel: 'silent', optimizeDeps: { - disabled: true, + noDiscovery: true, }, }) server.pluginContainer.buildStart({}) diff --git a/packages/vite/src/node/ssr/index.ts b/packages/vite/src/node/ssr/index.ts index d4cb4de3a054c0..3847e69544b2c0 100644 --- a/packages/vite/src/node/ssr/index.ts +++ b/packages/vite/src/node/ssr/index.ts @@ -60,8 +60,8 @@ export function resolveSSROptions( target, ...ssr, optimizeDeps: { - disabled: true, ...optimizeDeps, + noDiscovery: true, // always true for ssr esbuildOptions: { preserveSymlinks, ...optimizeDeps.esbuildOptions, diff --git a/playground/optimize-deps-no-discovery/vite.config.js b/playground/optimize-deps-no-discovery/vite.config.js index 4179400e2b1080..445f53fd121b0e 100644 --- a/playground/optimize-deps-no-discovery/vite.config.js +++ b/playground/optimize-deps-no-discovery/vite.config.js @@ -5,7 +5,6 @@ process.env.NODE_ENV = '' export default defineConfig({ optimizeDeps: { - disabled: false, noDiscovery: true, include: ['@vitejs/test-dep-no-discovery'], }, @@ -13,9 +12,5 @@ export default defineConfig({ build: { // to make tests faster minify: false, - // Avoid @rollup/plugin-commonjs - commonjsOptions: { - include: [], - }, }, }) diff --git a/playground/optimize-deps/__tests__/optimize-deps.spec.ts b/playground/optimize-deps/__tests__/optimize-deps.spec.ts index 2468a6e9ae2ea9..f2ad3680cdac47 100644 --- a/playground/optimize-deps/__tests__/optimize-deps.spec.ts +++ b/playground/optimize-deps/__tests__/optimize-deps.spec.ts @@ -172,7 +172,7 @@ test('vue + vuex', async () => { // When we use the Rollup CommonJS plugin instead of esbuild prebundling, // the esbuild plugins won't apply to dependencies -test('esbuild-plugin', async () => { +test.runIf(isServe)('esbuild-plugin', async () => { await expectWithRetry(() => page.textContent('.esbuild-plugin')).toMatch( `Hello from an esbuild plugin`, ) diff --git a/playground/optimize-deps/vite.config.js b/playground/optimize-deps/vite.config.js index 8947bb66020b7c..10aabf56651c51 100644 --- a/playground/optimize-deps/vite.config.js +++ b/playground/optimize-deps/vite.config.js @@ -17,7 +17,6 @@ export default defineConfig({ }, }, optimizeDeps: { - disabled: false, include: [ '@vitejs/test-dep-linked-include', '@vitejs/test-nested-exclude > @vitejs/test-nested-include', @@ -49,10 +48,6 @@ export default defineConfig({ build: { // to make tests faster minify: false, - // Avoid @rollup/plugin-commonjs - commonjsOptions: { - include: [], - }, rollupOptions: { onwarn(msg, warn) { // filter `"Buffer" is not exported by "__vite-browser-external"` warning diff --git a/playground/ssr-deps/server.js b/playground/ssr-deps/server.js index 05e86a863ea744..52134ea162a4ea 100644 --- a/playground/ssr-deps/server.js +++ b/playground/ssr-deps/server.js @@ -8,6 +8,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)) const isTest = process.env.VITEST +const noExternal = [ + '@vitejs/test-no-external-cjs', + '@vitejs/test-import-builtin-cjs', + '@vitejs/test-no-external-css', + '@vitejs/test-external-entry', +] + export async function createServer(root = process.cwd(), hmrPort) { const resolve = (p) => path.resolve(__dirname, p) @@ -35,18 +42,13 @@ export async function createServer(root = process.cwd(), hmrPort) { }, appType: 'custom', ssr: { - noExternal: [ - '@vitejs/test-no-external-cjs', - '@vitejs/test-import-builtin-cjs', - '@vitejs/test-no-external-css', - '@vitejs/test-external-entry', - ], + noExternal, external: [ '@vitejs/test-nested-external', '@vitejs/test-external-entry/entry', ], optimizeDeps: { - disabled: 'build', + include: noExternal, }, }, plugins: [ diff --git a/playground/ssr-noexternal/__tests__/ssr-noexternal.spec.ts b/playground/ssr-noexternal/__tests__/ssr-noexternal.spec.ts index 2143c7524a0e41..ab41f0a4602454 100644 --- a/playground/ssr-noexternal/__tests__/ssr-noexternal.spec.ts +++ b/playground/ssr-noexternal/__tests__/ssr-noexternal.spec.ts @@ -1,10 +1,10 @@ import { expect, test } from 'vitest' import { port } from './serve' -import { page } from '~utils' +import { isBuild, page } from '~utils' const url = `http://localhost:${port}` -test('message from require-external-cjs', async () => { +test.runIf(!isBuild)('message from require-external-cjs', async () => { await page.goto(url) expect(await page.textContent('.require-external-cjs')).toMatch('foo') }) diff --git a/playground/ssr-noexternal/vite.config.js b/playground/ssr-noexternal/vite.config.js index 76da96a7d018ed..1109bddd187001 100644 --- a/playground/ssr-noexternal/vite.config.js +++ b/playground/ssr-noexternal/vite.config.js @@ -1,11 +1,12 @@ import { defineConfig } from 'vite' +const noExternal = ['@vitejs/test-require-external-cjs'] export default defineConfig({ ssr: { - noExternal: ['@vitejs/test-require-external-cjs'], + noExternal, external: ['@vitejs/test-external-cjs'], optimizeDeps: { - disabled: false, + include: noExternal, }, }, build: { @@ -14,8 +15,5 @@ export default defineConfig({ rollupOptions: { external: ['@vitejs/test-external-cjs'], }, - commonjsOptions: { - include: [], - }, }, })