package bundler // Public-route code generation. The single source of truth is the TypeScript // manifest frontend/src/pages/public/pages.ts. This reads it (via esbuild + // goja, so it's real evaluation, not fragile text parsing) and generates: // // - internal/handlers/public_pages.gen.go Go registry: routes + + // the page's pre-rendered body (baked here, see genssr.go: writeGoRegistry) // - frontend/src/pages/public/routes.gen.ts client router maps: route → body // component (publicRoutes) and route → <title> (publicTitles) // // Generated files are committed like the other build artifacts; regenerate by // running the bundler. import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "github.com/dop251/goja" esbuild "github.com/evanw/esbuild/pkg/api" ) const pagesManifest = "src/pages/public/pages.ts" // relative to frontendDir type pageDef struct { Path string `json:"path"` Module string `json:"module"` // relative to frontend/src Component string `json:"component"` Title string `json:"title"` Dynamic bool `json:"dynamic"` // ISR page: also bake its render JS for request-time data rendering } func generatePublicRoutes() error { defs, err := loadPageDefs() if err != nil { return fmt.Errorf("loading page manifest: %w", err) } if err := writeGoRegistry(defs); err != nil { return fmt.Errorf("writing Go registry: %w", err) } if err := writeClientRoutes(defs); err != nil { return fmt.Errorf("writing client routes: %w", err) } fmt.Printf(" Public routes: %d page(s) generated\n", len(defs)) return nil } // loadPageDefs bundles and evaluates the TS manifest to get the page list. func loadPageDefs() ([]pageDef, error) { absCwd, err := filepath.Abs(".") if err != nil { return nil, err } pagesAbs := filepath.ToSlash(filepath.Join(absCwd, frontendDir, pagesManifest)) entry := fmt.Sprintf("import { publicPages } from %q;\nglobalThis.__PAGES__ = JSON.stringify(publicPages);", pagesAbs) res := esbuild.Build(esbuild.BuildOptions{ Stdin: &esbuild.StdinOptions{ Contents: entry, ResolveDir: absCwd, Sourcefile: "pages-manifest.js", Loader: esbuild.LoaderTS, }, Bundle: true, Format: esbuild.FormatIIFE, Target: esbuild.ES2017, Platform: esbuild.PlatformNeutral, LogLevel: esbuild.LogLevelSilent, Write: false, }) if len(res.Errors) > 0 { msgs := esbuild.FormatMessages(res.Errors, esbuild.FormatMessagesOptions{}) return nil, fmt.Errorf("esbuild: %s", strings.Join(msgs, "\n")) } vm := goja.New() if _, err := vm.RunString(string(res.OutputFiles[0].Contents)); err != nil { return nil, err } raw := vm.Get("__PAGES__") if raw == nil { return nil, fmt.Errorf("manifest did not export publicPages") } var defs []pageDef if err := json.Unmarshal([]byte(raw.String()), &defs); err != nil { return nil, err } return defs, nil } // goRoute maps a URL pathname to a Go ServeMux pattern. Root needs "/{$}" so // it matches exactly instead of as a catch-all subtree. func goRoute(pathname string) string { if pathname == "/" { return "/{$}" } return pathname } func writeClientRoutes(defs []pageDef) error { // Dedupe imports by component name (a component may back several routes). seen := map[string]bool{} var imports, entries, titles strings.Builder for _, d := range defs { if !seen[d.Component] { seen[d.Component] = true rel, err := filepath.Rel("pages/public", filepath.FromSlash(d.Module)) if err != nil { return err } imp := "./" + filepath.ToSlash(rel) fmt.Fprintf(&imports, "import { %s } from %q;\n", d.Component, imp) } fmt.Fprintf(&entries, " %q: %s,\n", d.Path, d.Component) fmt.Fprintf(&titles, " %q: %q,\n", d.Path, d.Title) } var b strings.Builder b.WriteString("// Code generated by cmd/bundle; DO NOT EDIT.\n") b.WriteString("// Source: frontend/src/pages/public/pages.ts\n\n") b.WriteString("import { JSXElement } from \"solid-js\";\n") b.WriteString(imports.String()) b.WriteString("\n// Body component for each public route, keyed by URL pathname. The client\n") b.WriteString("// router (public.ts) renders these when navigating without a full reload.\n") b.WriteString("export const publicRoutes: Record<string, () => JSXElement> = {\n") b.WriteString(entries.String()) b.WriteString("};\n") b.WriteString("\n// <title> for each public route, applied by the client router on navigation\n") b.WriteString("// (the first load gets its title from the server-rendered shell).\n") b.WriteString("export const publicTitles: Record<string, string> = {\n") b.WriteString(titles.String()) b.WriteString("};\n") return os.WriteFile(filepath.Join(frontendDir, "src", "pages", "public", "routes.gen.ts"), []byte(b.String()), 0644) }