main.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. )
  12. func main() {
  13. if len(os.Args) <= 1 || os.Args[1] == "serve" {
  14. serve()
  15. return
  16. }
  17. switch os.Args[1] {
  18. case "build":
  19. out := "dist/xjs.js"
  20. if len(os.Args) >= 3 && strings.TrimSpace(os.Args[2]) != "" {
  21. out = os.Args[2]
  22. }
  23. if err := build(out); err != nil {
  24. log.Fatal(err)
  25. }
  26. log.Printf("Built %s\n", out)
  27. default:
  28. fmt.Println("Usage:")
  29. fmt.Println(" go run main.go serve # dev server (default)")
  30. fmt.Println(" go run main.go build [out.js] # bundle animal.js + layer.js into dist/xjs.js (or out.js)")
  31. os.Exit(2)
  32. }
  33. }
  34. func serve() {
  35. // Serve files from the current directory
  36. fs := http.FileServer(http.Dir("."))
  37. noCache := func(next http.Handler) http.Handler {
  38. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  39. // Dev server: disable cache to avoid stale JS/CSS/HTML in iframes.
  40. w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
  41. w.Header().Set("Pragma", "no-cache")
  42. w.Header().Set("Expires", "0")
  43. next.ServeHTTP(w, r)
  44. })
  45. }
  46. http.Handle("/", noCache(fs))
  47. log.Println("Listening on :8080...")
  48. if err := http.ListenAndServe(":8080", nil); err != nil {
  49. log.Fatal(err)
  50. }
  51. }
  52. func readFile(path string) ([]byte, error) {
  53. f, err := os.Open(path)
  54. if err != nil {
  55. return nil, err
  56. }
  57. defer f.Close()
  58. return io.ReadAll(f)
  59. }
  60. func ensureDirForFile(out string) error {
  61. dir := filepath.Dir(out)
  62. if dir == "." || dir == "/" {
  63. return nil
  64. }
  65. return os.MkdirAll(dir, 0o755)
  66. }
  67. func build(out string) error {
  68. animal, err := readFile("animal.js")
  69. if err != nil {
  70. return fmt.Errorf("read animal.js: %w", err)
  71. }
  72. layer, err := readFile("layer.js")
  73. if err != nil {
  74. return fmt.Errorf("read layer.js: %w", err)
  75. }
  76. var buf bytes.Buffer
  77. buf.WriteString("// xjs.js - combined single-file build (animal + layer)\n")
  78. buf.WriteString("// Generated by `go run main.go build`.\n")
  79. buf.WriteString("// NOTE: Do not edit this file directly in development.\n")
  80. buf.WriteString("// Edit animal.js / layer.js instead.\n\n")
  81. // Keep layer first to match previous bundled layout; runtime lookups are lazy anyway.
  82. buf.WriteString("/* --- layer.js --- */\n")
  83. buf.Write(layer)
  84. if !bytes.HasSuffix(layer, []byte("\n")) {
  85. buf.WriteByte('\n')
  86. }
  87. buf.WriteString("\n/* --- animal.js --- */\n")
  88. buf.Write(animal)
  89. if !bytes.HasSuffix(animal, []byte("\n")) {
  90. buf.WriteByte('\n')
  91. }
  92. // Export globals expected by docs/users.
  93. buf.WriteString("\n;(function(g){\n")
  94. buf.WriteString(" try { if (g && g.animal && !g.xjs) g.xjs = g.animal; } catch {}\n")
  95. buf.WriteString(" try { if (g && g.XJS_GLOBAL_DOLLAR && !g.$ && g.xjs) g.$ = g.xjs; } catch {}\n")
  96. buf.WriteString("})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : this));\n")
  97. if err := ensureDirForFile(out); err != nil {
  98. return fmt.Errorf("mkdir %s: %w", filepath.Dir(out), err)
  99. }
  100. if err := os.WriteFile(out, buf.Bytes(), 0o644); err != nil {
  101. return fmt.Errorf("write %s: %w", out, err)
  102. }
  103. return nil
  104. }