refactor: remove build time pre-bundling (#15184)

Co-authored-by: 翠 / green <green@sapphi.red>
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
This commit is contained in:
patak 2024-01-15 14:29:54 +01:00 committed by GitHub
parent fdc142cd27
commit 757844f0bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 123 additions and 453 deletions

View File

@ -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.
To disable the optimizer completely, use `optimizeDeps.noDiscovery: true` to disallow automatic discovery of dependencies and leave `optimizeDeps.include` undefined or empty.
:::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.
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: [] }`.
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

View File

@ -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",

View File

@ -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.
`),
)
}
}
}

View File

@ -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<any>) => void
registerWorkersSource: (id: string) => void
resetRegisteredIds: () => void
ensureFirstRun: () => void
close: () => Promise<void>
@ -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<DepOptimizationMetadata> {
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<string, string> = {}
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<string, OptimizedDepInfo>,
ssr: boolean = resolvedConfig.command === 'build' &&
!!resolvedConfig.build.ssr,
ssr: boolean,
): {
cancel: () => Promise<void>
result: Promise<DepOptimizationResult>
@ -733,7 +704,6 @@ async function prepareEsbuildOptimizerRun(
context?: BuildContext
idToExports: Record<string, ExportsData>
}> {
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<string, string>,
config: ResolvedConfig,
ssr: boolean,
extra: string[] = [],
filter?: (id: string) => boolean,
): Promise<void> {
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: {

View File

@ -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<void> {
// 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<void> {
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<any>) {
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<any>) => void
cancel: () => void
}
@ -721,7 +684,6 @@ interface CrawlEndFinder {
function setupOnCrawlEnd(onCrawlEnd: () => void): CrawlEndFinder {
const registeredIds = new Set<string>()
const seenIds = new Set<string>()
const workersSources = new Set<string>()
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<any>): 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<any>) => {},
resetRegisteredIds: () => {},
ensureFirstRun: () => {},
close: async () => {},
options: config.ssr.optimizeDeps,

View File

@ -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 (

View File

@ -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)

View File

@ -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}". ` +

View File

@ -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 &&

View File

@ -837,7 +837,7 @@ export function tryNodeResolve(
}
const skipOptimization =
depsOptimizer?.options.noDiscovery ||
(!options.ssrOptimizeCheck && depsOptimizer?.options.noDiscovery) ||
!isJsType ||
(importer && isInNodeModules(importer)) ||
exclude?.includes(pkgId) ||

View File

@ -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(

View File

@ -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)

View File

@ -12,7 +12,7 @@ async function createDevServer() {
root,
logLevel: 'silent',
optimizeDeps: {
disabled: true,
noDiscovery: true,
},
})
server.pluginContainer.buildStart({})

View File

@ -10,7 +10,7 @@ async function createDevServer() {
root,
logLevel: 'silent',
optimizeDeps: {
disabled: true,
noDiscovery: true,
},
})
server.pluginContainer.buildStart({})

View File

@ -60,8 +60,8 @@ export function resolveSSROptions(
target,
...ssr,
optimizeDeps: {
disabled: true,
...optimizeDeps,
noDiscovery: true, // always true for ssr
esbuildOptions: {
preserveSymlinks,
...optimizeDeps.esbuildOptions,

View File

@ -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: [],
},
},
})

View File

@ -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`,
)

View File

@ -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

View File

@ -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: [

View File

@ -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')
})

View File

@ -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: [],
},
},
})