controller.go

56 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
package frontend

import (
	"html/template"
	"log"
	"net/http"

	"congo.gg/pkg/application"
)

// Controller returns a named controller that integrates the frontend bundler with the application.
func (f *Frontend) Controller() (string, *Controller) {
	return "frontend", &Controller{frontend: f}
}

// Controller is the frontend controller that handles component bundling and serving.
type Controller struct {
	frontend *Frontend
}

// Setup is called when the controller is registered with the application.
// It builds the components and registers routes.
func (c *Controller) Setup(app *application.App) {
	// Build components
	if err := c.frontend.Build(); err != nil {
		if !c.frontend.DevMode {
			// In production, fail loudly so broken JavaScript is caught early
			log.Fatalf("[frontend] Build failed: %v", err)
		}
		log.Printf("[frontend] Build error: %v", err)
	}

	// Start file watcher for HMR in dev mode
	if c.frontend.DevMode {
		if err := c.frontend.Dev(); err != nil {
			log.Printf("[frontend] Dev server error: %v", err)
		}
	}

	// Register routes
	http.HandleFunc("GET /_frontend/hmr", c.frontend.handleHMR)
	http.HandleFunc("GET /_frontend/components.js", c.frontend.handleComponents)
	http.HandleFunc("GET /_frontend/components.js.map", c.frontend.handleSourceMap)

	// Register template functions
	// frontend_script is a placeholder; the real per-request version is injected
	// in render() via app.ScriptFunc with the CSP nonce.
	app.Func("frontend_script", func() template.HTML { return c.frontend.Script("") })
	app.ScriptFunc = c.frontend.Script
	app.Func("render", c.frontend.Render)
}

// Handle implements the Controller interface (required but not used for frontend).
func (c *Controller) Handle(r *http.Request) application.Controller {
	return c
}