Files
AstroJS/resolver-go/cmd/api-server/main.go
T
2026-06-13 04:00:52 +08:00

143 lines
3.3 KiB
Go

package main
import (
"flag"
"log"
"net/http"
"strings"
"time"
"github.com/tgckpg/resolver-go/internal/closure"
"github.com/tgckpg/resolver-go/internal/compilecache"
"github.com/tgckpg/resolver-go/internal/generated"
"github.com/tgckpg/resolver-go/internal/minifier/css"
"github.com/tgckpg/resolver-go/internal/resolver"
)
func main() {
addr := flag.String("addr", ":8080", "listen address")
src := flag.String("src", "./src", "BotanJS source root")
flag.Parse()
r, err := resolver.New(*src, generated.ClassMap)
if err != nil {
log.Fatal(err)
}
h := handler{
r: r,
jsCache: compilecache.New(
closure.NewCompiler(), 2, 128,
),
cssCache: compilecache.New(
css.NewEsbuildCompiler(), 2, 128,
),
}
http.HandleFunc("/", h.index)
log.Printf("botan-api listening on %s", *addr)
log.Fatal(http.ListenAndServe(*addr, nil))
}
type handler struct {
r *resolver.Resolver
jsCache *compilecache.Cache
cssCache *compilecache.Cache
}
func (h handler) index(w http.ResponseWriter, req *http.Request) {
path := strings.Trim(req.URL.Path, "/")
if path == "" {
_, _ = w.Write([]byte("BotanJS Go resolver API\n"))
return
}
parts := strings.SplitN(path, "/", 2)
modeText := parts[0]
code := ""
if len(parts) == 2 {
code = parts[1]
} else {
code = req.URL.Query().Get("p")
}
if code == "" {
http.Error(w, "missing request payload", http.StatusBadRequest)
return
}
outMode := resolver.ModeJS
if strings.HasSuffix(modeText, "css") {
outMode = resolver.ModeCSS
} else if !strings.HasSuffix(modeText, "js") {
http.Error(w, "invalid mode", http.StatusBadRequest)
return
}
timerStart := time.Now()
res, err := h.r.ResolveRequest(code, outMode, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
if outMode == resolver.ModeJS {
state, compiled, err := h.jsCache.Get(res.Hash)
if state == compilecache.Ready {
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("X-Botan-Compiled", "hit")
elapsed := time.Since(timerStart)
w.Header().Set("X-Botan-Resolve-Time", elapsed.String())
w.Write(compiled)
return
}
jsExterns, err := h.r.GetExterns(generated.Externs)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
h.jsCache.Enqueue(compilecache.Job{
Hash: res.Hash,
Mode: "js",
Payload: closure.CompilePayload{
ExternSources: jsExterns,
JSSources: []closure.SourceInput{
{
Name: "botanjs-" + res.Hash + ".js",
Source: string(res.Content),
},
},
Defines: map[string]any{
"DEBUG": false,
},
},
})
} else if outMode == resolver.ModeCSS {
state, compiled, _ := h.cssCache.Get(res.Hash)
if state == compilecache.Ready {
w.Header().Set("Content-Type", "text/css")
w.Header().Set("X-Botan-Compiled", "hit")
elapsed := time.Since(timerStart)
w.Header().Set("X-Botan-Resolve-Time", elapsed.String())
w.Write(compiled)
return
}
h.cssCache.Enqueue(compilecache.Job{
Hash: res.Hash,
Mode: "css",
Payload: css.CompilePayload{
Name: "style.css",
Source: string(res.Content),
},
})
}
w.Header().Set("Content-Type", res.ContentType)
w.Header().Set("X-Botan-Compiled", "miss")
elapsed := time.Since(timerStart)
w.Header().Set("X-Botan-Resolve-Time", elapsed.String())
_, _ = w.Write(res.Content)
}