@@ -48,7 +48,7 @@ func NewHandler(config Config) *Handler {
48
48
config .AppDir , _ = os .Getwd ()
49
49
}
50
50
s := & Handler {config : & config }
51
- s .etagSuffix = fmt . Sprintf ( "-v%d" , VERSION )
51
+ s .etagSuffix = "-" + VERSION
52
52
if s .config .Dev {
53
53
s .etagSuffix += "-dev"
54
54
}
@@ -252,6 +252,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
252
252
defer htmlFile .Close ()
253
253
254
254
u := url.URL {Path : filename }
255
+ im := base64 .RawURLEncoding .EncodeToString ([]byte (filename ))
255
256
tokenizer := html .NewTokenizer (htmlFile )
256
257
hotLinks := []string {}
257
258
unocss := ""
@@ -295,7 +296,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
295
296
switch attrKey {
296
297
case "src" :
297
298
w .Write ([]byte (" src=\" " ))
298
- w .Write ([]byte (srcAttr + "?im=" + base64 . RawURLEncoding . EncodeToString ([] byte ( filename )) ))
299
+ w .Write ([]byte (srcAttr + "?im=" + im ))
299
300
w .Write ([]byte {'"' })
300
301
default :
301
302
w .Write ([]byte {' ' })
@@ -317,7 +318,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
317
318
hrefAttr = u .ResolveReference (& url.URL {Path : hrefAttr }).Path
318
319
if hrefAttr == "uno.css" || strings .HasSuffix (hrefAttr , "/uno.css" ) {
319
320
if unocss == "" {
320
- unocssHref := hrefAttr + "?ctx=" + base64 . RawURLEncoding . EncodeToString ([] byte ( filename ))
321
+ unocssHref := hrefAttr + "?ctx=" + im
321
322
w .Write ([]byte ("<link rel=\" stylesheet\" href=\" " ))
322
323
w .Write ([]byte (unocssHref ))
323
324
w .Write ([]byte {'"' , '>' })
@@ -390,6 +391,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
390
391
}
391
392
w .Write (tokenizer .Raw ())
392
393
}
394
+
393
395
if s .config .Dev {
394
396
// reload the page when the html file is modified
395
397
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
408
410
}
409
411
w .Write ([]byte (`]){const el=$("link[href='"+href+"']");hot.watch(href,kind=>{if(kind==="modify")el.href=href+"?t="+Date.now().toString(36)})}` ))
410
412
}
413
+
411
414
// reload the unocss when the module dependency tree is changed
412
415
if unocss != "" {
413
416
w .Write ([]byte (`const uno="` ))
@@ -420,6 +423,7 @@ func (s *Handler) ServeHtml(w http.ResponseWriter, r *http.Request, filename str
420
423
w .Write ([]byte (filename ))
421
424
w .Write ([]byte ("\" ,()=>location.reload());" ))
422
425
}
426
+
423
427
w .Write ([]byte ("</script>" ))
424
428
w .Write ([]byte (`<script>console.log("%c💚 Built with esm.sh, please uncheck \"Disable cache\" in Network tab for better DX!", "color:green")</script>` ))
425
429
}
@@ -431,10 +435,13 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
431
435
http .Error (w , "Bad Request" , 400 )
432
436
return
433
437
}
434
-
435
- importMapRaw , importMap , err := s .parseImportMap (string (im ))
438
+ imfi , err := os .Lstat (filepath .Join (s .config .AppDir , string (im )))
436
439
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
+ }
438
445
return
439
446
}
440
447
@@ -458,9 +465,7 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
458
465
modTime = uint64 (fi .ModTime ().UnixMilli ())
459
466
size = fi .Size ()
460
467
}
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 )
464
469
if r .Header .Get ("If-None-Match" ) == etag && ! query .Has ("t" ) {
465
470
w .WriteHeader (http .StatusNotModified )
466
471
return
@@ -485,11 +490,16 @@ func (s *Handler) ServeModule(w http.ResponseWriter, r *http.Request, filename s
485
490
http .Error (w , "Loader worker not started" , 500 )
486
491
return
487
492
}
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
+ }
488
498
args := []any {"tsx" , filename , importMap , nil , s .config .Dev }
489
499
if preTransform != nil {
490
500
args [3 ] = string (preTransform )
491
501
}
492
- _ , js , err := s .callLoader (args ... )
502
+ _ , js , err := s .callLoaderJS (args ... )
493
503
if err != nil {
494
504
fmt .Println (term .Red ("[error] " + err .Error ()))
495
505
http .Error (w , "Internal Server Error" , 500 )
@@ -700,12 +710,12 @@ func (s *Handler) ServeUnoCSS(w http.ResponseWriter, r *http.Request, query url.
700
710
for moreAttr {
701
711
var key , val []byte
702
712
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" )) {
704
716
srcAttr = string (val )
705
717
} else if bytes .Equal (key , []byte ("href" )) {
706
718
hrefAttr = string (val )
707
- } else if bytes .Equal (key , []byte ("type" )) {
708
- typeAttr = string (val )
709
719
}
710
720
}
711
721
if typeAttr == "importmap" {
@@ -885,15 +895,14 @@ func (s *Handler) ServeHmrWS(w http.ResponseWriter, r *http.Request) {
885
895
}
886
896
}
887
897
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 ))
891
900
if err != nil {
892
901
return
893
902
}
894
- defer imHtmlFile .Close ()
903
+ defer file .Close ()
895
904
896
- tokenizer := html .NewTokenizer (imHtmlFile )
905
+ tokenizer := html .NewTokenizer (file )
897
906
for {
898
907
tt := tokenizer .Next ()
899
908
if tt == html .ErrorToken {
@@ -918,7 +927,7 @@ func (s *Handler) parseImportMap(im string) (importMapRaw []byte, importMap impo
918
927
err = errors .New ("invalid import map" )
919
928
return
920
929
}
921
- importMap .Src = "file://" + string (im )
930
+ importMap .Src = "file://" + string (filename )
922
931
// todo: cache parsed import map
923
932
break
924
933
}
@@ -1004,7 +1013,7 @@ func (s *Handler) analyzeDependencyTree(entry string, importMap importmap.Import
1004
1013
if s .loaderWorker == nil {
1005
1014
return esbuild.OnLoadResult {}, errors .New ("loader worker not started" )
1006
1015
}
1007
- lang , code , err := s .callLoader (ext [1 :], pathname , contents , importMap )
1016
+ lang , code , err := s .callLoaderJS (ext [1 :], pathname , contents , importMap )
1008
1017
if err != nil {
1009
1018
return esbuild.OnLoadResult {}, err
1010
1019
}
@@ -1083,22 +1092,75 @@ func (s *Handler) startLoaderWorker() (err error) {
1083
1092
if err != nil {
1084
1093
return err
1085
1094
}
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 {}{}
1093
1136
}
1094
1137
}
1095
1138
}
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
+ }
1099
1161
}
1100
1162
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 ) {
1102
1164
var data string
1103
1165
format , data , err = s .loaderWorker .Call (args ... )
1104
1166
if err != nil {
0 commit comments