@@ -214,7 +214,11 @@ export function resolvePlugin(
214214 }
215215 }
216216
217- let res : string | PartialResolvedId | undefined
217+ let res :
218+ | string
219+ | PartialResolvedId
220+ | undefined
221+ | Promise < PartialResolvedId | undefined >
218222
219223 // resolve pre-bundled deps requests, these could be resolved by
220224 // tryFileResolve or /fs/ resolution but these files may not yet
@@ -233,7 +237,7 @@ export function resolvePlugin(
233237 // always return here even if res doesn't exist since /@fs/ is explicit
234238 // if the file doesn't exist it should be a 404.
235239 debug ?.( `[@fs] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
236- return ensureVersionQuery ( res , id , options , depsOptimizer )
240+ return await ensureVersionQuery ( res , id , options , depsOptimizer )
237241 }
238242
239243 // URL
@@ -246,7 +250,7 @@ export function resolvePlugin(
246250 const fsPath = path . resolve ( root , id . slice ( 1 ) )
247251 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
248252 debug ?.( `[url] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
249- return ensureVersionQuery ( res , id , options , depsOptimizer )
253+ return await ensureVersionQuery ( res , id , options , depsOptimizer )
250254 }
251255 }
252256
@@ -268,6 +272,9 @@ export function resolvePlugin(
268272 // Optimized files could not yet exist in disk, resolve to the full path
269273 // Inject the current browserHash version if the path doesn't have one
270274 if ( ! options . isBuild && ! DEP_VERSION_RE . test ( normalizedFsPath ) ) {
275+ // Wait for scanning to complete to ensure stable browserHash
276+ // This prevents inconsistent hashes between in-memory and persisted metadata
277+ await depsOptimizer . scanProcessing
271278 const browserHash = optimizedDepInfoFromFile (
272279 depsOptimizer . metadata ,
273280 normalizedFsPath ,
@@ -287,7 +294,7 @@ export function resolvePlugin(
287294 }
288295
289296 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
290- res = ensureVersionQuery ( res , id , options , depsOptimizer )
297+ res = await ensureVersionQuery ( res , id , options , depsOptimizer )
291298 debug ?.( `[relative] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
292299
293300 if ( ! options . idOnly && ! options . scan && options . isBuild ) {
@@ -318,7 +325,7 @@ export function resolvePlugin(
318325 const fsPath = path . resolve ( basedir , id )
319326 if ( ( res = tryFsResolve ( fsPath , options ) ) ) {
320327 debug ?.( `[drive-relative] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
321- return ensureVersionQuery ( res , id , options , depsOptimizer )
328+ return await ensureVersionQuery ( res , id , options , depsOptimizer )
322329 }
323330 }
324331
@@ -328,7 +335,7 @@ export function resolvePlugin(
328335 ( res = tryFsResolve ( id , options ) )
329336 ) {
330337 debug ?.( `[fs] ${ colors . cyan ( id ) } -> ${ colors . dim ( res ) } ` )
331- return ensureVersionQuery ( res , id , options , depsOptimizer )
338+ return await ensureVersionQuery ( res , id , options , depsOptimizer )
332339 }
333340
334341 // external
@@ -378,15 +385,12 @@ export function resolvePlugin(
378385 return res
379386 }
380387
381- if (
382- ( res = tryNodeResolve (
383- id ,
384- importer ,
385- options ,
386- depsOptimizer ,
387- external ,
388- ) )
389- ) {
388+ res = tryNodeResolve ( id , importer , options , depsOptimizer , external )
389+ if ( res ) {
390+ // Handle both sync and async returns
391+ if ( res instanceof Promise ) {
392+ return await res
393+ }
390394 return res
391395 }
392396
@@ -528,18 +532,21 @@ function resolveSubpathImports(
528532 return importsPath + postfix
529533}
530534
531- function ensureVersionQuery (
535+ async function ensureVersionQuery (
532536 resolved : string ,
533537 id : string ,
534538 options : InternalResolveOptions ,
535539 depsOptimizer ?: DepsOptimizer ,
536- ) : string {
540+ ) : Promise < string > {
537541 if (
538542 ! options . isBuild &&
539543 ! options . scan &&
540544 depsOptimizer &&
541545 ! ( resolved === normalizedClientEntry || resolved === normalizedEnvEntry )
542546 ) {
547+ // Wait for scanning to complete to ensure stable browserHash
548+ // This prevents inconsistent hashes between in-memory and persisted metadata
549+ await depsOptimizer . scanProcessing
543550 // Ensure that direct imports of node_modules have the same version query
544551 // as if they would have been imported through a bare import
545552 // Use the original id to do the check as the resolved id may be the real
@@ -699,7 +706,7 @@ export function tryNodeResolve(
699706 options : InternalResolveOptions ,
700707 depsOptimizer ?: DepsOptimizer ,
701708 externalize ?: boolean ,
702- ) : PartialResolvedId | undefined {
709+ ) : PartialResolvedId | undefined | Promise < PartialResolvedId | undefined > {
703710 const { root, dedupe, isBuild, preserveSymlinks, packageCache } = options
704711
705712 // check for deep import, e.g. "my-lib/foo"
@@ -833,6 +840,20 @@ export function tryNodeResolve(
833840 // can cache it without re-validation, but only do so for known js types.
834841 // otherwise we may introduce duplicated modules for externalized files
835842 // from pre-bundled deps.
843+
844+ // If we need to access browserHash, we must wait for scanning to complete
845+ // to ensure stable browserHash and prevent inconsistent hashes between
846+ // in-memory and persisted metadata
847+ if ( depsOptimizer . scanProcessing ) {
848+ return depsOptimizer . scanProcessing . then ( ( ) => {
849+ const versionHash = depsOptimizer . metadata . browserHash
850+ if ( versionHash && isJsType && resolved ) {
851+ resolved = injectQuery ( resolved , `v=${ versionHash } ` )
852+ }
853+ return processResult ( { id : resolved ! } )
854+ } )
855+ }
856+
836857 const versionHash = depsOptimizer . metadata . browserHash
837858 if ( versionHash && isJsType ) {
838859 resolved = injectQuery ( resolved , `v=${ versionHash } ` )
@@ -854,8 +875,8 @@ export async function tryOptimizedResolve(
854875 preserveSymlinks ?: boolean ,
855876 packageCache ?: PackageCache ,
856877) : Promise < string | undefined > {
857- // TODO: we need to wait until scanning is done here as this function
858- // is used in the preAliasPlugin to decide if an aliased dep is optimized,
878+ // Wait for scanning to complete to ensure stable browserHash and metadata
879+ // This function is used in the preAliasPlugin to decide if an aliased dep is optimized,
859880 // and avoid replacing the bare import with the resolved path.
860881 // We should be able to remove this in the future
861882 await depsOptimizer . scanProcessing
@@ -1116,12 +1137,14 @@ function tryResolveBrowserMapping(
11161137 if ( browserMappedPath ) {
11171138 if (
11181139 ( res = bareImportRE . test ( browserMappedPath )
1119- ? tryNodeResolve (
1120- browserMappedPath ,
1121- importer ,
1122- options ,
1123- undefined ,
1124- undefined ,
1140+ ? (
1141+ tryNodeResolve (
1142+ browserMappedPath ,
1143+ importer ,
1144+ options ,
1145+ undefined ,
1146+ undefined ,
1147+ ) as PartialResolvedId | undefined
11251148 ) ?. id
11261149 : tryFsResolve ( path . join ( pkg . dir , browserMappedPath ) , options ) )
11271150 ) {
0 commit comments