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

feat: single cancellable scan #11243

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 25 additions & 18 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ export async function optimizeDeps(
return cachedMetadata
}

const deps = await discoverProjectDependencies(config)
const deps = await (await discoverProjectDependencies(config)).depsPromise

const depsString = depsLogString(Object.keys(deps))
log(colors.green(`Optimizing dependencies:\n ${depsString}`))
Expand Down Expand Up @@ -382,24 +382,31 @@ export function loadCachedDepOptimizationMetadata(
*/
export async function discoverProjectDependencies(
config: ResolvedConfig,
): Promise<Record<string, string>> {
const { deps, missing } = await scanImports(config)

const missingIds = Object.keys(missing)
if (missingIds.length) {
throw new Error(
`The following dependencies are imported but could not be resolved:\n\n ${missingIds
.map(
(id) =>
`${colors.cyan(id)} ${colors.white(
colors.dim(`(imported by ${missing[id]})`),
)}`,
): Promise<{
cancel: () => void
depsPromise: Promise<Record<string, string>>
}> {
const { cancel, resultsPromise } = await scanImports(config)
return {
cancel,
depsPromise: resultsPromise.then(({ deps, missing }) => {
const missingIds = Object.keys(missing)
if (missingIds.length) {
throw new Error(
`The following dependencies are imported but could not be resolved:\n\n ${missingIds
.map(
(id) =>
`${colors.cyan(id)} ${colors.white(
colors.dim(`(imported by ${missing[id]})`),
)}`,
)
.join(`\n `)}\n\nAre they installed?`,
)
.join(`\n `)}\n\nAre they installed?`,
)
}
}

return deps
return deps
}),
}
}

export function toDiscoveredDependencies(
Expand Down Expand Up @@ -679,7 +686,7 @@ export async function findKnownImports(
config: ResolvedConfig,
ssr: boolean,
): Promise<string[]> {
const deps = (await scanImports(config)).deps
const { deps } = await (await scanImports(config)).resultsPromise
await addManuallyIncludedOptimizeDeps(deps, config, ssr)
return Object.keys(deps)
}
Expand Down
23 changes: 12 additions & 11 deletions packages/vite/src/node/optimizer/optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,20 @@ async function createDepsOptimizer(
let firstRunCalled = !!cachedMetadata

let postScanOptimizationResult: Promise<DepOptimizationResult> | undefined
let discoverProjectDependenciesPromise:
| Promise<Record<string, string>>
| undefined
let discover: {
cancel: () => void
depsPromise: Promise<Record<string, string>>
}

let optimizingNewDeps: Promise<DepOptimizationResult> | undefined
async function close() {
closed = true
await discoverProjectDependenciesPromise?.catch(() => {
/* ignore error for scanner because it's not important */
})
await postScanOptimizationResult
await optimizingNewDeps
discover?.cancel()
await Promise.allSettled([
discover?.depsPromise ?? Promise.resolve(),
postScanOptimizationResult,
optimizingNewDeps,
])
}

if (!cachedMetadata) {
Expand Down Expand Up @@ -208,9 +210,8 @@ async function createDepsOptimizer(
try {
debug(colors.green(`scanning for dependencies...`))

discoverProjectDependenciesPromise =
discoverProjectDependencies(config)
const deps = await discoverProjectDependenciesPromise
discover = await discoverProjectDependencies(config)
const deps = await discover.depsPromise

debug(
colors.green(
Expand Down
87 changes: 63 additions & 24 deletions packages/vite/src/node/optimizer/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ export const importsRE =
/(?<!\/\/.*)(?<=^|;|\*\/)\s*import(?!\s+type)(?:[\w*{}\n\r\t, ]+from)?\s*("[^"]+"|'[^']+')\s*(?=$|;|\/\/|\/\*)/gm

export async function scanImports(config: ResolvedConfig): Promise<{
deps: Record<string, string>
missing: Record<string, string>
cancel: () => void
resultsPromise: Promise<{
deps: Record<string, string>
missing: Record<string, string>
cancelled?: boolean
}>
}> {
// Only used to scan non-ssr code

Expand Down Expand Up @@ -93,7 +97,10 @@ export async function scanImports(config: ResolvedConfig): Promise<{
),
)
}
return { deps: {}, missing: {} }
return {
cancel: () => {},
resultsPromise: Promise.resolve({ deps: {}, missing: {} }),
}
} else {
debug(`Crawling dependencies using entries:\n ${entries.join('\n ')}`)
}
Expand All @@ -102,31 +109,43 @@ export async function scanImports(config: ResolvedConfig): Promise<{
const missing: Record<string, string> = {}
const container = await createPluginContainer(config)
const plugin = esbuildScanPlugin(config, container, deps, missing, entries)

const { cancel, plugin: cancelPlugin } = esbuildCancelPlugin()
const { plugins = [], ...esbuildOptions } =
config.optimizeDeps?.esbuildOptions ?? {}

await Promise.all(
entries.map((entry) =>
build({
absWorkingDir: process.cwd(),
write: false,
entryPoints: [entry],
bundle: true,
format: 'esm',
logLevel: 'error',
plugins: [...plugins, plugin],
...esbuildOptions,
}),
),
)

debug(`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`, deps)

const resultsPromise = build({
absWorkingDir: process.cwd(),
write: false,
stdin: {
contents: entries.map((e) => `import '${e}'`).join('\n'),
loader: 'js',
},
bundle: true,
format: 'esm',
logLevel: 'error',
plugins: [...plugins, plugin, cancelPlugin],
...esbuildOptions,
})
.then(() => ({
// Ensure a fixed order so hashes are stable and improve logs
deps: orderedDependencies(deps),
missing,
}))
.catch((e) => {
if (e.message === 'vite:cancel') {
return { deps: {}, missing: {}, cancelled: true }
}
throw e
})
.finally(() => {
debug(
`Scan completed in ${(performance.now() - start).toFixed(2)}ms:`,
deps,
)
})
return {
// Ensure a fixed order so hashes are stable and improve logs
deps: orderedDependencies(deps),
missing,
cancel,
resultsPromise,
}
}

Expand Down Expand Up @@ -519,6 +538,26 @@ function esbuildScanPlugin(
}
}

function esbuildCancelPlugin(): { plugin: Plugin; cancel: () => void } {
let cancelled = false
const plugin: Plugin = {
name: 'vite:cancel',
setup(build) {
// external urls
build.onResolve({ filter: /.*/ }, () => {
if (cancelled) {
throw new Error('vite:cancel')
}
return null
})
},
}
const cancel = () => {
cancelled = true
}
return { plugin, cancel }
}

/**
* when using TS + (Vue + `<script setup>`) or Svelte, imports may seem
* unused to esbuild and dropped in the build output, which prevents
Expand Down