Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove build time pre-bundling #15184

Merged
merged 15 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions docs/config/dep-optimization-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Use `optimizeDeps.noDiscovery` instead to disallow automatic discovery of dependencies and leave `optimizeDeps.include` undefined or empty to disable completely the optimizer instead.
patak-dev marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
73 changes: 51 additions & 22 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,20 +466,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,
Expand Down Expand Up @@ -774,7 +760,6 @@ export async function resolveConfig(
packageCache,
createResolver,
optimizeDeps: {
disabled: 'build',
...optimizeDeps,
esbuildOptions: {
preserveSymlinks: resolveOptions.preserveSymlinks,
Expand Down Expand Up @@ -810,6 +795,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),
Expand Down Expand Up @@ -1232,11 +1224,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.'
: ''
bluwy marked this conversation as resolved.
Show resolved Hide resolved
}
`),
)
} 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.
`),
)
}
}
}
112 changes: 21 additions & 91 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -58,9 +56,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>

Expand Down Expand Up @@ -119,10 +114,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'
Expand Down Expand Up @@ -225,8 +222,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,
Expand All @@ -247,7 +243,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()

Expand All @@ -268,37 +264,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()

Expand Down Expand Up @@ -446,8 +418,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>
Expand Down Expand Up @@ -708,7 +679,6 @@ async function prepareEsbuildOptimizerRun(
context?: BuildContext
idToExports: Record<string, ExportsData>
}> {
const isBuild = resolvedConfig.command === 'build'
const config: ResolvedConfig = {
...resolvedConfig,
command: 'build',
Expand Down Expand Up @@ -749,40 +719,15 @@ 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 =
ssr && config.ssr?.target !== 'webworker' ? 'node' : 'browser'

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))
Expand All @@ -806,13 +751,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',
Expand All @@ -830,13 +775,12 @@ export async function addManuallyIncludedOptimizeDeps(
deps: Record<string, string>,
config: ResolvedConfig,
ssr: boolean,
extra: string[] = [],
filter?: (id: string) => boolean,
patak-dev marked this conversation as resolved.
Show resolved Hide resolved
): 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(
Expand All @@ -847,7 +791,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)) {
Expand All @@ -862,7 +806,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)) {
Expand Down Expand Up @@ -901,30 +845,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()
)
}

Expand Down Expand Up @@ -1212,7 +1143,6 @@ export function getDepHash(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: {
Expand Down
Loading
Loading