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) }