//go:build !js // Package main provides a build-time CLI for generating Web Component JS bundles. // Reads a JSON slot map from stdin, writes the generated JS to stdout, and can // optionally watch a slot file and rewrite an output bundle on change. // // Usage: // // echo '{"H":"nav-bar","C":"main-content"}' | go run ./cmd/codegen/ > components.js // echo '{"H":"nav-bar","C":"main-content"}' | go run ./cmd/codegen/ -dts > components.d.ts // echo '{"H":"nav-bar","C":"main-content"}' > slots.json // go run ./cmd/codegen/ -watch -input slots.json -output components.js package main import ( "context" "encoding/json" "errors" "flag" goio "io" "os" "os/signal" "time" "dappco.re/go/core/html/codegen" coreio "dappco.re/go/core/io" log "dappco.re/go/core/log" ) var emitTypeDefinitions = flag.Bool("dts", false, "emit TypeScript declarations instead of JavaScript") var watchMode = flag.Bool("watch", false, "poll an input file and rewrite an output bundle when it changes") var watchInputPath = flag.String("input", "", "path to the JSON slot map used by -watch") var watchOutputPath = flag.String("output", "", "path to the generated bundle written by -watch") var watchPollInterval = flag.Duration("poll", 250*time.Millisecond, "poll interval used by -watch") func run(r goio.Reader, w goio.Writer) error { return runWithMode(r, w, false) } func runTypeDefinitions(r goio.Reader, w goio.Writer) error { return runWithMode(r, w, true) } func runWithMode(r goio.Reader, w goio.Writer, emitTypes bool) error { data, err := goio.ReadAll(r) if err != nil { return log.E("codegen", "reading stdin", err) } out, err := generate(data, emitTypes) if err != nil { return err } _, err = goio.WriteString(w, out) return err } func generate(data []byte, emitTypes bool) (string, error) { var slots map[string]string if err := json.Unmarshal(data, &slots); err != nil { return "", log.E("codegen", "invalid JSON", err) } if emitTypes { return codegen.GenerateTypeDefinitions(slots) } return codegen.GenerateBundle(slots) } func runDaemon(ctx context.Context, inputPath, outputPath string, emitTypes bool, pollInterval time.Duration) error { if inputPath == "" { return log.E("codegen", "watch mode requires -input", nil) } if outputPath == "" { return log.E("codegen", "watch mode requires -output", nil) } if pollInterval <= 0 { pollInterval = 250 * time.Millisecond } var lastInput string var lastOutput string for { input, err := coreio.Local.Read(inputPath) if err != nil { return log.E("codegen", "reading input file", err) } if input != lastInput { out, err := generate([]byte(input), emitTypes) if err != nil { // Watch mode should keep running through transient bad edits. log.Error("codegen watch skipped invalid input", "err", err) lastInput = input } else { if out != lastOutput { if err := coreio.Local.Write(outputPath, out); err != nil { return log.E("codegen", "writing output file", err) } lastOutput = out } lastInput = input } } select { case <-ctx.Done(): if errors.Is(ctx.Err(), context.Canceled) { return nil } return ctx.Err() case <-time.After(pollInterval): } } } func main() { flag.Parse() var err error if *watchMode { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() err = runDaemon(ctx, *watchInputPath, *watchOutputPath, *emitTypeDefinitions, *watchPollInterval) } else { if *emitTypeDefinitions { err = runTypeDefinitions(os.Stdin, os.Stdout) } else { err = run(os.Stdin, os.Stdout) } } if err != nil { log.Error("codegen failed", "err", err) os.Exit(1) } }