Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions packages/vite/src/node/__tests__/external.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { PartialEnvironment } from '../baseEnvironment'
describe('createIsConfiguredAsExternal', () => {
test('default', async () => {
const isExternal = await createIsExternal()
expect(isExternal('@vitejs/cjs-ssr-dep')).toBe(false)
expect(await isExternal('@vitejs/cjs-ssr-dep')).toBe(false)
})

test('force external', async () => {
const isExternal = await createIsExternal(true)
expect(isExternal('@vitejs/cjs-ssr-dep')).toBe(true)
expect(await isExternal('@vitejs/cjs-ssr-dep')).toBe(true)
})
})

Expand Down
52 changes: 27 additions & 25 deletions packages/vite/src/node/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1953,32 +1953,34 @@ async function bundleConfigFile(
name: 'externalize-deps',
setup(build) {
const packageCache = new Map()
const resolveByViteResolver = (
const resolveByViteResolver = async (
id: string,
importer: string,
isRequire: boolean,
) => {
return tryNodeResolve(id, importer, {
root: path.dirname(fileName),
isBuild: true,
isProduction: true,
preferRelative: false,
tryIndex: true,
mainFields: [],
conditions: [
'node',
...(isModuleSyncConditionEnabled ? ['module-sync'] : []),
],
externalConditions: [],
external: [],
noExternal: [],
dedupe: [],
extensions: configDefaults.resolve.extensions,
preserveSymlinks: false,
packageCache,
isRequire,
builtins: nodeLikeBuiltins,
})?.id
return (
await tryNodeResolve(id, importer, {
root: path.dirname(fileName),
isBuild: true,
isProduction: true,
preferRelative: false,
tryIndex: true,
mainFields: [],
conditions: [
'node',
...(isModuleSyncConditionEnabled ? ['module-sync'] : []),
],
externalConditions: [],
external: [],
noExternal: [],
dedupe: [],
extensions: configDefaults.resolve.extensions,
preserveSymlinks: false,
packageCache,
isRequire,
builtins: nodeLikeBuiltins,
})
)?.id
}

// externalize bare imports
Expand All @@ -2003,16 +2005,16 @@ async function bundleConfigFile(
const isImport = isESM || kind === 'dynamic-import'
let idFsPath: string | undefined
try {
idFsPath = resolveByViteResolver(id, importer, !isImport)
idFsPath = await resolveByViteResolver(id, importer, !isImport)
} catch (e) {
if (!isImport) {
let canResolveWithImport = false
try {
canResolveWithImport = !!resolveByViteResolver(
canResolveWithImport = !!(await resolveByViteResolver(
id,
importer,
false,
)
))
} catch {}
if (canResolveWithImport) {
throw new Error(
Expand Down
26 changes: 13 additions & 13 deletions packages/vite/src/node/external.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,25 @@ const debug = createDebugger('vite:external')

const isExternalCache = new WeakMap<
Environment,
(id: string, importer?: string) => boolean
(id: string, importer?: string) => Promise<boolean>
>()

export function shouldExternalize(
export async function shouldExternalize(
environment: Environment,
id: string,
importer: string | undefined,
): boolean {
): Promise<boolean> {
let isExternal = isExternalCache.get(environment)
if (!isExternal) {
isExternal = createIsExternal(environment)
isExternalCache.set(environment, isExternal)
}
return isExternal(id, importer)
return await isExternal(id, importer)
}

export function createIsConfiguredAsExternal(
environment: PartialEnvironment,
): (id: string, importer?: string) => boolean {
): (id: string, importer?: string) => Promise<boolean> {
const { config } = environment
const { root, resolve } = config
const { external, noExternal } = resolve
Expand All @@ -53,16 +53,16 @@ export function createIsConfiguredAsExternal(
conditions: targetConditions,
}

const isExternalizable = (
const isExternalizable = async (
id: string,
importer: string | undefined,
configuredAsExternal: boolean,
): boolean => {
): Promise<boolean> => {
if (!bareImportRE.test(id) || id.includes('\0')) {
return false
}
try {
const resolved = tryNodeResolve(
const resolved = await tryNodeResolve(
id,
// Skip passing importer in build to avoid externalizing non-hoisted dependencies
// unresolvable from root (which would be unresolvable from output bundles also)
Expand Down Expand Up @@ -91,7 +91,7 @@ export function createIsConfiguredAsExternal(

// Returns true if it is configured as external, false if it is filtered
// by noExternal and undefined if it isn't affected by the explicit config
return (id: string, importer?: string) => {
return async (id: string, importer?: string) => {
if (
// If this id is defined as external, force it as external
// Note that individual package entries are allowed in `external`
Expand Down Expand Up @@ -120,26 +120,26 @@ export function createIsConfiguredAsExternal(
}
// If external is true, all will be externalized by default, regardless if
// it's a linked package
return isExternalizable(id, importer, external === true)
return await isExternalizable(id, importer, external === true)
}
}

function createIsExternal(
environment: Environment,
): (id: string, importer?: string) => boolean {
): (id: string, importer?: string) => Promise<boolean> {
const processedIds = new Map<string, boolean>()

const isConfiguredAsExternal = createIsConfiguredAsExternal(environment)

return (id: string, importer?: string) => {
return async (id: string, importer?: string) => {
if (processedIds.has(id)) {
return processedIds.get(id)!
}
let isExternal = false
if (id[0] !== '.' && !path.isAbsolute(id)) {
isExternal =
isBuiltin(environment.config.resolve.builtins, id) ||
isConfiguredAsExternal(id, importer)
(await isConfiguredAsExternal(id, importer))
}
processedIds.set(id, isExternal)
return isExternal
Expand Down
4 changes: 2 additions & 2 deletions packages/vite/src/node/optimizer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1330,11 +1330,11 @@ function getDepHash(environment: Environment): {
}
}

function getOptimizedBrowserHash(
export function getOptimizedBrowserHash(
hash: string,
deps: Record<string, string>,
timestamp = '',
) {
): string {
return getHash(hash + JSON.stringify(deps) + timestamp)
}

Expand Down
15 changes: 15 additions & 0 deletions packages/vite/src/node/optimizer/optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
depsLogString,
discoverProjectDependencies,
extractExportsData,
getOptimizedBrowserHash,
getOptimizedDepPath,
initDepsOptimizerMetadata,
loadCachedDepOptimizationMetadata,
Expand Down Expand Up @@ -237,6 +238,20 @@ export function createDepsOptimizer(
const knownDeps = prepareKnownDeps()
startNextDiscoveredBatch()

// Ensure consistent browserHash between in-memory and persisted metadata.
// By setting it eagerly here (before scanProcessing resolves), both the
// current server and any subsequent server loading _metadata.json will
// produce the same browserHash for these deps, avoiding mismatches during
// mid-load restarts.
metadata.browserHash = getOptimizedBrowserHash(
metadata.hash,
depsFromOptimizedDepInfo(knownDeps),
)

for (const dep of Object.keys(metadata.discovered)) {
metadata.discovered[dep].browserHash = metadata.browserHash
}

// For dev, we run the scanner and the first optimization
// run on the background
optimizationResult = runOptimizeDeps(environment, knownDeps)
Expand Down
4 changes: 3 additions & 1 deletion packages/vite/src/node/plugins/importAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
depsOptimizer &&
moduleListContains(depsOptimizer.options.exclude, url)
) {
// Wait for scanning to complete to ensure stable browserHash and metadata
// This prevents inconsistent hashes between in-memory and persisted metadata
await depsOptimizer.scanProcessing

// if the dependency encountered in the optimized file was excluded from the optimization
Expand Down Expand Up @@ -520,7 +522,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin {
}
// skip ssr externals and builtins
if (ssr && !matchAlias(specifier)) {
if (shouldExternalize(environment, specifier, importer)) {
if (await shouldExternalize(environment, specifier, importer)) {
return
}
if (isBuiltin(environment.config.resolve.builtins, specifier)) {
Expand Down
Loading
Loading