@@ -48,7 +48,7 @@ func NewHandler(config Config) *Handler {
4848 config .AppDir , _ = os .Getwd ()
4949 }
5050 s := & Handler {config : & config }
51- s .etagSuffix = fmt . Sprintf ( "-v%d" , VERSION )
51+ s .etagSuffix = "-" + VERSION
5252 if s .config .Dev {
5353 s .etagSuffix += "-dev"
5454 }
@@ -252,6 +252,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
252252 defer htmlFile .Close ()
253253
254254 u := url.URL {Path : filename }
255+ im := base64 .RawURLEncoding .EncodeToString ([]byte (filename ))
255256 tokenizer := html .NewTokenizer (htmlFile )
256257 hotLinks := []string {}
257258 unocss := ""
@@ -295,7 +296,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
295296 switch attrKey {
296297 case "src" :
297298 w .Write ([]byte (" src=\" " ))
298- w .Write ([]byte (srcAttr + "?im=" + base64 . RawURLEncoding . EncodeToString ([] byte ( filename )) ))
299+ w .Write ([]byte (srcAttr + "?im=" + im ))
299300 w .Write ([]byte {'"' })
300301 default :
301302 w .Write ([]byte {' ' })
@@ -317,7 +318,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
317318 hrefAttr = u .ResolveReference (& url.URL {Path : hrefAttr }).Path
318319 if hrefAttr == "uno.css" || strings .HasSuffix (hrefAttr , "/uno.css" ) {
319320 if unocss == "" {
320- unocssHref := hrefAttr + "?ctx=" + base64 . RawURLEncoding . EncodeToString ([] byte ( filename ))
321+ unocssHref := hrefAttr + "?ctx=" + im
321322 w .Write ([]byte ("<link rel=\" stylesheet\" href=\" " ))
322323 w .Write ([]byte (unocssHref ))
323324 w .Write ([]byte {'"' , '>' })
@@ -390,6 +391,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
390391 }
391392 w .Write (tokenizer .Raw ())
392393 }
394+
393395 if s .config .Dev {
394396 // reload the page when the html file is modified
395397 w .Write ([]byte (`<script type="module">import createHotContext from"/@hmr";const hot=createHotContext("` ))
@@ -408,6 +410,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
408410 }
409411 w .Write ([]byte (`]){const el=$("link[href='"+href+"']");hot.watch(href,kind=>{if(kind==="modify")el.href=href+"?t="+Date.now().toString(36)})}` ))
410412 }
413+
411414 // reload the unocss when the module dependency tree is changed
412415 if unocss != "" {
413416 w .Write ([]byte (`const uno="` ))
@@ -420,6 +423,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
420423 w .Write ([]byte (filename ))
421424 w .Write ([]byte ("\" ,()=>location.reload());" ))
422425 }
426+
423427 w .Write ([]byte ("</script>" ))
424428 w .Write ([]byte (`<script>console.log("%c💚 Built with esm.sh, please uncheck \"Disable cache\" in Network tab for better DX!", "color:green")</script>` ))
425429 }
@@ -431,10 +435,13 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
431435 http .Error (w , "Bad Request" , 400 )
432436 return
433437 }
434-
435- importMapRaw , importMap , err := s .parseImportMap (string (im ))
438+ imfi , err := os .Lstat (filepath .Join (s .config .AppDir , string (im )))
436439 if err != nil {
437- http .Error (w , "could not parse import map: " + err .Error (), 500 )
440+ if os .IsNotExist (err ) {
441+ http .Error (w , "Bad Request" , 400 )
442+ } else {
443+ http .Error (w , "Internal Server Error" , 500 )
444+ }
438445 return
439446 }
440447
@@ -458,9 +465,7 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
458465 modTime = uint64 (fi .ModTime ().UnixMilli ())
459466 size = fi .Size ()
460467 }
461- xx := xxhash .New ()
462- xx .Write ([]byte (importMapRaw ))
463- etag := fmt .Sprintf ("w/\" %x-%x-%x%s\" " , modTime , size , xx .Sum (nil ), s .etagSuffix )
468+ etag := fmt .Sprintf ("w/\" %x-%x-%x-%x%s\" " , modTime , size , imfi .ModTime ().UnixMilli (), imfi .Size (), s .etagSuffix )
464469 if r .Header .Get ("If-None-Match" ) == etag && ! query .Has ("t" ) {
465470 w .WriteHeader (http .StatusNotModified )
466471 return
@@ -485,11 +490,16 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
485490 http .Error (w , "Loader worker not started" , 500 )
486491 return
487492 }
493+ _ , importMap , err := s .parseImportMap (string (im ))
494+ if err != nil {
495+ http .Error (w , "could not parse import map: " + err .Error (), 500 )
496+ return
497+ }
488498 args := []any {"tsx" , filename , importMap , nil , s .config .Dev }
489499 if preTransform != nil {
490500 args [3 ] = string (preTransform )
491501 }
492- _ , js , err := s .callLoader (args ... )
502+ _ , js , err := s .callLoaderJS (args ... )
493503 if err != nil {
494504 fmt .Println (term .Red ("[error] " + err .Error ()))
495505 http .Error (w , "Internal Server Error" , 500 )
@@ -700,12 +710,12 @@ func (s *Handler) ServeUnoCSS(w http.ResponseWriter, r *http.Request, query url.
700710 for moreAttr {
701711 var key , val []byte
702712 key , val , moreAttr = tokenizer .TagAttr ()
703- if bytes .Equal (key , []byte ("src" )) {
713+ if bytes .Equal (key , []byte ("type" )) {
714+ typeAttr = string (val )
715+ } else if bytes .Equal (key , []byte ("src" )) {
704716 srcAttr = string (val )
705717 } else if bytes .Equal (key , []byte ("href" )) {
706718 hrefAttr = string (val )
707- } else if bytes .Equal (key , []byte ("type" )) {
708- typeAttr = string (val )
709719 }
710720 }
711721 if typeAttr == "importmap" {
@@ -885,15 +895,14 @@ func (s *Handler) ServeHmrWS(w http.ResponseWriter, r *http.Request) {
885895 }
886896}
887897
888- func (s * Handler ) parseImportMap (im string ) (importMapRaw []byte , importMap importmap.ImportMap , err error ) {
889- imHtmlFilename := filepath .Join (s .config .AppDir , im )
890- imHtmlFile , err := os .Open (imHtmlFilename )
898+ func (s * Handler ) parseImportMap (filename string ) (importMapRaw []byte , importMap importmap.ImportMap , err error ) {
899+ file , err := os .Open (filepath .Join (s .config .AppDir , filename ))
891900 if err != nil {
892901 return
893902 }
894- defer imHtmlFile .Close ()
903+ defer file .Close ()
895904
896- tokenizer := html .NewTokenizer (imHtmlFile )
905+ tokenizer := html .NewTokenizer (file )
897906 for {
898907 tt := tokenizer .Next ()
899908 if tt == html .ErrorToken {
@@ -918,7 +927,7 @@ func (s *Handler) parseImportMap(im string) (importMapRaw []byte, importMap impo
918927 err = errors .New ("invalid import map" )
919928 return
920929 }
921- importMap .Src = "file://" + string (im )
930+ importMap .Src = "file://" + string (filename )
922931 // todo: cache parsed import map
923932 break
924933 }
@@ -1004,7 +1013,7 @@ func (s *Handler) analyzeDependencyTree(entry string, importMap importmap.Import
10041013 if s .loaderWorker == nil {
10051014 return esbuild.OnLoadResult {}, errors .New ("loader worker not started" )
10061015 }
1007- lang , code , err := s .callLoader (ext [1 :], pathname , contents , importMap )
1016+ lang , code , err := s .callLoaderJS (ext [1 :], pathname , contents , importMap )
10081017 if err != nil {
10091018 return esbuild.OnLoadResult {}, err
10101019 }
@@ -1083,22 +1092,75 @@ func (s *Handler) startLoaderWorker() (err error) {
10831092 if err != nil {
10841093 return err
10851094 }
1086- go s .callLoader ("tsx" , "_.tsx" , nil , "" , false )
1087- go func () {
1088- entries , err := os .ReadDir (s .config .AppDir )
1089- if err == nil {
1090- for _ , entry := range entries {
1091- if entry .Type ().IsRegular () && entry .Name () == "uno.css" {
1092- go s .callLoader ("unocss" , "_uno.css" , "flex" )
1095+ go s .preload ()
1096+ s .loaderWorker = loaderWorker
1097+ return
1098+ }
1099+
1100+ func (s * Handler ) preload () {
1101+ indexHtmlFilename := filepath .Join (s .config .AppDir , "index.html" )
1102+ indexHtmlFile , err := os .Open (indexHtmlFilename )
1103+ if err != nil {
1104+ return
1105+ }
1106+ defer indexHtmlFile .Close ()
1107+ entries := map [string ]struct {}{}
1108+ tokenizer := html .NewTokenizer (indexHtmlFile )
1109+ for {
1110+ tt := tokenizer .Next ()
1111+ if tt == html .ErrorToken {
1112+ break
1113+ }
1114+ if tt == html .StartTagToken {
1115+ tagName , moreAttr := tokenizer .TagName ()
1116+ if string (tagName ) == "script" {
1117+ var (
1118+ srcAttr string
1119+ hrefAttr string
1120+ )
1121+ for moreAttr {
1122+ var key , val []byte
1123+ key , val , moreAttr = tokenizer .TagAttr ()
1124+ if bytes .Equal (key , []byte ("src" )) {
1125+ srcAttr = string (val )
1126+ } else if bytes .Equal (key , []byte ("href" )) {
1127+ hrefAttr = string (val )
1128+ }
1129+ }
1130+ if hrefAttr != "" && isHttpSepcifier (srcAttr ) {
1131+ if ! isHttpSepcifier (hrefAttr ) && (hrefAttr == "uno.css" || strings .HasSuffix (hrefAttr , "/uno.css" ) || isModulePath (hrefAttr )) {
1132+ entries [hrefAttr ] = struct {}{}
1133+ }
1134+ } else if ! isHttpSepcifier (srcAttr ) && isModulePath (srcAttr ) {
1135+ entries [srcAttr ] = struct {}{}
10931136 }
10941137 }
10951138 }
1096- }()
1097- s .loaderWorker = loaderWorker
1098- return
1139+ }
1140+ if len (entries ) > 0 {
1141+ u := url.URL {Path : "/" }
1142+ im := base64 .RawURLEncoding .EncodeToString ([]byte ("/index.html" ))
1143+ w := & dummyResponseWriter {}
1144+ for entry := range entries {
1145+ pathname := u .ResolveReference (& url.URL {Path : entry }).Path
1146+ r := & http.Request {
1147+ Method : "GET" ,
1148+ URL : & url.URL {
1149+ Path : pathname ,
1150+ },
1151+ }
1152+ if strings .HasSuffix (entry , "uno.css" ) {
1153+ r .URL .RawQuery = "ctx=" + im
1154+ s .ServeUnoCSS (w , r , r .URL .Query ())
1155+ } else {
1156+ r .URL .RawQuery = "im=" + im
1157+ s .ServeModule (w , r , pathname , r .URL .Query (), nil )
1158+ }
1159+ }
1160+ }
10991161}
11001162
1101- func (s * Handler ) callLoader (args ... any ) (format string , code string , err error ) {
1163+ func (s * Handler ) callLoaderJS (args ... any ) (format string , code string , err error ) {
11021164 var data string
11031165 format , data , err = s .loaderWorker .Call (args ... )
11041166 if err != nil {
0 commit comments