@@ -23,6 +23,7 @@ export const serveHot = (options) => {
2323 const env = typeof Deno === "object" ? Deno . env . toObject ( ) : process . env ;
2424 const onFsNotify = fs . watch ( root ) ;
2525 const contentCache = new Map ( ) ; // todo: use worker `caches` api if possible
26+ const hotClients = new Map ( ) ;
2627
2728 /**
2829 * Fetcher handles requests for hot applications.
@@ -193,7 +194,9 @@ export const serveHot = (options) => {
193194 return new Response ( "[]" , { headers } ) ;
194195 }
195196 const entries = await fs . ls ( root ) ;
196- const matched = entries . filter ( ( entry ) => glob . includes ( entry ) || entry . match ( globToRegExp ( glob ) ) ) ;
197+ const matched = entries . filter ( ( entry ) =>
198+ glob . includes ( entry ) || entry . match ( globToRegExp ( glob ) )
199+ ) ;
197200 if ( ! matched . length ) {
198201 return new Response ( "[]" , { headers } ) ;
199202 }
@@ -241,23 +244,47 @@ export const serveHot = (options) => {
241244
242245 /** Event stream for HMR */
243246 case "/@hot-events" : {
247+ const channelName = url . searchParams . get ( "channel" ) ;
248+ const devChannel = channelName === "dev" ;
244249 const disposes = [ ] ;
250+ if ( req . method === "POST" ) {
251+ const data = await req . json ( ) ;
252+ const clients = hotClients . get ( channelName )
253+ if ( ! clients ) {
254+ return new Response ( "Channel not found" , { status : 404 } ) ;
255+ }
256+ clients . forEach ( ( { sentEvent } ) => sentEvent ( "message" , data ) ) ;
257+ return new Response ( "Ok" ) ;
258+ }
245259 return new Response (
246260 new ReadableStream ( {
247261 start ( controller ) {
248- const sendEvent = ( eventName , data ) => {
249- controller . enqueue ( "event: " + eventName + "\ndata: " + JSON . stringify ( data ) + "\n\n" ) ;
262+ const sentEvent = ( eventName , data ) => {
263+ controller . enqueue (
264+ "event: " + eventName + "\ndata: " + JSON . stringify ( data ) +
265+ "\n\n" ,
266+ ) ;
250267 } ;
251- disposes . push ( onFsNotify ( ( type , name ) => {
252- sendEvent ( "fs-notify" , { type, name } ) ;
253- } ) ) ;
254- controller . enqueue ( ": hot notify stream\n\n" ) ;
255- if ( isLocalHost ( url ) ) {
256- sendEvent ( "open-devtools" , null ) ;
268+ controller . enqueue ( ": hot events stream\n\n" ) ;
269+ if ( devChannel ) {
270+ disposes . push ( onFsNotify ( ( type , name ) => {
271+ sentEvent ( "fs-notify" , { type, name } ) ;
272+ } ) ) ;
273+ if ( isLocalHost ( url ) ) {
274+ sentEvent ( "open-devtools" , null ) ;
275+ }
276+ } else {
277+ const map = hotClients . get ( channelName ) ??
278+ hotClients . set ( channelName , new Map ( ) ) . get ( channelName ) ;
279+ map . set ( req , { sentEvent } ) ;
257280 }
258281 } ,
259282 cancel ( ) {
260- disposes . forEach ( ( dispose ) => dispose ( ) ) ;
283+ if ( devChannel ) {
284+ disposes . forEach ( ( dispose ) => dispose ( ) ) ;
285+ } else {
286+ hotClients . get ( channelName ) ?. delete ( req ) ;
287+ }
261288 } ,
262289 } ) ,
263290 {
@@ -299,12 +326,10 @@ export const serveHot = (options) => {
299326 ) ;
300327 }
301328 default : {
302- const htmls = [
303- pathname !== "/" ? pathname + ".html" : null ,
304- pathname !== "/" ? pathname + "/index.html" : null ,
305- "/404.html" ,
306- "/index.html" ,
307- ] . filter ( Boolean ) ;
329+ const htmls = [ "/404.html" , "/index.html" ] ;
330+ if ( pathname !== "/" ) {
331+ htmls . unshift ( pathname + ".html" , pathname + "/index.html" ) ;
332+ }
308333 for ( const path of htmls ) {
309334 filepath = path ;
310335 file = await fs . open ( root + filepath ) ;
@@ -376,7 +401,9 @@ export const serveHot = (options) => {
376401 const { pathname } = new URL ( content , url . origin + filepath ) ;
377402 const index = await fs . ls ( root + pathname ) ;
378403 el . replace (
379- `<script type="applicatin/json" id="@hot/router">${ JSON . stringify ( { index } ) } </script>` ,
404+ `<script type="applicatin/json" id="@hot/router">${
405+ JSON . stringify ( { index } )
406+ } </script>`,
380407 { html : true } ,
381408 ) ;
382409 } ,
@@ -417,7 +444,9 @@ export const serveHot = (options) => {
417444 async element ( el ) {
418445 if ( contentMap ) {
419446 try {
420- const { contents = { } } = isNEString ( contentMap ) ? ( contentMap = JSON . parse ( contentMap ) ) : contentMap ;
447+ const { contents = { } } = isNEString ( contentMap )
448+ ? ( contentMap = JSON . parse ( contentMap ) )
449+ : contentMap ;
421450 const name = el . getAttribute ( "from" ) ;
422451 let content = contents [ name ] ;
423452 let asterisk = undefined ;
@@ -454,7 +483,9 @@ export const serveHot = (options) => {
454483 value = new Function ( "return this." + expr ) . call ( data ) ;
455484 }
456485 }
457- return ! isNullish ( value ) ? value . toString ?. ( ) ?? stringify ( value ) : "" ;
486+ return ! isNullish ( value )
487+ ? value . toString ?. ( ) ?? stringify ( value )
488+ : "" ;
458489 } ;
459490 const render = ( data ) => {
460491 el . setInnerContent ( process ( data ) , { html : true } ) ;
0 commit comments