@@ -324,6 +324,10 @@ function buildExtractedModule(
324324 */
325325export default function useDirectiveInline ( configs ) {
326326 const moduleCache = new Map ( ) ;
327+ // Track which virtual module IDs originate from each source file.
328+ // Keys are absolute file paths; values are Sets of resolved virtual IDs
329+ // (which may be root-relative in client env or absolute in rsc/ssr env).
330+ const sourceToVirtualIds = new Map ( ) ;
327331 let root = "" ;
328332
329333 // Build lookup maps
@@ -365,7 +369,9 @@ export default function useDirectiveInline(configs) {
365369 } ,
366370
367371 async resolveId ( source , importer ) {
368- if ( matchQueryKey ( source ) ) return source ;
372+ if ( matchQueryKey ( source ) ) {
373+ return source ;
374+ }
369375
370376 // Resolve relative imports from our extracted virtual modules.
371377 // Vite can't determine the correct directory for virtual module IDs
@@ -384,8 +390,6 @@ export default function useDirectiveInline(configs) {
384390 const match = matchQueryKey ( id ) ;
385391 if ( ! match ) return ;
386392
387- const cached = moduleCache . get ( id ) ;
388- if ( cached ) return cached ;
389393 const { cfg, marker } = match ;
390394 const qIdx = id . indexOf ( marker ) ;
391395 const rawPath = id . slice ( 0 , qIdx ) ;
@@ -396,6 +400,20 @@ export default function useDirectiveInline(configs) {
396400 ? rawPath . slice ( 0 , rawPath . indexOf ( "?" ) )
397401 : rawPath ;
398402 const filePath = basePath . startsWith ( root ) ? basePath : root + basePath ;
403+
404+ // Track this virtual module ID so our hotUpdate hook can find it
405+ if ( ! sourceToVirtualIds . has ( filePath ) ) {
406+ sourceToVirtualIds . set ( filePath , new Set ( ) ) ;
407+ }
408+ sourceToVirtualIds . get ( filePath ) . add ( id ) ;
409+
410+ // Check if transform already built this module during its pass
411+ const cached = moduleCache . get ( id ) ;
412+ if ( cached ) {
413+ moduleCache . delete ( id ) ;
414+ return cached ;
415+ }
416+
399417 const sourceCode = await readFile ( filePath , "utf-8" ) ;
400418 const ast = await parse ( sourceCode , filePath ) ;
401419 if ( ! ast ) return ;
@@ -427,7 +445,6 @@ export default function useDirectiveInline(configs) {
427445 cfg . injectCapturedParams ,
428446 rawPath
429447 ) ;
430- moduleCache . set ( id , extractedCode ) ;
431448 return extractedCode ;
432449 } ,
433450
@@ -744,5 +761,38 @@ export default function useDirectiveInline(configs) {
744761 return modifiedCode ;
745762 } ,
746763 } ,
764+
765+ // When a source file changes, find any virtual modules derived from it
766+ // in this environment and include them in the HMR update.
767+ // Vite's built-in mechanism misses client-env virtual modules because
768+ // their fileToModulesMap keys are root-relative while the file watcher
769+ // reports absolute paths.
770+ hotUpdate ( options ) {
771+ const virtualIds = sourceToVirtualIds . get ( options . file ) ;
772+ if ( ! virtualIds || virtualIds . size === 0 ) return ;
773+
774+ const graph = this . environment . moduleGraph ;
775+ const additionalModules = [ ] ;
776+
777+ for ( const vid of virtualIds ) {
778+ const mod = graph . getModuleById ( vid ) ;
779+ if ( mod ) {
780+ // Clear any stale one-shot cache entry so the load hook
781+ // re-reads from disk and re-parses.
782+ moduleCache . delete ( vid ) ;
783+ // Eagerly invalidate the module so its cached transform result
784+ // is cleared. A downstream plugin (e.g. use-client) may remove
785+ // the module from the HMR list, which would prevent Vite's
786+ // updateModules from invalidating it. Without invalidation the
787+ // browser would receive stale code on the next request.
788+ graph . invalidateModule ( mod ) ;
789+ additionalModules . push ( mod ) ;
790+ }
791+ }
792+
793+ if ( additionalModules . length > 0 ) {
794+ return [ ...options . modules , ...additionalModules ] ;
795+ }
796+ } ,
747797 } ;
748798}
0 commit comments