@@ -51,7 +51,7 @@ interface Reload {
5151class Hmr extends Service {
5252 public baseDir : string
5353
54- private internal : ModuleLoader
54+ private internal : ModuleLoader | undefined
5555 private watcher ! : FSWatcher
5656
5757 /**
@@ -77,8 +77,8 @@ class Hmr extends Service {
7777
7878 constructor ( ctx : Context , public config : Hmr . Config ) {
7979 super ( ctx , 'hmr' )
80- if ( ! this . ctx . loader . internal ) {
81- throw new Error ( '--expose-internals is required for HMR service ' )
80+ if ( this . config . root . length && ! this . ctx . loader . internal ) {
81+ throw new Error ( 'HMR module reload requires loader internals: run with --expose-internals or use watch-only mode (root: []) ' )
8282 }
8383 this . internal = this . ctx . loader . internal
8484 this . baseDir = fileURLToPath ( new URL ( config . base || '.' , ctx . baseUrl ) )
@@ -88,9 +88,9 @@ class Hmr extends Service {
8888 * Resolve a module specifier to a URL, compatible with Node 22-24.
8989 */
9090 private async _resolve ( specifier : string , parentURL : string , attrs : ImportAttributes ) : Promise < ResolveResult > {
91- switch ( this . internal . version ) {
92- case 'v1' : return await this . internal . resolve ( specifier , parentURL , attrs )
93- case 'v2' : return this . internal . resolveSync ( parentURL , { specifier, attributes : attrs } )
91+ switch ( this . internal ! . version ) {
92+ case 'v1' : return await this . internal ! . resolve ( specifier , parentURL , attrs )
93+ case 'v2' : return this . internal ! . resolveSync ( parentURL , { specifier, attributes : attrs } )
9494 }
9595 }
9696
@@ -114,10 +114,12 @@ class Hmr extends Service {
114114
115115 // Collect externals: framework modules reachable from the main entry.
116116 // Changes to these files require a full process restart, not HMR.
117+ // In watch-only mode (root: []) no module reload happens, so externals
118+ // tracking is meaningless and loader internals may be unavailable.
117119 const mainUrl = pathToFileURL ( resolve ( process . argv [ 1 ] ) ) . href
118- const mainJob = this . internal . loadCache . get ( mainUrl )
119- if ( mainJob ) {
120- this . externals = await loadDependencies ( mainJob )
120+ if ( this . config . root . length ) {
121+ const mainJob = this . internal ! . loadCache . get ( mainUrl )
122+ this . externals = mainJob ? await loadDependencies ( mainJob ) : new Set ( )
121123 } else {
122124 this . externals = new Set ( )
123125 }
@@ -135,7 +137,7 @@ class Hmr extends Service {
135137 // Partial reload: the file is in the ESM loadCache
136138 // In Node 24, both CJS and ESM modules imported via import() end up
137139 // in loadCache, so this check covers all module formats.
138- if ( loader . internal ! . loadCache . has ( url ) ) {
140+ if ( this . internal ? .loadCache . has ( url ) ) {
139141 this . stashed . add ( url )
140142 return partialReload ( )
141143 }
@@ -158,7 +160,7 @@ class Hmr extends Service {
158160 ]
159161
160162 async getLinked ( url : string ) {
161- const job = this . internal . loadCache . get ( url )
163+ const job = this . internal ? .loadCache . get ( url )
162164 if ( ! job ) return [ ]
163165 const linked = await job . linked
164166 return Array . prototype . map . call ( linked , ( job : ModuleJob ) => job . url ) as string [ ]
@@ -245,7 +247,7 @@ class Hmr extends Service {
245247 try {
246248 const { url } = await this . _resolve ( name , baseUrl , { } )
247249 if ( this . declined . has ( url ) ) continue
248- const job = this . internal . loadCache . get ( url )
250+ const job = this . internal ! . loadCache . get ( url )
249251 const plugin = this . ctx . loader . unwrapExports ( job ?. module ?. getNamespace ( ) )
250252 if ( ! job || ! plugin ) continue
251253 pending . set ( job , plugin )
@@ -292,9 +294,9 @@ class Hmr extends Service {
292294 const require = createRequire ( import . meta. url )
293295 for ( const filename of this . accepted ) {
294296 // Backup and clear ESM loadCache
295- const job = Map . prototype . get . call ( this . internal . loadCache , filename )
297+ const job = Map . prototype . get . call ( this . internal ! . loadCache , filename )
296298 esmBackup [ filename ] = job
297- Map . prototype . delete . call ( this . internal . loadCache , filename )
299+ Map . prototype . delete . call ( this . internal ! . loadCache , filename )
298300
299301 // Backup and clear CJS Module._cache
300302 try {
@@ -310,7 +312,7 @@ class Hmr extends Service {
310312
311313 const rollback = ( ) => {
312314 for ( const filename in esmBackup ) {
313- Map . prototype . set . call ( this . internal . loadCache , filename , esmBackup [ filename ] )
315+ Map . prototype . set . call ( this . internal ! . loadCache , filename , esmBackup [ filename ] )
314316 }
315317 for ( const filepath in cjsBackup ) {
316318 require . cache [ filepath ] = cjsBackup [ filepath ]
0 commit comments