Skip to content

Commit

Permalink
refactor: remove build time pre-bundling (#15184)
Browse files Browse the repository at this point in the history
Co-authored-by: 翠 / green <green@sapphi.red>
Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
  • Loading branch information
3 people authored Jan 15, 2024
1 parent fdc142c commit 757844f
Show file tree
Hide file tree
Showing 21 changed files with 123 additions and 453 deletions.
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.
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
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 @@ -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,
Expand Down Expand Up @@ -780,7 +766,6 @@ export async function resolveConfig(
packageCache,
createResolver,
optimizeDeps: {
disabled: 'build',
...optimizeDeps,
esbuildOptions: {
preserveSymlinks: resolveOptions.preserveSymlinks,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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.
`),
)
}
}
}
113 changes: 21 additions & 92 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 @@ -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>

Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand All @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -733,7 +704,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 @@ -774,40 +744,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 @@ -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',
Expand All @@ -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(
Expand All @@ -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)) {
Expand All @@ -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)) {
Expand Down Expand Up @@ -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()
)
}

Expand Down Expand Up @@ -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: {
Expand Down
Loading

0 comments on commit 757844f

Please sign in to comment.