Documentation
¶
Overview ¶
Package view is the view layer: kyse for markup, HTMX for interaction, Alpine for ephemeral client state, Tailwind for style. It is a binary and it is never Node.
A project that uses it still runs with `git clone && aru dev`: no node_modules, no package.json, no lockfile of JavaScript, nothing installed beyond Go and the standalone binaries the CLI fetches. Having a build step is allowed; being Node is not (RULE 13).
It lives in the framework and the views do not. That is the split the Laravel mirror asks for: resources/views/ belongs to laravel/laravel, the rendering machinery to laravel/framework. It used to be a repository of its own, `porang`, and dissolving it is ADR 0021.
The error page deliberately does not use this package. It has to render when the rest is broken, including when the view build failed, so it stays as html/template inline in observability/errorpage.
Index ¶
- Constants
- func CSRF(w io.Writer, data any) error
- func Handler(w http.ResponseWriter, r *http.Request)
- func Include(w io.Writer, name string, data any) error
- func Register(name string, f Func)
- func RegisterLayout(name string, f LayoutFunc)
- func Registered() []string
- func RenderInto(w io.Writer, layout string, data any, ...) error
- func Text(v any) string
- func URL(name string) string
- func Version() string
- func WrongData(view, want string, got any) error
- func Yield(w io.Writer, sections map[string]func(io.Writer) error, name string) error
- type Func
- type LayoutFunc
- type Module
- type Renderer
Constants ¶
const AssetPath = "/_arandu/assets/"
AssetPath is where assets are served from. The hash is in the path, so the response can be cached forever and a new build simply has a new URL.
Variables ¶
This section is empty.
Functions ¶
func CSRF ¶
CSRF writes the hidden input a form needs.
The token comes from the data, through an interface the page data satisfies. It is not read from a global: a template that reaches for request state outside the data it was given is how a form ends up with another session's token under load.
func Handler ¶
func Handler(w http.ResponseWriter, r *http.Request)
Handler serves the embedded assets.
Anything whose path carries the right hash is immutable and cached for a year; a wrong hash is served without caching, so a stale reference degrades into a slow page rather than a broken one.
func Include ¶
Include renders a partial with the same data as the page.
Blade's @include shares the parent's variables. Here the data is one typed struct, so the partial receives exactly it -- and a partial that wants something else is a partial that takes different data, which is what a component is for.
func Register ¶
Register records a compiled view under the name a controller renders it by.
view.Register("invoices/index", renderInvoicesIndex)
Generated code calls it from init(), so importing the views package is what makes them reachable -- the same shape as a database/sql driver.
Registering twice panics rather than replacing. Two views for one name is a build artifact that outlived its source, and finding out at boot beats finding out from a page that renders the wrong thing.
func RegisterLayout ¶
func RegisterLayout(name string, f LayoutFunc)
RegisterLayout records a compiled layout. Generated code calls it from init() when the view contains a @yield.
func Registered ¶
func Registered() []string
Registered returns the known view names, sorted. `aru doctor` reads it to check that every ctx.View("x") has a view named x.
func RenderInto ¶
func RenderInto(w io.Writer, layout string, data any, sections map[string]func(io.Writer) error) error
RenderInto renders a layout, handing it the sections of the child view.
This is what `@extends` compiles to: the child does not write markup, it renders the layout and passes what goes in the holes. Same shape as Blade, where the child template is evaluated to fill sections before the layout runs.
func Text ¶
Text renders a value as a string, for interpolation.
It handles the types a view actually interpolates, and formats anything else with %v. It is not reflection over a struct: the field access already happened in generated Go, and this only turns the result into characters.
func URL ¶
URL returns the versioned path of an asset: /_arandu/assets/<hash>/htmx.min.js
The hash comes from the content, so upgrading HTMX changes the URL and no browser serves a stale script -- without anyone remembering to bump a version.
func Version ¶
func Version() string
Version reports the embedded version of each asset, for `aru doctor` and for the debug page.
func WrongData ¶
WrongData is what a generated view returns when the data is not the struct it declared.
Generated code calls it, so the message is the same everywhere:
d, ok := data.(HomeData)
if !ok { return view.WrongData("home", "HomeData", data) }
The alternative -- rendering the zero value -- is a blank page with a 200, which is the failure this framework exists to make impossible.
Types ¶
type Func ¶
Func is what a compiled view is: a function that writes HTML.
`aru view:build` emits one per `.kyse.go` and registers it by name. The data arrives as `any` and the generated function asserts it back to the struct the view declared -- which is why a wrong type is an error naming both sides rather than a blank page.
type LayoutFunc ¶
LayoutFunc is a view that receives sections, which is what a layout is.
type Module ¶
type Module struct{}
Module serves the embedded assets and wires the renderer.
It is a kernel.Module for one reason: it was not, and every application shipped a page that asked for its own stylesheet and got 404.
The route used to be exported as Mount, a plain function, with a comment arguing that "a module that exists only to serve two files is a module people have to remember to register". That reasoning was backwards, and the code proved it: Mount had zero call sites across every repository -- the kernel did not call it, the generated main.go did not call it, and neither did the sign-in screen that `aru make:auth` writes. That screen emits three tags, and against a real server all three answered 404: no stylesheet, no HTMX, no Alpine. Found by audit, reproduced end to end.
A function has to be remembered. A module appears in the Register call next to events, jobs and the scheduler, which is where somebody reading main.go already looks to learn what an application is made of.
func NewModule ¶
func NewModule() *Module
NewModule returns the module.
k.Register(view.NewModule(), auth.New(...), events.NewModule())
func (*Module) Renderer ¶
Renderer supplies the view renderer to the kernel.
It is kernel.RendererProvider, an optional interface: the kernel asks every registered module whether it brings one, before any route is registered. That is what makes ctx.View work without the application calling a wiring function that somebody eventually forgets.
type Renderer ¶
type Renderer struct{}
Renderer draws a view. It is the concrete side of httpx.Renderer.
func NewRenderer ¶
func NewRenderer() *Renderer
NewRenderer returns the renderer. The kernel hands it to the router at boot.
func (*Renderer) Render ¶
func (*Renderer) Render(ctx context.Context, w http.ResponseWriter, status int, name string, data any) error
Render writes a named view to the response.
The render is recorded on the Collector, so a slow page shows on the console whether the time went to the database or to the markup -- which is the difference between two very different afternoons.