Skip to content

Commit

Permalink
feat: non-blocking esbuild optimization at build time (#8280)
Browse files Browse the repository at this point in the history
  • Loading branch information
patak-dev committed May 26, 2022
1 parent c68db4d commit 909cf9c
Show file tree
Hide file tree
Showing 22 changed files with 673 additions and 253 deletions.
13 changes: 8 additions & 5 deletions packages/plugin-react/src/index.ts
Expand Up @@ -2,7 +2,6 @@ import path from 'path'
import type { ParserOptions, TransformOptions, types as t } from '@babel/core'
import * as babel from '@babel/core'
import { createFilter } from '@rollup/pluginutils'
import resolve from 'resolve'
import { normalizePath } from 'vite'
import type { Plugin, PluginOption, ResolvedConfig } from 'vite'
import {
Expand Down Expand Up @@ -362,7 +361,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
}
}

const runtimeId = 'react/jsx-runtime'
// const runtimeId = 'react/jsx-runtime'
// Adapted from https://github.com/alloc/vite-react-jsx
const viteReactJsx: Plugin = {
name: 'vite:react-jsx',
Expand All @@ -373,10 +372,14 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
include: ['react/jsx-dev-runtime']
}
}
},
}
// TODO: this optimization may not be necesary and it is breacking esbuild+rollup compat,
// see https://github.com/vitejs/vite/pull/7246#discussion_r861552185
// We could still do the same trick and resolve to the optimized dependency here
/*
resolveId(id: string) {
return id === runtimeId ? id : null
},
},
load(id: string) {
if (id === runtimeId) {
const runtimePath = resolve.sync(runtimeId, {
Expand All @@ -391,7 +394,7 @@ export default function viteReact(opts: Options = {}): PluginOption[] {
...exports.map((name) => `export const ${name} = jsxRuntime.${name}`)
].join('\n')
}
}
} */
}

return [viteBabel, viteReactRefresh, useAutomaticRuntime && viteReactJsx]
Expand Down
16 changes: 13 additions & 3 deletions packages/vite/src/node/build.ts
Expand Up @@ -22,7 +22,7 @@ import type { RollupCommonJSOptions } from 'types/commonjs'
import type { RollupDynamicImportVarsOptions } from 'types/dynamicImportVars'
import type { TransformOptions } from 'esbuild'
import type { InlineConfig, ResolvedConfig } from './config'
import { resolveConfig } from './config'
import { isDepsOptimizerEnabled, resolveConfig } from './config'
import { buildReporterPlugin } from './plugins/reporter'
import { buildEsbuildPlugin } from './plugins/esbuild'
import { terserPlugin } from './plugins/terser'
Expand All @@ -34,7 +34,11 @@ import { buildImportAnalysisPlugin } from './plugins/importAnalysisBuild'
import { resolveSSRExternal, shouldExternalizeForSSR } from './ssr/ssrExternal'
import { ssrManifestPlugin } from './ssr/ssrManifestPlugin'
import type { DepOptimizationMetadata } from './optimizer'
import { findKnownImports, getDepsCacheDir } from './optimizer'
import {
findKnownImports,
getDepsCacheDir,
initDepsOptimizer
} from './optimizer'
import { assetImportMetaUrlPlugin } from './plugins/assetImportMetaUrl'
import { loadFallbackPlugin } from './plugins/loadFallback'
import type { PackageData } from './packages'
Expand Down Expand Up @@ -283,7 +287,9 @@ export function resolveBuildPlugins(config: ResolvedConfig): {
pre: [
...(options.watch ? [ensureWatchPlugin()] : []),
watchPackageDataPlugin(config),
commonjsPlugin(options.commonjsOptions),
...(!isDepsOptimizerEnabled(config) || options.ssr
? [commonjsPlugin(options.commonjsOptions)]
: []),
dataURIPlugin(),
assetImportMetaUrlPlugin(config),
...(options.rollupOptions.plugins
Expand Down Expand Up @@ -390,6 +396,10 @@ async function doBuild(
)
}

if (isDepsOptimizerEnabled(config) && !ssr) {
await initDepsOptimizer(config)
}

const rollupOptions: RollupOptions = {
input,
context: 'globalThis',
Expand Down
6 changes: 6 additions & 0 deletions packages/vite/src/node/cli.ts
Expand Up @@ -25,6 +25,7 @@ interface GlobalCLIOptions {
filter?: string
m?: string
mode?: string
force?: boolean
}

/**
Expand Down Expand Up @@ -152,6 +153,10 @@ cli
)
.option('--manifest [name]', `[boolean | string] emit build manifest json`)
.option('--ssrManifest [name]', `[boolean | string] emit ssr manifest json`)
.option(
'--force',
`[boolean] force the optimizer to ignore the cache and re-bundle (experimental)`
)
.option(
'--emptyOutDir',
`[boolean] force empty outDir when it's outside of root`
Expand All @@ -169,6 +174,7 @@ cli
configFile: options.config,
logLevel: options.logLevel,
clearScreen: options.clearScreen,
force: options.force,
build: buildOptions
})
} catch (e) {
Expand Down
15 changes: 15 additions & 0 deletions packages/vite/src/node/config.ts
Expand Up @@ -144,6 +144,11 @@ export interface UserConfig {
* Preview specific options, e.g. host, port, https...
*/
preview?: PreviewOptions
/**
* Force dep pre-optimization regardless of whether deps have changed.
* @experimental
*/
force?: boolean
/**
* Dep optimization options
*/
Expand Down Expand Up @@ -855,3 +860,13 @@ async function loadConfigFromBundledFile(
_require.extensions[extension] = defaultLoader
return config
}

export function isDepsOptimizerEnabled(config: ResolvedConfig) {
const { command, optimizeDeps } = config
const { disabled } = optimizeDeps
return !(
disabled === true ||
(command === 'build' && disabled === 'build') ||
(command === 'serve' && optimizeDeps.disabled === 'dev')
)
}
2 changes: 1 addition & 1 deletion packages/vite/src/node/index.ts
Expand Up @@ -36,7 +36,7 @@ export type {
DepOptimizationResult,
DepOptimizationProcessing,
OptimizedDepInfo,
OptimizedDeps,
DepsOptimizer,
ExportsData
} from './optimizer'
export type { Plugin } from './plugin'
Expand Down
65 changes: 45 additions & 20 deletions packages/vite/src/node/optimizer/index.ts
Expand Up @@ -22,6 +22,7 @@ import {
import { transformWithEsbuild } from '../plugins/esbuild'
import { esbuildDepPlugin } from './esbuildDepPlugin'
import { scanImports } from './scan'
export { initDepsOptimizer, getDepsOptimizer } from './optimizer'

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

export interface OptimizedDeps {
export interface DepsOptimizer {
metadata: DepOptimizationMetadata
scanProcessing?: Promise<void>
registerMissingImport: (id: string, resolved: string) => OptimizedDepInfo
run: () => void
isOptimizedDepFile: (id: string) => boolean
isOptimizedDepUrl: (url: string) => boolean
getOptimizedDepId: (depInfo: OptimizedDepInfo) => string
options: DepOptimizationOptions
}

export interface DepOptimizationOptions {
Expand Down Expand Up @@ -107,11 +113,13 @@ export interface DepOptimizationOptions {
*/
extensions?: string[]
/**
* Disables dependencies optimizations
* 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 both
* @default false
* @experimental
*/
disabled?: boolean
disabled?: boolean | 'build' | 'dev'
}

export interface DepOptimizationResult {
Expand Down Expand Up @@ -184,7 +192,7 @@ export interface DepOptimizationMetadata {
*/
export async function optimizeDeps(
config: ResolvedConfig,
force = config.server.force,
force = config.force,
asCommand = false
): Promise<DepOptimizationMetadata> {
const log = asCommand ? config.logger.info : debug
Expand All @@ -209,7 +217,7 @@ export async function optimizeDeps(
return result.metadata
}

export function createOptimizedDepsMetadata(
export function initDepsOptimizerMetadata(
config: ResolvedConfig,
timestamp?: string
): DepOptimizationMetadata {
Expand Down Expand Up @@ -240,7 +248,7 @@ export function addOptimizedDepInfo(
*/
export function loadCachedDepOptimizationMetadata(
config: ResolvedConfig,
force = config.server.force,
force = config.force,
asCommand = false
): DepOptimizationMetadata | undefined {
const log = asCommand ? config.logger.info : debug
Expand All @@ -257,7 +265,7 @@ export function loadCachedDepOptimizationMetadata(
let cachedMetadata: DepOptimizationMetadata | undefined
try {
const cachedMetadataPath = path.join(depsCacheDir, '_metadata.json')
cachedMetadata = parseOptimizedDepsMetadata(
cachedMetadata = parseDepsOptimizerMetadata(
fs.readFileSync(cachedMetadataPath, 'utf-8'),
depsCacheDir
)
Expand Down Expand Up @@ -301,6 +309,21 @@ export async function discoverProjectDependencies(
)
}

return initialProjectDependencies(config, timestamp, deps)
}

/**
* Create the initial discovered deps list. At build time we only
* have the manually included deps. During dev, a scan phase is
* performed and knownDeps is the list of discovered deps
*/
export async function initialProjectDependencies(
config: ResolvedConfig,
timestamp?: string,
knownDeps?: Record<string, string>
): Promise<Record<string, OptimizedDepInfo>> {
const deps: Record<string, string> = knownDeps ?? {}

await addManuallyIncludedOptimizeDeps(deps, config)

const browserHash = getOptimizedBrowserHash(
Expand Down Expand Up @@ -342,16 +365,16 @@ export function depsLogString(qualifiedIds: string[]): string {
* the metadata and start the server without waiting for the optimizeDeps processing to be completed
*/
export async function runOptimizeDeps(
config: ResolvedConfig,
resolvedConfig: ResolvedConfig,
depsInfo: Record<string, OptimizedDepInfo>
): Promise<DepOptimizationResult> {
config = {
...config,
const config: ResolvedConfig = {
...resolvedConfig,
command: 'build'
}

const depsCacheDir = getDepsCacheDir(config)
const processingCacheDir = getProcessingDepsCacheDir(config)
const depsCacheDir = getDepsCacheDir(resolvedConfig)
const processingCacheDir = getProcessingDepsCacheDir(resolvedConfig)

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

const metadata = createOptimizedDepsMetadata(config)
const metadata = initDepsOptimizerMetadata(config)

metadata.browserHash = getOptimizedBrowserHash(
metadata.hash,
Expand Down Expand Up @@ -493,7 +516,7 @@ export async function runOptimizeDeps(
const id = path
.relative(processingCacheDirOutputPath, o)
.replace(jsExtensionRE, '')
const file = getOptimizedDepPath(id, config)
const file = getOptimizedDepPath(id, resolvedConfig)
if (
!findOptimizedDepInfoInRecord(
metadata.optimized,
Expand All @@ -511,7 +534,7 @@ export async function runOptimizeDeps(
}

const dataPath = path.join(processingCacheDir, '_metadata.json')
writeFile(dataPath, stringifyOptimizedDepsMetadata(metadata, depsCacheDir))
writeFile(dataPath, stringifyDepsOptimizerMetadata(metadata, depsCacheDir))

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

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

export function getDepsCacheDir(config: ResolvedConfig) {
return normalizePath(path.resolve(config.cacheDir, 'deps'))
const dirName = config.command === 'build' ? 'depsBuild' : 'deps'
return normalizePath(path.resolve(config.cacheDir, dirName))
}

function getProcessingDepsCacheDir(config: ResolvedConfig) {
return normalizePath(path.resolve(config.cacheDir, 'processing'))
const dirName = config.command === 'build' ? 'processingBuild' : 'processing'
return normalizePath(path.resolve(config.cacheDir, dirName))
}

export function isOptimizedDepFile(id: string, config: ResolvedConfig) {
Expand All @@ -605,7 +630,7 @@ export function createIsOptimizedDepUrl(config: ResolvedConfig) {
}
}

function parseOptimizedDepsMetadata(
function parseDepsOptimizerMetadata(
jsonMetadata: string,
depsCacheDir: string
): DepOptimizationMetadata | undefined {
Expand Down Expand Up @@ -659,7 +684,7 @@ function parseOptimizedDepsMetadata(
* the next time the server start we need to use the global
* browserHash to allow long term caching
*/
function stringifyOptimizedDepsMetadata(
function stringifyDepsOptimizerMetadata(
metadata: DepOptimizationMetadata,
depsCacheDir: string
) {
Expand Down

0 comments on commit 909cf9c

Please sign in to comment.