Skip to content

Commit 909cf9c

Browse files
authored
feat: non-blocking esbuild optimization at build time (#8280)
1 parent c68db4d commit 909cf9c

File tree

22 files changed

+673
-253
lines changed

22 files changed

+673
-253
lines changed

packages/plugin-react/src/index.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import path from 'path'
22
import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
33
import * as babel from '@babel/core'
44
import { createFilter } from '@rollup/pluginutils'
5-
import resolve from 'resolve'
65
import { normalizePath } from 'vite'
76
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
87
import {
@@ -362,7 +361,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
362361
}
363362
}
364363

365-
const runtimeId = 'react/jsx-runtime'
364+
// const runtimeId = 'react/jsx-runtime'
366365
// Adapted from https://github.com/alloc/vite-react-jsx
367366
const viteReactJsx: Plugin = {
368367
name: 'vite:react-jsx',
@@ -373,10 +372,14 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
373372
include: ['react/jsx-dev-runtime']
374373
}
375374
}
376-
},
375+
}
376+
// TODO: this optimization may not be necesary and it is breacking esbuild+rollup compat,
377+
// see https://github.com/vitejs/vite/pull/7246#discussion_r861552185
378+
// We could still do the same trick and resolve to the optimized dependency here
379+
/*
377380
resolveId(id: string) {
378381
return id === runtimeId ? id : null
379-
},
382+
},
380383
load(id: string) {
381384
if (id === runtimeId) {
382385
const runtimePath = resolve.sync(runtimeId, {
@@ -391,7 +394,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
391394
...exports.map((name) => `export const ${name} = jsxRuntime.${name}`)
392395
].join('\n')
393396
}
394-
}
397+
} */
395398
}
396399

397400
return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx]

packages/vite/src/node/build.ts

+13-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import type { RollupCommonJSOptions } from 'types/commonjs'
2222
import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
2323
import type { TransformOptions } from 'esbuild'
2424
import type { InlineConfig, ResolvedConfig } from './config'
25-
import { resolveConfig } from './config'
25+
import { isDepsOptimizerEnabled, resolveConfig } from './config'
2626
import { buildReporterPlugin } from './plugins/reporter'
2727
import { buildEsbuildPlugin } from './plugins/esbuild'
2828
import { terserPlugin } from './plugins/terser'
@@ -34,7 +34,11 @@ import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild'
3434
import { resolveSSRExternal, shouldExternalizeForSSR } from './ssr/ssrExternal'
3535
import { ssrManifestPlugin } from './ssr/ssrManifestPlugin'
3636
import type { DepOptimizationMetadata } from './optimizer'
37-
import { findKnownImports, getDepsCacheDir } from './optimizer'
37+
import {
38+
findKnownImports,
39+
getDepsCacheDir,
40+
initDepsOptimizer
41+
} from './optimizer'
3842
import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl'
3943
import { loadFallbackPlugin } from './plugins/loadFallback'
4044
import type { PackageData } from './packages'
@@ -283,7 +287,9 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
283287
pre: [
284288
...(options.watch ? [ensureWatchPlugin()] : []),
285289
watchPackageDataPlugin(config),
286-
commonjsPlugin(options.commonjsOptions),
290+
...(!isDepsOptimizerEnabled(config) || options.ssr
291+
? [commonjsPlugin(options.commonjsOptions)]
292+
: []),
287293
dataURIPlugin(),
288294
assetImportMetaUrlPlugin(config),
289295
...(options.rollupOptions.plugins
@@ -390,6 +396,10 @@ async function doBuild(
390396
)
391397
}
392398

399+
if (isDepsOptimizerEnabled(config) && !ssr) {
400+
await initDepsOptimizer(config)
401+
}
402+
393403
const rollupOptions: RollupOptions = {
394404
input,
395405
context: 'globalThis',

packages/vite/src/node/cli.ts

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface GlobalCLIOptions {
2525
filter?: string
2626
m?: string
2727
mode?: string
28+
force?: boolean
2829
}
2930

3031
/**
@@ -152,6 +153,10 @@ cli
152153
)
153154
.option('--manifest [name]', `[boolean | string] emit build manifest json`)
154155
.option('--ssrManifest [name]', `[boolean | string] emit ssr manifest json`)
156+
.option(
157+
'--force',
158+
`[boolean] force the optimizer to ignore the cache and re-bundle (experimental)`
159+
)
155160
.option(
156161
'--emptyOutDir',
157162
`[boolean] force empty outDir when it's outside of root`
@@ -169,6 +174,7 @@ cli
169174
configFile: options.config,
170175
logLevel: options.logLevel,
171176
clearScreen: options.clearScreen,
177+
force: options.force,
172178
build: buildOptions
173179
})
174180
} catch (e) {

packages/vite/src/node/config.ts

+15
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@ export interface UserConfig {
144144
* Preview specific options, e.g. host, port, https...
145145
*/
146146
preview?: PreviewOptions
147+
/**
148+
* Force dep pre-optimization regardless of whether deps have changed.
149+
* @experimental
150+
*/
151+
force?: boolean
147152
/**
148153
* Dep optimization options
149154
*/
@@ -855,3 +860,13 @@ async function loadConfigFromBundledFile(
855860
_require.extensions[extension] = defaultLoader
856861
return config
857862
}
863+
864+
export function isDepsOptimizerEnabled(config: ResolvedConfig) {
865+
const { command, optimizeDeps } = config
866+
const { disabled } = optimizeDeps
867+
return !(
868+
disabled === true ||
869+
(command === 'build' && disabled === 'build') ||
870+
(command === 'serve' && optimizeDeps.disabled === 'dev')
871+
)
872+
}

packages/vite/src/node/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export type {
3636
DepOptimizationResult,
3737
DepOptimizationProcessing,
3838
OptimizedDepInfo,
39-
OptimizedDeps,
39+
DepsOptimizer,
4040
ExportsData
4141
} from './optimizer'
4242
export type { Plugin } from './plugin'

packages/vite/src/node/optimizer/index.ts

+45-20
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { transformWithEsbuild } from '../plugins/esbuild'
2323
import { esbuildDepPlugin } from './esbuildDepPlugin'
2424
import { scanImports } from './scan'
25+
export { initDepsOptimizer, getDepsOptimizer } from './optimizer'
2526

2627
export const debuggerViteDeps = createDebugger('vite:deps')
2728
const debug = debuggerViteDeps
@@ -38,10 +39,15 @@ export type ExportsData = ReturnType<typeof parse> & {
3839
jsxLoader?: true
3940
}
4041

41-
export interface OptimizedDeps {
42+
export interface DepsOptimizer {
4243
metadata: DepOptimizationMetadata
4344
scanProcessing?: Promise<void>
4445
registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo
46+
run: () => void
47+
isOptimizedDepFile: (id: string) => boolean
48+
isOptimizedDepUrl: (url: string) => boolean
49+
getOptimizedDepId: (depInfo: OptimizedDepInfo) => string
50+
options: DepOptimizationOptions
4551
}
4652

4753
export interface DepOptimizationOptions {
@@ -107,11 +113,13 @@ export interface DepOptimizationOptions {
107113
*/
108114
extensions?: string[]
109115
/**
110-
* Disables dependencies optimizations
116+
* Disables dependencies optimizations, true disables the optimizer during
117+
* build and dev. Pass 'build' or 'dev' to only disable the optimizer in
118+
* one of the modes. Deps optimization is enabled by default in both
111119
* @default false
112120
* @experimental
113121
*/
114-
disabled?: boolean
122+
disabled?: boolean | 'build' | 'dev'
115123
}
116124

117125
export interface DepOptimizationResult {
@@ -184,7 +192,7 @@ export interface DepOptimizationMetadata {
184192
*/
185193
export async function optimizeDeps(
186194
config: ResolvedConfig,
187-
force = config.server.force,
195+
force = config.force,
188196
asCommand = false
189197
): Promise<DepOptimizationMetadata> {
190198
const log = asCommand ? config.logger.info : debug
@@ -209,7 +217,7 @@ export async function optimizeDeps(
209217
return result.metadata
210218
}
211219

212-
export function createOptimizedDepsMetadata(
220+
export function initDepsOptimizerMetadata(
213221
config: ResolvedConfig,
214222
timestamp?: string
215223
): DepOptimizationMetadata {
@@ -240,7 +248,7 @@ export function addOptimizedDepInfo(
240248
*/
241249
export function loadCachedDepOptimizationMetadata(
242250
config: ResolvedConfig,
243-
force = config.server.force,
251+
force = config.force,
244252
asCommand = false
245253
): DepOptimizationMetadata | undefined {
246254
const log = asCommand ? config.logger.info : debug
@@ -257,7 +265,7 @@ export function loadCachedDepOptimizationMetadata(
257265
let cachedMetadata: DepOptimizationMetadata | undefined
258266
try {
259267
const cachedMetadataPath = path.join(depsCacheDir, '_metadata.json')
260-
cachedMetadata = parseOptimizedDepsMetadata(
268+
cachedMetadata = parseDepsOptimizerMetadata(
261269
fs.readFileSync(cachedMetadataPath, 'utf-8'),
262270
depsCacheDir
263271
)
@@ -301,6 +309,21 @@ export async function discoverProjectDependencies(
301309
)
302310
}
303311

312+
return initialProjectDependencies(config, timestamp, deps)
313+
}
314+
315+
/**
316+
* Create the initial discovered deps list. At build time we only
317+
* have the manually included deps. During dev, a scan phase is
318+
* performed and knownDeps is the list of discovered deps
319+
*/
320+
export async function initialProjectDependencies(
321+
config: ResolvedConfig,
322+
timestamp?: string,
323+
knownDeps?: Record<string, string>
324+
): Promise<Record<string, OptimizedDepInfo>> {
325+
const deps: Record<string, string> = knownDeps ?? {}
326+
304327
await addManuallyIncludedOptimizeDeps(deps, config)
305328

306329
const browserHash = getOptimizedBrowserHash(
@@ -342,16 +365,16 @@ export function depsLogString(qualifiedIds: string[]): string {
342365
* the metadata and start the server without waiting for the optimizeDeps processing to be completed
343366
*/
344367
export async function runOptimizeDeps(
345-
config: ResolvedConfig,
368+
resolvedConfig: ResolvedConfig,
346369
depsInfo: Record<string, OptimizedDepInfo>
347370
): Promise<DepOptimizationResult> {
348-
config = {
349-
...config,
371+
const config: ResolvedConfig = {
372+
...resolvedConfig,
350373
command: 'build'
351374
}
352375

353-
const depsCacheDir = getDepsCacheDir(config)
354-
const processingCacheDir = getProcessingDepsCacheDir(config)
376+
const depsCacheDir = getDepsCacheDir(resolvedConfig)
377+
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig)
355378

356379
// Create a temporal directory so we don't need to delete optimized deps
357380
// until they have been processed. This also avoids leaving the deps cache
@@ -369,7 +392,7 @@ export async function runOptimizeDeps(
369392
JSON.stringify({ type: 'module' })
370393
)
371394

372-
const metadata = createOptimizedDepsMetadata(config)
395+
const metadata = initDepsOptimizerMetadata(config)
373396

374397
metadata.browserHash = getOptimizedBrowserHash(
375398
metadata.hash,
@@ -493,7 +516,7 @@ export async function runOptimizeDeps(
493516
const id = path
494517
.relative(processingCacheDirOutputPath, o)
495518
.replace(jsExtensionRE, '')
496-
const file = getOptimizedDepPath(id, config)
519+
const file = getOptimizedDepPath(id, resolvedConfig)
497520
if (
498521
!findOptimizedDepInfoInRecord(
499522
metadata.optimized,
@@ -511,7 +534,7 @@ export async function runOptimizeDeps(
511534
}
512535

513536
const dataPath = path.join(processingCacheDir, '_metadata.json')
514-
writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata, depsCacheDir))
537+
writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir))
515538

516539
debug(`deps bundled in ${(performance.now() - start).toFixed(2)}ms`)
517540

@@ -532,7 +555,7 @@ async function addManuallyIncludedOptimizeDeps(
532555
): Promise<void> {
533556
const include = config.optimizeDeps?.include
534557
if (include) {
535-
const resolve = config.createResolver({ asSrc: false })
558+
const resolve = config.createResolver({ asSrc: false, scan: true })
536559
for (const id of include) {
537560
// normalize 'foo >bar` as 'foo > bar' to prevent same id being added
538561
// and for pretty printing
@@ -575,11 +598,13 @@ export function getOptimizedDepPath(id: string, config: ResolvedConfig) {
575598
}
576599

577600
export function getDepsCacheDir(config: ResolvedConfig) {
578-
return normalizePath(path.resolve(config.cacheDir, 'deps'))
601+
const dirName = config.command === 'build' ? 'depsBuild' : 'deps'
602+
return normalizePath(path.resolve(config.cacheDir, dirName))
579603
}
580604

581605
function getProcessingDepsCacheDir(config: ResolvedConfig) {
582-
return normalizePath(path.resolve(config.cacheDir, 'processing'))
606+
const dirName = config.command === 'build' ? 'processingBuild' : 'processing'
607+
return normalizePath(path.resolve(config.cacheDir, dirName))
583608
}
584609

585610
export function isOptimizedDepFile(id: string, config: ResolvedConfig) {
@@ -605,7 +630,7 @@ export function createIsOptimizedDepUrl(config: ResolvedConfig) {
605630
}
606631
}
607632

608-
function parseOptimizedDepsMetadata(
633+
function parseDepsOptimizerMetadata(
609634
jsonMetadata: string,
610635
depsCacheDir: string
611636
): DepOptimizationMetadata | undefined {
@@ -659,7 +684,7 @@ function parseOptimizedDepsMetadata(
659684
* the next time the server start we need to use the global
660685
* browserHash to allow long term caching
661686
*/
662-
function stringifyOptimizedDepsMetadata(
687+
function stringifyDepsOptimizerMetadata(
663688
metadata: DepOptimizationMetadata,
664689
depsCacheDir: string
665690
) {

0 commit comments

Comments
 (0)