153 lines
3.9 KiB
Go
153 lines
3.9 KiB
Go
//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
|
|
ticker := time.NewTicker(pollInterval)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
input, err := coreio.Local.Read(inputPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
select {
|
|
case <-ctx.Done():
|
|
if errors.Is(ctx.Err(), context.Canceled) {
|
|
return nil
|
|
}
|
|
return ctx.Err()
|
|
case <-ticker.C:
|
|
}
|
|
continue
|
|
}
|
|
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 <-ticker.C:
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|