Documentation
¶
Index ¶
- Constants
- Variables
- func BindProps[T any](data any) T
- func CheckConditional(rc *RenderContext, p *Program) bool
- func ClearRenderState()
- func CurrentURL(r *http.Request) string
- func Execute(p *Program, w ByteWriter, rc *RenderContext, data any) error
- func HXPromptResponse(r *http.Request) string
- func HXTargetID(r *http.Request) string
- func HXTriggerID(r *http.Request) string
- func HashContent(p *Program) string
- func IsHTMXBoosted(r *http.Request) bool
- func IsHTMXHistoryRestore(r *http.Request) bool
- func IsHTMXRequest(r *http.Request) bool
- func IsVoidTag(tag string) bool
- func MinifyHTML(src []byte, cfg MinifyConfig) ([]byte, error)
- func Props[T any](c *ComponentContext) T
- func ReleaseContext(rc *RenderContext)
- func ReleasePerFlushWriter(pw ByteWriter)
- func ReleaseWriter(w ByteWriter)
- func RenderComponent(ctx context.Context, w io.Writer, name string, props any) error
- func SetFlusher(w ByteWriter, f http.Flusher)
- func SetRenderState(rc *RenderContext)
- func SuperpowerFuncs() template.FuncMap
- func TriggerName(r *http.Request) string
- func UseContextFunc(rc *RenderContext) any
- func UseState[T any](s *State, key string, initial T) (T, func(T))
- func UseStateOrDefault[T any](s *State, key string) (T, bool)
- func WithContext(ctx context.Context, rc *RenderContext) context.Context
- type ActionFunc
- type ActionProvider
- type AsyncOptioner
- type AttrKV
- type ByteWriter
- type Bytecode
- type Cache
- func (c *Cache) Close()
- func (c *Cache) Delete(engine, name string)
- func (c *Cache) Get(engine, name string) (*Program, bool)
- func (c *Cache) InvalidateTag(tag string) int
- func (c *Cache) Put(engine, name string, p *Program)
- func (c *Cache) SetTag(engine, name string, tags ...string)
- func (c *Cache) Stats() CacheStats
- type CacheStats
- type Children
- type Component
- type ComponentContext
- func (c *ComponentContext) ActionURL(action string) string
- func (c *ComponentContext) AddHXTrigger(name string)
- func (c *ComponentContext) AddHXTriggerAfterSettle(name string)
- func (c *ComponentContext) AddHXTriggerAfterSwap(name string)
- func (c *ComponentContext) AddHXTriggerWithDetail(name string, detail any)
- func (c *ComponentContext) AddMeta(name, content string)
- func (c *ComponentContext) GetFormError(key string) string
- func (c *ComponentContext) GetState(key string) (any, bool)
- func (c *ComponentContext) ProvideContext(key string, val any)
- func (c *ComponentContext) Render(name string, props any) error
- func (c *ComponentContext) RequiresCSS(href string)
- func (c *ComponentContext) RequiresJS(src string)
- func (c *ComponentContext) SetFormError(key, msg string)
- func (c *ComponentContext) SetState(key string, v any)
- func (c *ComponentContext) SetTitle(title string)
- func (c *ComponentContext) UseContext(key string) any
- func (c *ComponentContext) UseId() string
- func (c *ComponentContext) UseState(key string, initial any) (any, func(any))
- func (c *ComponentContext) Write(p []byte) (int, error)
- func (c *ComponentContext) WriteChildren()
- func (c *ComponentContext) WriteHydrateProps(attr string, props any) error
- func (c *ComponentContext) WriteSlot(name string)
- func (c *ComponentContext) WriteString(s string) (int, error)
- func (c *ComponentContext) Yield() error
- type ComponentFunc
- type ComponentMiddleware
- type ComponentRegistry
- func (r *ComponentRegistry) Define(name string) *Definition
- func (r *ComponentRegistry) Lookup(name string) (Component, bool)
- func (r *ComponentRegistry) Memoize(name string, keyer func(rc *RenderContext, data any) string)
- func (r *ComponentRegistry) Names() []string
- func (r *ComponentRegistry) Register(name string, c Component)
- func (r *ComponentRegistry) Unregister(name string)
- type ComponentRenderFunc
- type ComponentWithChildren
- type ComponentWithFuncs
- type Composition
- type CompressConfig
- type DataInjector
- type Definition
- func (d *Definition) Action(name string, fn ActionFunc) *Definition
- func (d *Definition) Async() *Definition
- func (d *Definition) ErrorBoundary(fn ErrorBoundaryFunc) *Definition
- func (d *Definition) Fallback(fn ComponentRenderFunc) *Definition
- func (d *Definition) Metadata(fn func(rc *RenderContext, data any) error) *Definition
- func (d *Definition) Register(cr *ComponentRegistry)
- func (d *Definition) Render(fn ComponentRenderFunc) *Definition
- func (d *Definition) RenderChildren(fn ComponentRenderFunc) *Definition
- func (d *Definition) Use(mw ...ComponentMiddleware) *Definition
- func (d *Definition) WithFuncs(funcs template.FuncMap) *Definition
- func (d *Definition) WithOOB(targetID string) *Definition
- type Dispatchable
- type Engine
- type EngineName
- type ErrorBoundaryFunc
- type Event
- type FuncMapBuilder
- type HTMXResponse
- type HXTriggers
- type MetadataProvider
- type MinifyConfig
- type NodeStream
- func (s *NodeStream) AttrKey(i, k int) string
- func (s *NodeStream) AttrVal(i, k int) any
- func (s *NodeStream) ChildRange(i int) (uint32, uint32)
- func (s *NodeStream) FirstChildOf(i int) int
- func (s *NodeStream) IsComponent(i int) bool
- func (s *NodeStream) IsText(i int) bool
- func (s *NodeStream) IsVoid(i int) bool
- func (s *NodeStream) NextSiblingOf(i int) int
- func (s *NodeStream) NumAttr(i int) int
- func (s *NodeStream) Reset()
- func (s *NodeStream) Text(i int) []byte
- type NodeStreamBuilder
- func (b *NodeStreamBuilder) AppendComponent(name string, attrs []string, vals []any) int
- func (b *NodeStreamBuilder) AppendNode(tag string, flags uint8) int
- func (b *NodeStreamBuilder) AppendText(text []byte, raw bool) int
- func (b *NodeStreamBuilder) Reserve(approxNodes int) *NodeStreamBuilder
- func (b *NodeStreamBuilder) ReserveText(srcSize int) *NodeStreamBuilder
- func (b *NodeStreamBuilder) Reset()
- func (b *NodeStreamBuilder) SetAttrs(i int, keys []string, vals []any)
- func (b *NodeStreamBuilder) SetAttrsEach(i int, attrs []AttrKV)
- func (b *NodeStreamBuilder) SetChildren(i int, start, end uint32)
- func (b *NodeStreamBuilder) SetParent(child, parent int)
- func (b *NodeStreamBuilder) Stream() NodeStream
- type OOBOptioner
- type Option
- func WithActionPrefix(prefix string) Option
- func WithCache(c *Cache) Option
- func WithCacheSize(n int) Option
- func WithCompression(level, minSize int) Option
- func WithConditional() Option
- func WithDebug() Option
- func WithDefaultLoader(loader SourceFunc) Option
- func WithEngine(e Engine) Option
- func WithEngines(engines ...Engine) Option
- func WithFuncMap(fn FuncMapBuilder) Option
- func WithInjector(fn DataInjector) Option
- func WithPipeline(p *Pipeline) Option
- func WithVariant(name string, values ...string) Option
- type PageComposer
- type ParseError
- type Pipeline
- type PreloadHint
- type PreloadHints
- type Program
- type Registry
- func (r *Registry) ActionPrefix() string
- func (r *Registry) AddEngine(e Engine) *Registry
- func (r *Registry) AttachComponents(c *ComponentRegistry)
- func (r *Registry) Cache() *Cache
- func (r *Registry) ComponentLookup(name string) (Component, bool)
- func (r *Registry) Components() *ComponentRegistry
- func (r *Registry) Compress(cfg CompressConfig) *Registry
- func (r *Registry) CompressWriter(rc *RenderContext, w ByteWriter) (ByteWriter, func() error)
- func (r *Registry) Conditional(enabled bool) *Registry
- func (r *Registry) Default() Engine
- func (r *Registry) DefaultLoader() SourceFunc
- func (r *Registry) Define(name string) *Definition
- func (r *Registry) Engine(name string) Engine
- func (r *Registry) Engines() []Engine
- func (r *Registry) FuncMap(b FuncMapBuilder)
- func (r *Registry) HandleAction(w http.ResponseWriter, req *http.Request)
- func (r *Registry) Inject(in DataInjector)
- func (r *Registry) InjectFuncMap(rc *RenderContext, data any)
- func (r *Registry) InvalidateTag(tag string) int
- func (r *Registry) Minify() *MinifyConfig
- func (r *Registry) MustPreloadParts(rc *RenderContext, engine string, parts ...string)
- func (r *Registry) Page(rc *RenderContext) *RenderBuilder
- func (r *Registry) Part(rc *RenderContext, engine, name string) (*Program, error)
- func (r *Registry) Preload(h PreloadHint)
- func (r *Registry) PreloadParts(rc *RenderContext, engine string, parts ...string) error
- func (r *Registry) Preloads() []PreloadHint
- func (r *Registry) Render(rc *RenderContext, engine, name string, data any) error
- func (r *Registry) RenderComponent(w ByteWriter, rc *RenderContext, name string, props any) error
- func (r *Registry) RenderComposition(rc *RenderContext, c Composition, data any) error
- func (r *Registry) RenderNamed(rc *RenderContext, engine EngineName, name string, data any) error
- func (r *Registry) RenderPage(rc *RenderContext, layout, view string, data any) error
- func (r *Registry) RenderPageEngines(rc *RenderContext, layoutEngine EngineName, layout string, ...) error
- func (r *Registry) RenderPageWith(rc *RenderContext, engine, layout, view string, data any) error
- func (r *Registry) RenderStream(rc *RenderContext, engine EngineName, layout, view string, data any, ...) error
- func (r *Registry) SetActionPrefix(prefix string)
- func (r *Registry) SetCache(c *Cache)
- func (r *Registry) SetDefaultLoader(loader SourceFunc)
- func (r *Registry) SetPipeline(p *Pipeline)
- func (r *Registry) SetTag(engine, name string, tags ...string)
- func (r *Registry) SetVariant(dim, value string)
- func (r *Registry) Unpreload(as, typeStr string)
- func (r *Registry) Variant(name string, values ...string)
- func (r *Registry) WithMinify(cfg MinifyConfig) *Registry
- type RenderBuilder
- func (rb *RenderBuilder) Engine(name EngineName) *RenderBuilder
- func (rb *RenderBuilder) EngineInstance(e Engine) *RenderBuilder
- func (rb *RenderBuilder) Layout(name string) *RenderBuilder
- func (rb *RenderBuilder) Parts(parts ...string) *RenderBuilder
- func (rb *RenderBuilder) Render() error
- func (rb *RenderBuilder) RenderComposition() error
- func (rb *RenderBuilder) View(name string) *RenderBuilder
- func (rb *RenderBuilder) With(data any) *RenderBuilder
- func (rb *RenderBuilder) WithDone(fn func()) *RenderBuilder
- type RenderContext
- func (rc *RenderContext) AddHXTrigger(name string)
- func (rc *RenderContext) AddHXTriggerAfterSettle(name string)
- func (rc *RenderContext) AddHXTriggerAfterSwap(name string)
- func (rc *RenderContext) AddHXTriggerWithDetail(name string, detail any)
- func (rc *RenderContext) AddMeta(name, content string)
- func (rc *RenderContext) ClearAssets()
- func (rc *RenderContext) ClearFormErrors()
- func (rc *RenderContext) ClearHeadState()
- func (rc *RenderContext) CloseSuspense()
- func (rc *RenderContext) ComponentRegistry() *ComponentRegistry
- func (rc *RenderContext) ComponentState() *State
- func (rc *RenderContext) ContextDepth() int
- func (rc *RenderContext) EffectiveFuncMap(programDefaults template.FuncMap) template.FuncMap
- func (rc *RenderContext) EnsureSuspense(w ByteWriter) *suspenseCoordinator
- func (rc *RenderContext) FormErrors() map[string]string
- func (rc *RenderContext) GetContext(key string) any
- func (rc *RenderContext) GetFormError(key string) string
- func (rc *RenderContext) HTMXResponse() HTMXResponse
- func (rc *RenderContext) HXTriggers() HXTriggers
- func (rc *RenderContext) HXTriggersAfterSettle() HXTriggers
- func (rc *RenderContext) HXTriggersAfterSwap() HXTriggers
- func (rc *RenderContext) OOBSink() ByteWriter
- func (rc *RenderContext) PopContextTo(savedPtr int)
- func (rc *RenderContext) PushContext(key string, val any)
- func (rc *RenderContext) RequiresCSS(href string)
- func (rc *RenderContext) RequiresJS(src string)
- func (rc *RenderContext) SetComponentRegistry(c *ComponentRegistry)
- func (rc *RenderContext) SetFormError(key, msg string)
- func (rc *RenderContext) SetHXLocation(url string)
- func (rc *RenderContext) SetHXPushURL(url string)
- func (rc *RenderContext) SetHXRedirect(url string)
- func (rc *RenderContext) SetHXRefresh()
- func (rc *RenderContext) SetHXReplaceURL(url string)
- func (rc *RenderContext) SetHXReselect(selector string)
- func (rc *RenderContext) SetHXResettle(value string)
- func (rc *RenderContext) SetHXReswap(strategy string)
- func (rc *RenderContext) SetHXRetarget(selector string)
- func (rc *RenderContext) SetOOBSink(w ByteWriter)
- func (rc *RenderContext) SetTitle(title string)
- func (rc *RenderContext) SetUserData(d any)
- func (rc *RenderContext) SetVariant(dim, value string)
- func (rc *RenderContext) SubmitAsync(id string, fn func(w ByteWriter, rc *RenderContext, data any) error, ...)
- func (rc *RenderContext) Suspense() *suspenseCoordinator
- func (rc *RenderContext) Title() string
- func (rc *RenderContext) UseId() string
- func (rc *RenderContext) UserData() any
- func (rc *RenderContext) WithDebug() *RenderContext
- func (rc *RenderContext) WithFuncMap(fm template.FuncMap) *RenderContext
- func (rc *RenderContext) WithLayout(name string) *RenderContext
- func (rc *RenderContext) WithLoader(loader SourceFunc) *RenderContext
- func (rc *RenderContext) WriteHTMXHeaders(w http.ResponseWriter)
- func (rc *RenderContext) WriteHeader(key, value string)
- func (rc *RenderContext) WriteHeadersTo(w io.Writer) error
- type Slots
- type SourceFunc
- type Sourceless
- type State
- type Watcher
Constants ¶
const ( // HXRequest is set to "true" on every HTMX request. Its presence // is the canonical "this is an HTMX request" signal. HXRequest = "HX-Request" // HXBoosted is set to "true" when the request came from an // hx-boosted element (typically an <a> or <form>). Boosted // requests render only the response body, not the full page. HXBoosted = "HX-Boosted" // HXHistoryRestoreRequest is set to "true" when HTMX is taking // a snapshot of the page (history restoration on navigation). // Servers should render the full page, not a partial swap. HXHistoryRestoreRequest = "HX-History-Restore-Request" // HXTarget is the id of the element targeted by the request. HXTarget = "HX-Target" // HXTrigger is the id of the triggered element (when the request // was triggered by an event, not a load). HXTrigger = "HX-Trigger" // HXTriggerName is the name attribute of the triggered element. HXTriggerName = "HX-Trigger-Name" // HXCurrentURL is the URL of the page the browser was on when // the request was made. Useful for server-side history tracking. HXCurrentURL = "HX-Current-URL" // HXPrompt is the user's response to an hx-prompt dialog. HXPrompt = "HX-Prompt" )
HTMX request header constants. HTMX sets these on every request it makes; servers use them to differentiate HTMX-driven requests from full-page loads.
See https://htmx.org/reference/#request_headers for the full list.
const ( // HXTriggerHeader (response) tells HTMX to trigger client-side // events after the response is swapped. Comma-separated list of // event names. nanite-render accumulates triggered events on // rc.hxTriggers during a render; the router copies the joined // list into this header. HXTriggerHeader = "HX-Trigger" // HXTriggerAfterSwap fires after the swap step. HXTriggerAfterSwap = "HX-Trigger-After-Swap" // HXTriggerAfterSettle fires after the settle step. HXTriggerAfterSettle = "HX-Trigger-After-Settle" // HXRetarget overrides hx-target on the triggered element. HXRetarget = "HX-Retarget" // HXReswapHeader overrides hx-swap on the triggered element. HXReswapHeader = "HX-Reswap" // HXPushURL pushes a new URL into the history stack. HXPushURL = "HX-Push-Url" // HXRedirect triggers a client-side redirect. HXRedirect = "HX-Redirect" // HXRefresh makes the client do a full page refresh. HXRefresh = "HX-Refresh" // HXReplaceURL replaces the current URL without history entry. HXReplaceURL = "HX-Replace-Url" // HXLocation triggers client-side navigation without history. HXLocation = "HX-Location" // HXReselect is a CSS selector telling HTMX which part of the // response body to extract and swap. Useful when the server // returns a wrapper page (e.g. layout) and only an inner region // should land in the DOM. HXReselect = "HX-Reselect" // HXResettle controls the settle algorithm after the swap. // "true" forces scroll reset to the top; "false" (default) tries // to preserve scroll position across the swap. HXResettle = "HX-Resettle" )
HTMX response header constants. The server sets these to drive HTMX client behavior.
Where the same header is used for both directions (e.g. HX-Trigger) the request constant (HXTrigger) and the response constant share the same value; the alias HXTriggerHeader is provided for code that handles both sides and wants to make the direction explicit.
const ( FlagVoid uint8 = 1 << 0 // <br>, <img>, <input> FlagSelfClosing uint8 = 1 << 1 // <MyComponent/> FlagComponent uint8 = 1 << 2 // templ component FlagFragment uint8 = 1 << 3 // <template> / no parent FlagRaw uint8 = 1 << 4 // text not escaped FlagText uint8 = 1 << 5 // synthetic text node FlagDynamicTag uint8 = 1 << 6 // <{tag}>, tag chosen at render FlagHasChildren uint8 = 1 << 7 // hot-loop guard for child walk )
Node flags. Stuffed in a uint8 so the Flags slice is one byte per node.
const ChildrenDataKey = "__children__"
ChildrenDataKey is the data-map key where the dispatcher stores a component's pre-rendered children. Components that compose via data maps can read this key to inject the children into their output:
cr.Register("Parent", render.ComponentFunc(
func(w, rc, data) error {
children := data[render.ChildrenDataKey].(render.Children)
for _, child := range children {
w.WriteString(child)
}
return nil
},
))
const ContextKey = "render"
ContextKey is the key used to store the *RenderContext on the nanite.Context's Values map (or any compat router's context map). Re-exported as a public constant so external callers can read the context back without stringly-typed map access.
const DefaultActionPrefix = "/_nano/action/"
DefaultActionPrefix is the default URL prefix for colocated server actions. Actions are mounted at {prefix}/{COMPONENT}/{action} — e.g. /_nano/action/LIKE_BUTTON/toggle. Override with WithActionPrefix.
const SlotsDataKey = "__slots__"
SlotsDataKey is the data-map key where the dispatcher stores the slots map. Components access this via data[SlotsDataKey].
Variables ¶
var ( // ErrEngineNotFound is returned when the requested engine is // not registered with the Registry. Likely a misconfiguration // in the boot path; usually indicates a 500. ErrEngineNotFound = errors.New("render: engine not found") // ErrTemplateNotFound is returned when the loader cannot find // the template. Suitable for 404 responses. ErrTemplateNotFound = errors.New("render: template not found") // ErrLoaderMissing is returned when no template loader is // configured on the RenderContext. Indicates the middleware // wasn't installed or the handler is missing setup. ErrLoaderMissing = errors.New("render: no loader on context") // ErrLayoutMissing is returned when a layout is required but // not provided. Misconfiguration; usually 500. ErrLayoutMissing = errors.New("render: layout missing") // ErrRenderPageInvalid is returned when RenderPage is called // with invalid arguments (empty layout or view, missing data). ErrRenderPageInvalid = errors.New("render: invalid render page") )
Sentinel errors. Routers can match these via errors.Is to translate framework errors into HTTP responses:
switch {
case errors.Is(err, render.ErrTemplateNotFound):
c.JSON(404, ...)
case errors.Is(err, render.ErrEngineNotFound):
c.JSON(500, "engine not registered")
default:
c.JSON(500, err.Error())
}
var ErrValidation = errors.New("render: validation error")
ErrValidation is the sentinel error an Action returns to signal a validation failure. HandleAction intercepts it: instead of returning 500, it re-renders the component with the form errors already set on the RenderContext (via rc.SetFormError) and returns 200 so HTMX swaps the form with the inline error spans.
Match with errors.Is — actions may wrap it:
return fmt.Errorf("submit: %w", render.ErrValidation)
Functions ¶
func BindProps ¶
BindProps unmarshals a data map (or any value) into a typed struct T. It uses `nanite:"key"` struct tags to map fields to data-map keys. The default key is the lowercased field name.
Implementation: zero heap allocations on the hot path. The struct T is declared on the stack; per-type field layouts (offsets + reflect.Type) are cached in a sync.Map keyed by reflect.Type, so reflection runs once per distinct T.
Scalar fields (string, bool, int*, uint*, float*) are written directly via unsafe.Pointer arithmetic — no reflect.Value.Set, no boxing, no per-call reflection. Complex fields (slices, maps, pointers, nested structs, custom interface types) fall back to reflect.Value.Set which can allocate. Typical prop structs are mostly scalars; complex fields are the exception.
Zero-value semantics: missing keys leave the field at its zero value. Type mismatches leave the field at its zero value.
Usage:
type NavbarProps struct {
Theme string `nanite:"theme"`
Size int `nanite:"size"`
}
cr.Register("NAVBAR", render.ComponentFunc(
func(w, rc, data) error {
props := render.BindProps[NavbarProps](data)
return nil
},
))
func CheckConditional ¶
func CheckConditional(rc *RenderContext, p *Program) bool
CheckConditional compares the program's ETag against the request's If-None-Match header. If it matches, the function writes a 304 response (with appropriate headers) and returns true.
func ClearRenderState ¶
func ClearRenderState()
ClearRenderState clears the per-render state. Engine adapters call this after Execute completes.
func CurrentURL ¶
CurrentURL returns the URL of the page that initiated the request. Useful for server-side redirect logic that needs to know where the user came from.
func Execute ¶
func Execute(p *Program, w ByteWriter, rc *RenderContext, data any) error
Execute renders the program to w. When the program carries a compiled Bytecode (see CompileBytecode), the flat instruction stream is executed — static HTML is coalesced into a few writes, matching templ's compile-to-output speed. Otherwise the NodeStream is walked depth-first via the FirstChild/NextSibling links (linear-time, zero-alloc for hand-built streams).
func HXPromptResponse ¶
HXPromptResponse returns the user's response to an hx-prompt dialog. Empty when no prompt was shown.
func HXTargetID ¶
HXTargetID returns the id of the element HTMX is targeting. Empty when no hx-target was specified (HTMX defaults to the triggering element).
func HXTriggerID ¶
HXTriggerID returns the id of the element that triggered the request. Empty when the request was a load (no specific trigger).
func HashContent ¶
HashContent computes a strong ETag for a program. The hash is derived from the engine name, template name, and the program's content fingerprint. Compile-time; not re-computed on every render.
The computed ETag is stored on the Program.ETag field so the render path reads it with no allocation.
func IsHTMXBoosted ¶
IsHTMXBoosted reports whether the request came from an hx-boost (typically a link or form that the user activated). Boosted requests should render the response body only — the layout is already on the page.
func IsHTMXHistoryRestore ¶
IsHTMXHistoryRestore reports whether HTMX is restoring a history snapshot. Servers should render the full page (no partial swap) so the snapshot is faithful.
func IsHTMXRequest ¶
IsHTMXRequest reports whether the request was made by HTMX. This is the canonical check; all other HTMX request modifiers imply IsHTMXRequest is also true.
func MinifyHTML ¶
func MinifyHTML(src []byte, cfg MinifyConfig) ([]byte, error)
MinifyHTML minifies the given HTML according to the config. Used by engines at compile time to produce the smallest possible cached program.
func Props ¶
func Props[T any](c *ComponentContext) T
Props binds c.Data to a typed struct via nanite:"key" tags. Equivalent to render.BindProps[T](c.Data).
func ReleaseContext ¶
func ReleaseContext(rc *RenderContext)
ReleaseContext returns the context to the pool. The associated Writer is *not* closed by this call; the caller is responsible for flushing.
func ReleasePerFlushWriter ¶
func ReleasePerFlushWriter(pw ByteWriter)
ReleasePerFlushWriter returns pw to the pool. Flushes any remaining bytes through to the parent. Safe to call with nil.
func ReleaseWriter ¶
func ReleaseWriter(w ByteWriter)
ReleaseWriter returns w to the pool. The write buffer is retained for the next user.
func RenderComponent ¶
RenderComponent renders a named, registered component inline, writing to w. It recovers the *RenderContext from ctx (see WithContext), so a templ component can compose a nanite-render component like this:
rc := render.FromContext(ctx) render.RenderComponent(ctx, w, "Navbar", props)
w may be any io.Writer (templ components receive io.Writer); if it is not a ByteWriter, a pooled writer is used and released.
func SetFlusher ¶
func SetFlusher(w ByteWriter, f http.Flusher)
SetFlusher explicitly attaches an http.Flusher. Use this from middleware that wraps the underlying writer (e.g. gzip, compression) AFTER AcquireWriter has already run — the wrapper still satisfies http.Flusher even though the original writer was wrapped. Passing nil clears the flusher.
This is a no-op when the writer is not a *byteWriter (other writers in the ByteWriter interface have their own Flush implementations).
func SetRenderState ¶
func SetRenderState(rc *RenderContext)
SetRenderState populates the per-render state. Engine adapters that use html/template (jade, HTMLTemplate) call this before Execute and call ClearRenderState after.
func SuperpowerFuncs ¶
SuperpowerFuncs returns a FuncMap that exposes the framework's cross-engine superpowers: `component` and `yield`. Engines that use html/template (jade, the HTMLTemplate adapter) inject this into their template compilation.
func TriggerName ¶
TriggerName returns the name attribute of the triggered element. Useful for forms whose submit handler depends on the input name.
func UseContextFunc ¶
func UseContextFunc(rc *RenderContext) any
UseContextFunc returns a template.FuncMap entry that resolves the cascading-context stack. Register it via WithFuncMap so templates can call {{ useContext "theme" }} and get the nearest binding. The result is `any` — the template type-asserts.
Usage:
reg := render.New(render.WithFuncMap(render.UseContextFunc))
// or merge into your own builder:
fm := template.FuncMap{"useContext": render.UseContextFunc}
Templates that don't go through the per-request FuncMap won't see this helper — but fluent components use ComponentContext directly.
func UseState ¶
UseState is a generic, type-safe wrapper around State.UseState. It returns the value (typed T) and a setter. The setter writes the new value back to the same key. This is the React-style useState hook for Go templates — no type assertions needed.
Usage in a component:
count, setCount := render.UseState(state, "count", 0) setCount(count + 1)
The first call returns initial and stores it. Subsequent calls return the stored value. The setter always writes the typed value.
func UseStateOrDefault ¶
UseStateOrDefault returns the value for the given key if it is set and is of type T. The boolean indicates whether both the key was present and the type assertion succeeded.
func WithContext ¶
func WithContext(ctx context.Context, rc *RenderContext) context.Context
WithContext returns a context carrying the given *RenderContext. Engine adapters that render through a context.Context (notably templ, whose components receive one) use this so components can reach the framework's runtime layer.
A templ component can recover the framework state like this:
rc := render.FromContext(ctx) count, setCount := render.UseState(rc.ComponentState(), "count", 0) slots, _ := rc.Slots // or data[SlotsDataKey]
Types ¶
type ActionFunc ¶
type ActionFunc func(rc *RenderContext, props map[string]any) error
ActionFunc is a colocated server action: mutation logic that lives next to the component that triggers it. Invoked by Registry.HandleAction when the action's URL is POSTed.
props is the parsed request body — JSON bodies decode into a map[string]any with real types (BindProps-compatible); form bodies get best-effort conversion ("42" → int, "true" → bool, else string).
The state contract: this framework does NOT persist state across requests. The action mutates the user's own storage (DB, session, cookie), and the subsequent re-render of the component is a pure function of (props, request). Per-request state set here (rc.SetState) IS visible to the re-render that follows in the same request.
type ActionProvider ¶
type ActionProvider interface {
Component
LookupAction(name string) (ActionFunc, bool)
}
ActionProvider is an optional interface a Component implements to expose colocated server actions. Engines that pre-compile components can implement it directly; the fluent builder exposes it via Definition.Action.
type AsyncOptioner ¶
type AsyncOptioner interface {
Component
AsyncFallback() (id string, render func(w ByteWriter, rc *RenderContext, data any) error)
}
AsyncOptioner is an optional interface a Component implements to participate in Suspense-style asynchronous rendering. Components that opt in via AsyncFallback() have their render executed in a background goroutine; the executor writes the fallback HTML inline (TTFB ≈ 0ms for that region), then emits the real output as a trailing OOB chunk when the worker finishes.
Suspense trades Sum(component_times) for Max(component_times) on independent components. It requires an http.Flusher-compatible response writer; non-flushing writers fall back to synchronous rendering of the slow component.
Engines that pre-compile components can implement AsyncOptioner directly. The fluent builder exposes this via Definition.Async().Fallback(fn).
type AttrKV ¶
type AttrKV struct {
K, V string
}
AttrKV is a (key, value) pair used during parsing. Exported so the parser can pass a slice of these to SetAttrsEach without needing intermediate []string / []any slices.
type ByteWriter ¶
type ByteWriter interface {
io.Writer
io.ByteWriter
io.StringWriter
// Flush drains the internal buffer into the wrapped writer.
Flush() error
// Reset re-targets the writer at a new underlying io.Writer. Used
// when the writer is reused across requests.
Reset(w io.Writer)
// Bytes returns the current buffer contents. Intended for direct
// chunked writes (e.g. SSE) rather than the typical render path.
Bytes() []byte
}
ByteWriter is the interface render writes through. Implementations are pooled; Flush returns the writer to its pool after draining the underlying io.Writer.
func AcquirePerFlushWriter ¶
func AcquirePerFlushWriter(parent ByteWriter, threshold int) ByteWriter
AcquirePerFlushWriter returns a per-flush writer wrapping parent. When threshold bytes accumulate, the writer auto-flushes to parent. A threshold of 0 disables auto-flush (writes pass through directly to parent). A negative threshold is treated as 0.
Callers must ReleasePerFlushWriter when done. The wrapper is safe to use as a ByteWriter; its Reset/Bytes methods delegate to parent.
func AcquireWriter ¶
func AcquireWriter(w io.Writer) ByteWriter
AcquireWriter returns a ByteWriter targeting w. The writer is reused from the pool; callers must Flush and ReleaseWriter when done. The internal buffer is reset to len 0 so previous renders do not leak into the new render.
If w implements http.Flusher (e.g. a net/http response writer), the flusher is captured once. Subsequent Flush() calls push the buffered bytes to the underlying writer AND signal the flusher, so chunked HTTP responses emit each chunk as it is produced.
type Bytecode ¶
type Bytecode struct {
// contains filtered or unexported fields
}
Bytecode is a flat, zero-allocation instruction stream compiled from a NodeStream at compile time.
Static HTML (tags, attributes, text, and HTML escaping) is coalesced into a single output buffer at compile time — exactly what templ's compiler does — so a static-heavy template renders as a handful of OP_STATIC writes instead of a per-node tree walk. The render loop is a tight switch over a []uint64, maximising cache-line efficiency and branch predictability.
Dynamic points (components, slots) remain explicit instructions, so the bytecode supports the full composition layer (state, children, slots, memoization) on top of the same ComponentContext API.
func CompileBytecode ¶
func CompileBytecode(nodes NodeStream) *Bytecode
CompileBytecode compiles a NodeStream into a flat instruction stream. Static content is escaped and coalesced into a single output buffer; consecutive static runs emit one opStatic each. Components and slots remain dynamic instructions.
func (*Bytecode) Exec ¶
func (bc *Bytecode) Exec(w ByteWriter, rc *RenderContext, data any) error
Exec runs the bytecode, writing the rendered output to w.
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is a high-performance, lock-free, off-heap program cache.
The slot array (seq/hash/tagBits) is mmap'd via memory.MmapAnonymous so the Go GC never scans the hot synchronization state. Each slot is 24 bytes, so the read path touches at most one cache line per probe.
The *Program references live in the Go-heap `programs` array ([]atomic.Pointer[Program]) — the same split liteLRU uses: off-heap for the bitmask/seqlock sync state, Go-heap for the pointers the GC must see to keep objects alive. A raw uintptr in off-heap memory would let the GC collect the Program (use-after-free), and pinning it (runtime.Pinner) is not lock-free — so the GC-visible reference array is the lock-free-correct design. Get remains a lock-free atomic.Pointer.Load with zero allocations.
Concurrency model:
- Get: lock-free. Probes off-heap seq+hash; on a match, loads the program via the Go-heap atomic.Pointer.
- Put: CAS-loop. Increments seq (locks the slot), writes hash, stores the program pointer on the heap, increments seq (unlocks).
- Delete: same as Put with hash=0.
Cache keys are (engine, name) pairs. The 64-bit hash is computed inline; no string concat is performed on the hot path.
func NewCache ¶
NewCache returns a Cache with the given capacity. Capacity is rounded up to the next power of two.
func (*Cache) Close ¶
func (c *Cache) Close()
Close releases the off-heap memory. Programs remain referenced by the Go-heap `programs` array; the cache must not be used after Close.
func (*Cache) InvalidateTag ¶
InvalidateTag drops every cached entry whose tag bit matches.
type CacheStats ¶
CacheStats is the cache-level stats.
type Children ¶
type Children []string
Children is the pre-rendered HTML of a component's children. Each entry is a string chunk that the parent component can inject into its layout (React-style composition).
type Component ¶
type Component interface {
Render(w ByteWriter, rc *RenderContext, data any) error
}
Component is a renderable component. Components are registered by name and referenced from templates via `<NAVBAR data={...} />` (temmpl), `+navbar(data)` (jade), or `{{ template "navbar" . }}` (html/template).
The Component interface is engine-agnostic: each engine adapter translates its native component syntax into a Component reference in the NodeStream. The executor dispatches to the registered Component when the node has one set.
type ComponentContext ¶
type ComponentContext struct {
// Writer is the ByteWriter render writes to.
Writer ByteWriter
// Context is the per-request RenderContext.
Context *RenderContext
// Data is the user-supplied data passed to Render or RenderPage.
Data any
// Children is the pre-rendered HTML of the component's slot-less
// children (slot children live in Slots).
Children Children
// Slots maps slot name to the pre-rendered HTML of the slot's
// children.
Slots Slots
// State is the per-render state. Use for useState-style hooks.
State *State
// DataMap is the data map (may be nil). The component can
// read/modify it for child propagation.
DataMap map[string]any
// contains filtered or unexported fields
}
ComponentContext bundles all the render arguments into a single value. Components written with the fluent API receive a ComponentContext instead of the multi-arg ComponentFunc signature. The included State and Children/Slots are read from the render state set by the dispatcher.
func (*ComponentContext) ActionURL ¶
func (c *ComponentContext) ActionURL(action string) string
ActionURL builds the URL for a colocated server action on this component:
{prefix}/{COMPONENT}/{action}
e.g. with the default prefix:
c.ActionURL("toggle") // "/_nano/action/LIKE_BUTTON/toggle"
The prefix is configurable via WithActionPrefix; the URL is safe for hx-post / form action attributes. Returns "" for a nil receiver or empty action name.
func (*ComponentContext) AddHXTrigger ¶
func (c *ComponentContext) AddHXTrigger(name string)
AddHXTrigger records an HTMX client-side event to fire after this render completes. Convenience over RenderContext.AddHXTrigger for component authors. Safe to call with nil receiver.
func (*ComponentContext) AddHXTriggerAfterSettle ¶
func (c *ComponentContext) AddHXTriggerAfterSettle(name string)
AddHXTriggerAfterSettle records an HTMX event to fire after the settle step. Convenience over RenderContext.AddHXTriggerAfterSettle.
func (*ComponentContext) AddHXTriggerAfterSwap ¶
func (c *ComponentContext) AddHXTriggerAfterSwap(name string)
AddHXTriggerAfterSwap records an HTMX event to fire after the swap step. Convenience over RenderContext.AddHXTriggerAfterSwap.
func (*ComponentContext) AddHXTriggerWithDetail ¶
func (c *ComponentContext) AddHXTriggerWithDetail(name string, detail any)
AddHXTriggerWithDetail records an event with a JSON-encodable detail payload. Convenience over RenderContext.AddHXTriggerWithDetail.
func (*ComponentContext) AddMeta ¶
func (c *ComponentContext) AddMeta(name, content string)
AddMeta adds a <meta name="..." content="..."> tag for this request, emitted by the layout's <NANO_HEAD/> on full page loads. Last write wins per name.
func (*ComponentContext) GetFormError ¶
func (c *ComponentContext) GetFormError(key string) string
GetFormError returns the flash validation error for a form field, or "" if none was set for this request.
func (*ComponentContext) GetState ¶
func (c *ComponentContext) GetState(key string) (any, bool)
GetState reads a value from the per-render state.
func (*ComponentContext) ProvideContext ¶
func (c *ComponentContext) ProvideContext(key string, val any)
ProvideContext pushes (key, val) onto the cascading-context stack. The binding is visible to this component and every component it renders (children, sub-children, …) until the component's render scope exits — the dispatcher automatically pops the stack back to savedCtxPtr on return, so bindings don't leak into siblings or parent frames.
Shadowing: an inner ProvideContext("theme", "light") overrides an outer binding for the duration of the inner component's scope. Lookup is reverse-scan (newest first), so the nearest binding wins.
No hard cap: bindings beyond the inline fast-path spill to a heap slice so genuinely deep trees still work. There's no panic — if you need hundreds of nested ambient contexts you're probably doing it wrong, but the framework won't stop you.
Nil-safe: a nil receiver is a no-op.
func (*ComponentContext) Render ¶
func (c *ComponentContext) Render(name string, props any) error
Render dispatches a named, registered component inline, writing to this context's writer. The component receives a fresh ComponentContext (same state, its own data). This is the `{{ component "Name" . }}` superpower as a method — usable from any component on any engine.
func (*ComponentContext) RequiresCSS ¶
func (c *ComponentContext) RequiresCSS(href string)
RequiresCSS declares a CSS dependency for this component. The path is emitted once (deduplicated) by the layout's <NANO_ASSETS/> as a <link rel="stylesheet"> tag. Safe to call from components rendered in loops — the head gets one tag.
func (*ComponentContext) RequiresJS ¶
func (c *ComponentContext) RequiresJS(src string)
RequiresJS declares a JS dependency for this component. The path is emitted once (deduplicated) by the layout's <NANO_ASSETS/> as a <script defer> tag.
func (*ComponentContext) SetFormError ¶
func (c *ComponentContext) SetFormError(key, msg string)
SetFormError records a flash validation error for a form field. During a server-action re-render, the error is readable via GetFormError and rendered inline. Flash semantics: the error lives for this request only — cleared on pool reuse.
func (*ComponentContext) SetState ¶
func (c *ComponentContext) SetState(key string, v any)
SetState writes a value to the per-render state. When this context is bound to a component declared with WithOOB, SetState also marks the frame dirty so the render wrapper emits an HTMX OOB swap.
func (*ComponentContext) SetTitle ¶
func (c *ComponentContext) SetTitle(title string)
SetTitle sets the document <title> for this request. On full page loads the view renders before the layout, so the layout's <NANO_HEAD/> (or {{ nanoHead }}) emits it. On HTMX partial swaps, write `<title>` directly to the output instead — HTMX hoists it from the response body.
func (*ComponentContext) UseContext ¶
func (c *ComponentContext) UseContext(key string) any
UseContext returns the value bound to the nearest matching key in the cascading-context stack, or nil if no binding exists. The caller type-asserts the result:
theme := c.UseContext("theme").(string)
Nil-safe for the receiver.
func (*ComponentContext) UseId ¶
func (c *ComponentContext) UseId() string
UseId returns a unique per-request identifier ("nano-1", …) for pairing <label for> with <input id>. Zero-alloc for the first 256 ids.
func (*ComponentContext) UseState ¶
func (c *ComponentContext) UseState(key string, initial any) (any, func(any))
UseState is the React-style hook. It returns the current value for key (initial on first use) and a setter that writes back. The setter affects subsequent renders; the current render keeps its value.
For a typed variant use render.UseState[T](c.State, key, initial).
When this context is bound to a component declared with WithOOB, the setter also marks the frame dirty so the render wrapper emits an HTMX OOB swap.
func (*ComponentContext) Write ¶
func (c *ComponentContext) Write(p []byte) (int, error)
Write writes a byte slice to the output. Convenience over Writer.
func (*ComponentContext) WriteChildren ¶
func (c *ComponentContext) WriteChildren()
WriteChildren writes all slot-less children to the output. The component controls the placement; the framework just provides the pre-rendered HTML.
func (*ComponentContext) WriteHydrateProps ¶
func (c *ComponentContext) WriteHydrateProps(attr string, props any) error
WriteHydrateProps serializes props as JSON into a single-quoted HTML attribute — the hydration bridge for Alpine.js (x-data), HTMX extensions, and vanilla JS:
c.WriteHydrateProps("x-data", props)
// writes: x-data='{"min":0,"max":100}'
The JSON is written inside single quotes (JSON contains " but not '), and every byte is HTML-escaped via escapeBytes — the browser decodes entities when reading the attribute, so the client-side JS sees the exact JSON. Safe against quote/script injection in values.
Allocation note: encoding/json.Marshal allocates — this is a CORRECTNESS helper (proper escaping, no manual string concat), not a zero-alloc one. It runs once per component per render. The escape step streams the bytes directly (no string(b) conversion) so no extra allocation is added on our side.
Returns an error if props cannot be marshaled (e.g. channels, functions, cyclic values).
func (*ComponentContext) WriteSlot ¶
func (c *ComponentContext) WriteSlot(name string)
WriteSlot writes the named slot's content to the output. If the slot is not present, nothing is written.
func (*ComponentContext) WriteString ¶
func (c *ComponentContext) WriteString(s string) (int, error)
WriteString writes a string to the output.
func (*ComponentContext) Yield ¶
func (c *ComponentContext) Yield() error
Yield writes the pre-rendered view body (rc.ViewBytes) to the output — the layout composition hook. In the two-pass page pipeline, RenderPage renders the view first and stashes the bytes on the RenderContext; layouts call Yield where the view body should appear. Emitted by the gsx @yield directive.
Standalone renders (no RenderPage) have empty ViewBytes — Yield writes nothing.
type ComponentFunc ¶
type ComponentFunc func(w ByteWriter, rc *RenderContext, data any) error
ComponentFunc is a function adapter for Component.
func (ComponentFunc) Render ¶
func (f ComponentFunc) Render(w ByteWriter, rc *RenderContext, data any) error
Render implements Component.
func (ComponentFunc) WithFuncs ¶
func (f ComponentFunc) WithFuncs(funcs template.FuncMap) Component
WithFuncs returns a Component that wraps the ComponentFunc with the given FuncMap. Use it when registering a component:
cr.Register("NAVBAR", render.ComponentFunc(myFunc).WithFuncs(
template.FuncMap{
"currentUser": func() any { return loadUser() },
},
))
type ComponentMiddleware ¶
type ComponentMiddleware func(ComponentRenderFunc) ComponentRenderFunc
ComponentMiddleware is a Higher-Order Component wrapper in the React withAuth(Widget) style. It receives the next render function and returns a wrapped one:
func RequireAdmin(next render.ComponentRenderFunc) render.ComponentRenderFunc {
return func(c *render.ComponentContext) error {
if !isAdmin(c) {
return nil // abort — component renders nothing
}
return next(c)
}
}
cr.Define("ADMIN_BUTTON").Use(RequireAdmin).Render(...)
The chain is folded ONCE at Register time — the hot render path calls a single function pointer, zero iteration, zero allocation.
Middleware wraps the component DISPATCHER, not just the render function: it runs on the main thread BEFORE the async fork, so it has full access to cascading context (c.UseContext), and an abort (not calling next) prevents the async fallback from being written and the worker from being spawned. The async worker itself runs the raw render function — middleware runs exactly once, on the main thread.
Children are evaluated eagerly (pre-rendered before dispatch), so .Use is for gating leaf components and micro-interactions — not massive page layouts. Use HTTP router middleware for route-level protection.
type ComponentRegistry ¶
type ComponentRegistry struct {
// contains filtered or unexported fields
}
ComponentRegistry is the user-facing façade for components. Components are registered by name and looked up by name. The executor reaches into the registry at render time.
The registry also holds per-component memo caches (see Memoize): name → key → rendered HTML. Memoized components skip re-rendering when their key repeats, mirroring GO-Portfolio's per-part cache.
func NewComponentRegistry ¶
func NewComponentRegistry() *ComponentRegistry
NewComponentRegistry returns an empty ComponentRegistry.
func (*ComponentRegistry) Define ¶
func (r *ComponentRegistry) Define(name string) *Definition
Define returns a fluent Definition builder for a component. The component is registered with this ComponentRegistry when Register is called on the Definition.
Usage:
cr.Define("NAVBAR").
WithFuncs(template.FuncMap{"currentUser": ...}).
RenderChildren(func(c *render.ComponentContext) error {
c.WriteString("<nav>")
c.WriteChildren()
c.WriteString("</nav>")
return nil
}).
Register(cr)
func (*ComponentRegistry) Lookup ¶
func (r *ComponentRegistry) Lookup(name string) (Component, bool)
Lookup returns the component registered under the given name.
func (*ComponentRegistry) Memoize ¶
func (r *ComponentRegistry) Memoize(name string, keyer func(rc *RenderContext, data any) string)
Memoize wraps the component registered under name so its rendered HTML is cached by key. keyer maps (rc, data) to a cache key; when two renders produce the same key, the second writes the cached HTML without re-rendering the component. Return "" from keyer to disable caching for a particular render.
This is the GO-Portfolio per-part cache applied at the component level. Use it for components whose output is data-independent or keyable by a small digest (e.g. a heavily reused navbar or a component that renders an expensive query result keyed by an id).
cr.Memoize("Navbar", func(rc *render.RenderContext, data any) string {
return "static" // data-independent → render once, cache forever
})
cr.Memoize("UserCard", func(rc *render.RenderContext, data any) string {
return data.(UserCardProps).ID
})
func (*ComponentRegistry) Names ¶
func (r *ComponentRegistry) Names() []string
Names returns the registered component names.
func (*ComponentRegistry) Register ¶
func (r *ComponentRegistry) Register(name string, c Component)
Register adds a component under the given name.
func (*ComponentRegistry) Unregister ¶
func (r *ComponentRegistry) Unregister(name string)
Unregister removes a component from the registry. Used internally to clean up after RenderPage.
type ComponentRenderFunc ¶
type ComponentRenderFunc func(c *ComponentContext) error
ComponentRenderFunc is the function signature for the fluent component API. The receiver is a *ComponentContext that bundles the render arguments.
type ComponentWithChildren ¶
type ComponentWithChildren interface {
Component
RenderWithChildren(w ByteWriter, rc *RenderContext, data any, children Children) error
}
ComponentWithChildren is an optional interface a Component can implement to receive pre-rendered children. The children are computed by the dispatcher (the SoA executor walks the parent node's children; the html/template engine receives the rendered inner content). A component that implements this interface gets a direct call with the children list.
type ComponentWithFuncs ¶
ComponentWithFuncs is an optional interface a Component can implement to provide a component-specific FuncMap. The FuncMap is set on the RenderContext for the duration of this component's render. This is the per-component equivalent of render.SuperpowerFuncs (which is global).
Components that compose themselves via html/template (or templ) can use this to expose helpers scoped to the component.
type Composition ¶
Composition is a render request that composes a layout, view, and optional partials.
type CompressConfig ¶
CompressConfig controls automatic gzip compression of rendered output. Mirrors the original GO-Portfolio's hint at compression (`Transfer-Encoding: gzip, chunked`) but actually implements it.
type DataInjector ¶
type DataInjector func(rc *RenderContext, data map[string]any)
DataInjector is a function that mutates the render data map before the engine executes. The injector is the user's seam for whatever request-scoped values need to land in the template — there is no hardcoded list of keys. Anything the user wants to inject, they inject.
Mirrors the original GO-Portfolio pattern (object["current_user"] = a.User, object["view"] = view, object["flashes"] = a.Session.Flashes()) but generalises it: any key, any value, any number of injectors registered by the user.
type Definition ¶
type Definition struct {
// contains filtered or unexported fields
}
Definition is the fluent builder for a single component. It is returned by cr.Define(name) and chained with WithFuncs, Render, and RenderChildren. The definition is finalised by calling Register(cr) on the resulting ComponentRegistry.
Usage:
cr.Define("WRAP").
WithFuncs(template.FuncMap{"currentUser": ...}).
RenderChildren(func(c *render.ComponentContext) error {
c.WriteString("<div class='wrap'>")
c.WriteChildren()
c.WriteString("</div>")
return nil
}).
Register(cr)
func (*Definition) Action ¶
func (d *Definition) Action(name string, fn ActionFunc) *Definition
Action registers a colocated server action: mutation logic that lives next to the component. The action is dispatched by Registry.HandleAction when its URL is POSTed; after it runs, the component re-renders with the post-action state.
name is the action name — it becomes part of the URL:
{prefix}/{COMPONENT}/{name}. Use c.ActionURL(name) in the
component's Render to generate the URL.
fn receives the per-request RenderContext and the parsed
request body as props (JSON → typed map, forms → best-effort
conversion). It should mutate the user's own storage — the
framework does not persist state across requests.
The action's error contract: returning nil triggers the re-render; returning an error yields a 500 and no re-render.
func (*Definition) Async ¶
func (d *Definition) Async() *Definition
Async marks the component for Suspense-style asynchronous rendering. Requires Fallback(fn) to set the placeholder; calling Register without a fallback when Async() is set is a no-op.
When the executor encounters an Async component:
- It writes the Fallback output inline and flushes immediately so the browser paints the skeleton with TTFB ≈ 0ms.
- It spawns a worker goroutine that runs Render to a pooled buffer.
- When the worker finishes (or the request context cancels), the real output is emitted as a trailing OOB chunk via the suspense coordinator, wrapped in <div id="<component>" hx-swap-oob="true">...</div>.
Async turns Sum(component_times) into Max(component_times) for independent components. It is strictly opt-in and lazy: the coordinator allocates no channels and no goroutines unless an Async component is actually hit during the render walk.
func (*Definition) ErrorBoundary ¶
func (d *Definition) ErrorBoundary(fn ErrorBoundaryFunc) *Definition
ErrorBoundary installs a panic recovery callback for this component. When set, the component's Render runs inside a deferred recover; a panic invokes fn with the panic value, the failed render's buffer is discarded, and fn's output replaces the failed component in the page. The rest of the render tree continues normally.
Components without ErrorBoundary run on the existing fast path (no defer/recover overhead).
fn := func(c *render.ComponentContext, err any) error {
log.Printf("widget failed: %v", err) // safe to log
return c.WriteString(`<div class="error">Widget unavailable</div>`)
}
cr.Define("DASHBOARD_WIDGET").ErrorBoundary(fn).Render(...)
If fn itself panics or returns an error, a generic `<!-- error boundary failed -->` placeholder is emitted so the page never crashes. This is the silent-fallback contract documented in the design.
func (*Definition) Fallback ¶
func (d *Definition) Fallback(fn ComponentRenderFunc) *Definition
Fallback sets the synchronous placeholder rendered inline before the async component's real output is available. fn runs against the same ComponentContext as Render — it can read props, write placeholder markup, and emit any inline CSS/JS the skeleton needs.
Fallback is required when Async() is used; calling Fallback without Async() is harmless (no async dispatch happens) but the fallback function never runs.
func (*Definition) Metadata ¶
func (d *Definition) Metadata(fn func(rc *RenderContext, data any) error) *Definition
Metadata registers a head-metadata closure that runs BEFORE the page streams (see MetadataProvider). It receives the page data and sets the document title / meta tags via rc.SetTitle / rc.AddMeta; the layout's <NANO_HEAD/> (or {{ nanoHead }}) emits them.
For the common case this is redundant: components rendered in the view can call c.SetTitle / c.AddMeta during their render — the view renders before the layout in the two-pass page pipeline, and the head state is transferred to the layout's context. Metadata is for views that are NOT registered components, or metadata that should run even if the render walk is skipped.
fn := func(rc *render.RenderContext, data any) error {
user := data.(User)
rc.SetTitle(user.Name + " - Profile")
rc.AddMeta("description", "Profile page for " + user.Name)
return nil
}
cr.Define("USER_PROFILE").Metadata(fn).Render(...)
func (*Definition) Register ¶
func (d *Definition) Register(cr *ComponentRegistry)
Register finalises the definition and registers it with the ComponentRegistry.
func (*Definition) Render ¶
func (d *Definition) Render(fn ComponentRenderFunc) *Definition
Render sets the render function. The function receives a ComponentContext that bundles the render arguments. The component has no children (use RenderChildren for that).
func (*Definition) RenderChildren ¶
func (d *Definition) RenderChildren(fn ComponentRenderFunc) *Definition
RenderChildren sets the render function and indicates the component receives children. The component should call c.WriteChildren() to inject them at the desired location.
func (*Definition) Use ¶
func (d *Definition) Use(mw ...ComponentMiddleware) *Definition
Use registers component middleware (HOC wrappers). The chain is folded once at Register time around the component's dispatcher; the first middleware registered is the OUTERMOST wrapper (closest to the framework machinery), the last is the INNERMOST (closest to the render function) — standard middleware ordering.
cr.Define("ADMIN_BUTTON").
Use(RequireAdmin, LogRender).
Render(...)
Middleware runs on the main thread before any async fork, has full access to cascading context, and aborts by not calling next (nothing renders, no worker spawns). See ComponentMiddleware for the full contract.
func (*Definition) WithFuncs ¶
func (d *Definition) WithFuncs(funcs template.FuncMap) *Definition
WithFuncs declares the per-component FuncMap. The FuncMap is available to the component's Render via the render state (rc.ComponentFuncMap). It is also passed to the component as ComponentContext if the component is a ComponentWithFuncs.
func (*Definition) WithOOB ¶
func (d *Definition) WithOOB(targetID string) *Definition
WithOOB enables HTMX Out-of-Band swap emission for this component. targetID is the DOM `id` of the element the swap replaces — it must match an `id` attribute in the page. The id is required and is a hard contract: HTMX selectors are case-sensitive and a mismatch silently breaks the swap.
When the component mutates state via SetState / UseState during its render, its output is wrapped in
<div id="<targetID>" hx-swap-oob="true">...</div>
and written to rc.OOBSink() (which falls back to rc.Writer when no explicit sink is configured). When the component does NOT mutate state, its bytes are written to the original writer unchanged.
WithOOB is strictly opt-in — components without it render exactly as before, with no buffer copy or wrapper overhead.
type Dispatchable ¶
type Dispatchable interface {
Component
Dispatch(w ByteWriter, rc *RenderContext, data any, children Children) error
}
Dispatchable is an optional interface a Component implements to take full control of its dispatch: middleware wrapping, async forking, children handling. The executor and renderComponent call Dispatch INSTEAD of the async check + Render path. The fluent builder implements this via .Use().
type Engine ¶
type Engine interface {
Name() string
Compile(src []byte, name string) (*Program, error)
Execute(p *Program, w ByteWriter, rc *RenderContext, data any) error
}
Engine is the contract every template engine implements. Engines own three things: name, compile, execute.
type EngineName ¶
type EngineName string
EngineName is a compile-time-checked engine identifier. It is a string underneath — the same value used for cache keys and the byName lookup — but the distinct type rejects arbitrary strings at the call site. A typo'd `"jad"` is a compile error; `EngineJade` is not.
Prefer the constants for the built-in engines:
reg.Page(rc).
Engine(EngineJade).
Layout("layouts/app").
View("posts/show").
Render()
Custom engines use CustomEngine(name):
reg.Page(rc).
Engine(CustomEngine("my-engine")).
View("home").
Render()
const ( EngineJade EngineName = "jade" EngineHTML EngineName = "html" EngineHTMLTemplate EngineName = "html-template" EngineTempl EngineName = "templ" )
Built-in engine names.
func CustomEngine ¶
func CustomEngine(name string) EngineName
CustomEngine returns the EngineName for a user-defined engine. Because EngineName is a string type, custom engines may use any name they like — this is the escape hatch for the built-in set.
myEng := &MyEngine{}
reg.AddEngine(myEng)
reg.Page(rc).Engine(CustomEngine(myEng.Name())).View("home").Render()
func (EngineName) String ¶
func (n EngineName) String() string
String returns the raw engine name (cache key, byName lookup).
type ErrorBoundaryFunc ¶
type ErrorBoundaryFunc func(c *ComponentContext, err any) error
ErrorBoundaryFunc is the panic recovery callback for a component. It receives the ComponentContext (a fresh one pointing at the same underlying writer as the failed render) and the raw panic value (any — string, error, *runtime.Error, etc.). Returning nil swallows the panic and lets page rendering continue; returning a non-nil error propagates it.
The boundary's writes land in the live response, NOT in the discarded buffer the panicking render was using. This keeps the rest of the page intact — only the failed component's output is replaced.
Security note: the panic value is the raw value passed to panic(). Boundaries that print it to the response must take care not to leak internal state (DB errors with credentials, stack traces, etc.) to end users.
type Event ¶
type Event struct {
// Path is the file path that triggered the event, relative to
// the watched root.
Path string
// Op is the fsnotify op (Create, Write, Remove, Rename, Chmod).
Op fsnotify.Op
}
Event is a single invalidation signal sent to subscribers.
type FuncMapBuilder ¶
type FuncMapBuilder func(rc *RenderContext) template.FuncMap
FuncMapBuilder is a per-request FuncMap factory. The original GO-Portfolio built a fresh FuncMap on every render — a closure capturing the request handler's locals. We honour that exactly: the builder is called on every render and returns a FuncMap that closures can capture from rc.
type HTMXResponse ¶
type HTMXResponse struct {
// Retarget overrides hx-target. CSS selector string.
Retarget string
// Reswap overrides hx-swap. e.g. "outerHTML", "innerHTML", "none".
Reswap string
// PushURL controls HX-Push-Url. Empty = unset. "true" pushes the
// current URL; any other value is treated as a URL to push.
PushURL string
// Redirect triggers a client-side redirect to the given URL.
Redirect string
// Refresh triggers a full page refresh when "true".
Refresh string
// ReplaceURL replaces the current URL without a history entry.
// Empty = unset. "true" replaces with the current URL; any
// other value is treated as the replacement URL.
ReplaceURL string
// Location triggers client-side navigation without history.
// Full URL required.
Location string
// Reselect is a CSS selector for the part of the response body
// HTMX should swap in. Useful when the server returns a wrapper
// and only an inner region should land.
Reselect string
// Resettle controls the post-swap settle algorithm. "true"
// forces scroll reset to the top; "false" preserves scroll.
Resettle string
}
HTMXResponse bundles server-driven HTMX response decisions. The router calls one method on RenderContext (e.g. SetHXRetarget) per decision during the handler, then calls WriteHTMXHeaders once at the end to apply everything to the response. Each field's zero value means "leave the client-side default in place."
Field reference (HTMX 2.x):
Retarget — overrides hx-target on the triggered element Reswap — overrides hx-swap on the triggered element PushURL — pushes a new URL into history (true → use current, or a URL) Redirect — triggers a client-side redirect (full URL required) Refresh — triggers a full page refresh ReplaceURL — replaces current URL without history entry (true or URL) Location — triggers client-side navigation without history (full URL) Reselect — CSS selector picking which part of the response to swap Resettle — "true" forces scroll reset; "false" preserves scroll
type HXTriggers ¶
HXTriggers is a deduplicating set of event names to fire on the client via the HX-Trigger response header. Components accumulate triggers during a render via RenderContext.AddHXTrigger; the router joins them into the response header at the end of the handler.
Each entry maps an event name to an optional detail value. When all values are empty (the common case), Join() emits the plain comma-separated form HTMX uses for simple event firing:
HX-Trigger: card-updated,nav-refresh
When any entry carries detail, Join() emits JSON so the client can read the detail on the event object:
HX-Trigger: {"item-changed":{"id":42},"card-updated":null}
The router only needs to write the joined string into the header; nanite-render handles the format decision.
func (HXTriggers) Add ¶
func (t HXTriggers) Add(name string)
Add records an event name to fire on the client. Idempotent: adding the same name twice is a no-op (a later AddWithDetail for the same name upgrades the entry — first detail wins for now).
func (HXTriggers) AddWithDetail ¶
func (t HXTriggers) AddWithDetail(name string, detail any)
AddWithDetail records an event name with a detail payload that the client receives on the event object. The detail is anything JSON-marshalable (struct, map, slice, primitive). An empty detail is equivalent to Add. If the name was previously added without detail, the detail upgrades the entry.
func (HXTriggers) HasDetail ¶
func (t HXTriggers) HasDetail() bool
HasDetail reports whether any trigger in the set carries a JSON detail payload. Determines which Join format to use.
func (HXTriggers) Join ¶
func (t HXTriggers) Join() string
Join returns the trigger set in HTMX's wire format. Plain comma-separated names when no entry carries detail; JSON object otherwise. Returns "" for an empty set so the caller can skip setting the header.
func (HXTriggers) Names ¶
func (t HXTriggers) Names() []string
Names returns the trigger names as a sorted slice. Deterministic ordering makes tests reproducible and lets the router cache the joined header.
type MetadataProvider ¶
type MetadataProvider interface {
Component
Metadata() func(rc *RenderContext, data any) error
}
MetadataProvider is an optional interface a Component implements to contribute document head metadata BEFORE the page streams. The Metadata closure runs with the page data before the view renders; it sets rc.SetTitle / rc.AddMeta, which the layout's <NANO_HEAD/> injector emits.
For the common case this is redundant: any component rendered in the view can call c.SetTitle during its render (the view renders before the layout in the two-pass page pipeline, and the head state is transferred to the layout's context). Metadata exists for views that are NOT registered components, or metadata that should run even if the render walk is skipped.
type MinifyConfig ¶
type MinifyConfig struct {
KeepDefaultAttrVals bool
KeepWhitespace bool
KeepDocumentTags bool
KeepEndTags bool
KeepConditionalComments bool
}
MinifyConfig controls HTML minification at compile time. The original GO-Portfolio used tdewolff/minify per-render; we apply it once at compile time so the cache stores the minified output.
type NodeStream ¶
type NodeStream struct {
Tag []string
Flags []uint8
NS []uint16
ChildStart []uint32
ChildEnd []uint32
AttrStart []uint32
AttrEnd []uint32
TextOff []uint32
// Parent[i] is the parent node index of node i. -1 means root.
Parent []int32
// FirstChild[i] is the first direct child index of node i, or -1
// if i is a leaf. NextSibling[i] is the next sibling of i (same
// parent), or -1. These sibling links let the executor walk a tree
// in O(n) — direct children are NOT contiguous in the stream
// (grandchildren interleave), so a flat child range can't
// represent them. Built once at compile time by
// NodeStreamBuilder.Stream().
FirstChild []int32
NextSibling []int32
// ComponentName[i] is the name of a registered component when
// Flags[i]&FlagComponent != 0. The executor dispatches to
// ComponentRegistry.Lookup(ComponentName[i]).
ComponentName []string
// Shared attribute key/value tables. attrKeys/attrVals are indexed
// by the (AttrStart, AttrEnd) ranges above. Component props can be
// a *Program reference.
AttrKeys []string
AttrVals []any
// Count is the number of nodes currently in the stream. Equal to
// len(Tag) == len(Flags) == … when the stream is well-formed.
Count int
// contains filtered or unexported fields
}
NodeStream is the SoA representation of a parsed template. Each "node" is just an index into the parallel slices. Walking children is walking (start, end) ranges into the same stream, which keeps the working set inside L1 cache (target: cc < 10 on the hot loop).
Field shapes (bytes per node, approximate):
Tag 16 string header Flags 1 bitmap NS 2 namespace id ChildStart 4 first child index ChildEnd 4 one-past-last child index AttrStart 4 first attr pair index AttrEnd 4 one-past-last attr pair index TextOff 4 [start, end) into the shared textBuf ComponentName 16 name key for component dispatch (when flag set)
func (*NodeStream) AttrKey ¶
func (s *NodeStream) AttrKey(i, k int) string
AttrKey returns the attribute key for the k-th attribute of node i.
func (*NodeStream) AttrVal ¶
func (s *NodeStream) AttrVal(i, k int) any
AttrVal returns the attribute value for the k-th attribute of node i.
func (*NodeStream) ChildRange ¶
func (s *NodeStream) ChildRange(i int) (uint32, uint32)
ChildRange returns the [start, end) child index range for node i.
func (*NodeStream) FirstChildOf ¶
func (s *NodeStream) FirstChildOf(i int) int
FirstChildOf returns the first direct child index of node i, or -1.
func (*NodeStream) IsComponent ¶
func (s *NodeStream) IsComponent(i int) bool
IsComponent reports whether the node is a templ component.
func (*NodeStream) IsText ¶
func (s *NodeStream) IsText(i int) bool
IsText reports whether the node is a synthetic text node.
func (*NodeStream) IsVoid ¶
func (s *NodeStream) IsVoid(i int) bool
IsVoid reports whether the node is a known void element that has no closing tag.
func (*NodeStream) NextSiblingOf ¶
func (s *NodeStream) NextSiblingOf(i int) int
NextSiblingOf returns the next sibling index of node i, or -1.
func (*NodeStream) NumAttr ¶
func (s *NodeStream) NumAttr(i int) int
NumAttr returns the number of attribute pairs for node i.
func (*NodeStream) Reset ¶
func (s *NodeStream) Reset()
Reset clears the stream without releasing backing storage. After Reset the stream is reusable for a new program.
func (*NodeStream) Text ¶
func (s *NodeStream) Text(i int) []byte
Text returns the text slice for a text node. Returns nil if the node is not a text node or has no text.
type NodeStreamBuilder ¶
type NodeStreamBuilder struct {
// contains filtered or unexported fields
}
NodeStreamBuilder appends nodes to a NodeStream with growth on demand. The builder is stateful: call Reset to reuse. Slices grow geometrically to keep amortised allocation cost constant.
func ParseHTML ¶
func ParseHTML(src []byte, name string) (*NodeStreamBuilder, error)
ParseHTML is the exported HTML parser. Returns a NodeStreamBuilder populated from src.
Contract: the returned Program's string fields (Tag, AttrKeys, AttrVals, ComponentName) reference bytes inside src. The caller MUST keep src alive for as long as the Program is in use. The Go GC will keep src alive transitively (the string pointer fields are scanned), so this is safe under normal usage patterns — but if the caller releases src, the strings become dangling.
Engine adapters that wrap the source in a long-lived cache (the typical case) satisfy this contract automatically.
func (*NodeStreamBuilder) AppendComponent ¶
func (b *NodeStreamBuilder) AppendComponent(name string, attrs []string, vals []any) int
AppendComponent appends a component node that dispatches to the named component at render time. The component must be registered via Registry.AttachComponents before render.
func (*NodeStreamBuilder) AppendNode ¶
func (b *NodeStreamBuilder) AppendNode(tag string, flags uint8) int
AppendNode appends a new node with the given tag and flags. Returns the new node's index.
func (*NodeStreamBuilder) AppendText ¶
func (b *NodeStreamBuilder) AppendText(text []byte, raw bool) int
AppendText appends a synthetic text node. The text is copied into the shared textBuf. Returns the new node's index.
func (*NodeStreamBuilder) Reserve ¶
func (b *NodeStreamBuilder) Reserve(approxNodes int) *NodeStreamBuilder
Reserve pre-allocates the builder's internal slices with the given expected node count. Use this before parsing to avoid the geometric-growth reallocations as nodes are appended. Returns the builder for chaining.
func (*NodeStreamBuilder) ReserveText ¶
func (b *NodeStreamBuilder) ReserveText(srcSize int) *NodeStreamBuilder
ReserveText estimates the textBuf capacity based on the source size. The text content of an HTML document is typically 40-60% of the source bytes. Pre-allocating avoids the growth reallocations as text runs are appended.
func (*NodeStreamBuilder) Reset ¶
func (b *NodeStreamBuilder) Reset()
Reset re-initialises the builder for reuse.
func (*NodeStreamBuilder) SetAttrs ¶
func (b *NodeStreamBuilder) SetAttrs(i int, keys []string, vals []any)
SetAttrs attaches the attribute key/value pairs to node i. The slices passed to this call are appended once (no per-node copying).
func (*NodeStreamBuilder) SetAttrsEach ¶
func (b *NodeStreamBuilder) SetAttrsEach(i int, attrs []AttrKV)
SetAttrsEach appends (key, value) pairs to the builder's attr tables and records the [start, end) range on node i. It avoids the two make() calls that SetAttrs requires; the parser uses this with a pooled working slice.
func (*NodeStreamBuilder) SetChildren ¶
func (b *NodeStreamBuilder) SetChildren(i int, start, end uint32)
SetChildren sets the child range for node i.
func (*NodeStreamBuilder) SetParent ¶
func (b *NodeStreamBuilder) SetParent(child, parent int)
SetParent sets the parent node index for the given node.
func (*NodeStreamBuilder) Stream ¶
func (b *NodeStreamBuilder) Stream() NodeStream
Stream returns the underlying stream, finalising the sibling links. The builder is consumed after Stream; further appends are invalid.
type OOBOptioner ¶
OOBOptioner is an optional interface a Component implements to participate in HTMX-style Out-of-Band swaps. Components that opt in via OOBID() have their rendered output wrapped in
<div id="<oob-id>" hx-swap-oob="true">...</div>
when their render mutates state via SetState / UseState. The id is a hard DOM contract — it must match the `id` attribute of the element the swap replaces.
Engines that pre-compile components (templ, html/template) can implement OOBOptioner directly without using the fluent builder. The fluent builder exposes this via Definition.WithOOB(id).
type Option ¶
type Option func(*Registry)
Option configures a Registry. Apply via New(WithEngine(...), WithCompression(...), etc.) or via the fluent builder (reg.WithEngine(...).WithCompression(...)).
func WithActionPrefix ¶
WithActionPrefix changes the URL prefix for colocated server actions (default "/_nano/action/"). The prefix must end with "/" — ActionURL appends {COMPONENT}/{action} directly.
func WithCacheSize ¶
WithCacheSize sets the default cache capacity.
func WithCompression ¶
WithCompression enables gzip compression.
func WithDefaultLoader ¶
func WithDefaultLoader(loader SourceFunc) Option
WithDefaultLoader sets the registry-level loader. Used when a RenderContext has no per-request loader. This is the typical boot configuration — one loader for the whole app.
func WithEngines ¶
WithEngines registers multiple engines.
func WithFuncMap ¶
func WithFuncMap(fn FuncMapBuilder) Option
WithFuncMap registers a FuncMapBuilder.
func WithInjector ¶
func WithInjector(fn DataInjector) Option
WithInjector registers a DataInjector.
func WithVariant ¶
WithVariant declares a variant dimension with allowed values.
type PageComposer ¶
type PageComposer interface {
Engine
RenderPage(rc *RenderContext, w ByteWriter, layout, view string, data any) error
}
PageComposer is an optional interface an Engine implements to provide NATIVE layout composition. Engines that compose by nesting (templ's Layout(view) pattern) implement this; Registry.RenderPageWith routes to it instead of the string-injection (yield) path.
type ParseError ¶
ParseError is a structured error with a name and offset. Used by the parser and engine adapters to report failures with enough context to diagnose the offending template. ComposeError is a ParseError and is matched by errors.As.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
Error implements the error interface.
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
Unwrap supports errors.Is/As to introspect wrapped errors.
type Pipeline ¶
type Pipeline struct {
// BeforeCompile runs before Engine.Compile. Useful for source
// pre-processing (e.g. stripping comments, normalising whitespace).
BeforeCompile func(name string, src []byte) ([]byte, error)
// AfterCompile runs after Engine.Compile. Useful for additional
// optimisation or instrumentation on the resulting *Program.
AfterCompile func(p *Program) error
// BeforeExecute runs before Engine.Execute. Useful for setting
// up per-request FunMaps, layout decisions, or response headers.
BeforeExecute func(p *Program, rc *RenderContext, data any) error
// AfterExecute runs after Engine.Execute. Useful for measuring
// render time or post-processing the writer's bytes.
AfterExecute func(p *Program, rc *RenderContext, data any) error
}
Pipeline is a thin hook layer for custom preprocessing. Each engine compiles its own source — nanite-render does not parse templates. The Pipeline is for users who want to inject preprocessing (e.g. sanitisation, i18n key expansion, custom FuncMap wrapping) around the engine's compile step.
The pipeline is *not* templating-engine-specific. The hook points are: before-compile, after-compile, before-execute, after-execute.
Most users do not need a Pipeline. The default flow is:
Registry.Part(name) → Engine.Compile → cache Registry.Render(name) → Engine.Execute → ByteWriter
The Pipeline is the seam for users who want to layer in their own behaviour at any of those points.
func NewPipeline ¶
func NewPipeline() *Pipeline
NewPipeline returns an empty Pipeline. Hooks are populated by the caller; missing hooks are skipped.
func (*Pipeline) Debug ¶
Debug returns a Pipeline that prints per-stage timing to w. The pipeline populates rc.Times so callers can read the map back programmatically. Mirrors GO-Portfolio's `times["jade"]`, `times["render-page"]`.
Usage:
p := render.NewPipeline().Debug(os.Stderr) reg.SetPipeline(p)
type PreloadHint ¶
PreloadHint describes a resource the browser should fetch ahead of time. The hint is emitted as a <link rel="preload"> tag when the <PRELOADS/> component is rendered in the layout's <head>.
Empty fields are omitted from the emitted tag. Common values:
href: "/static/app.css" the resource URL
as: "style" the content type the browser expects
(style, script, image, font, fetch)
type: "text/css" MIME type — used by the browser to
validate support before fetching
type PreloadHints ¶
type PreloadHints struct {
// contains filtered or unexported fields
}
PreloadHints returns the active set of preload hints registered against the registry. Returned for read-only inspection; mutations must go through Registry.Preload / Unpreload.
type Program ¶
type Program struct {
Engine string
Name string
Nodes NodeStream
Layout *Program
FuncMap map[string]any
ETag string
Tags []string
// EngineData is the engine's compiled output. nil if the engine
// uses the SoA executor. Engines that defer to html/template or
// templ store their compiled *template.Template or templ.Component
// here. The render path calls Engine.Execute which dispatches on
// the type.
EngineData any
// Bytecode is the flat instruction stream compiled from Nodes
// (see CompileBytecode). When present, Execute runs it instead of
// the recursive tree walk — static HTML renders as coalesced
// writes, matching templ's compile-to-output speed.
Bytecode *Bytecode
// contains filtered or unexported fields
}
Program is a compiled, cacheable template. Programs are immutable after Compile returns. Render never mutates a *Program; it only reads from the SoA node stream and writes to a ByteWriter.
A Program may reference another Program through its Layout field. This mirrors GO-Portfolio's two-pass render where the layout and the inner view are cached separately and composed at render time.
EngineData is an opaque blob for engines that need their own compiled representation (e.g. *template.Template for jade and the html/template engine). Plain engines (the default HTML adapter) leave this nil and use the Nodes stream via the SoA executor.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the user-facing façade. It holds the set of registered engines, the shared program cache, and the per-request features (injectors, FuncMap, variants, compression, conditional).
All hot-path fields are accessed via atomic.Pointer for lock-free reads. Writes are CAS-loop copy-on-write, which is also lock-free. No RWMutex in the hot path.
func NewRegistry ¶
NewRegistry returns a Registry with the given engines. The first engine is the default for unqualified Render calls.
The PRELOADS component is registered automatically. It emits the registry-level preload hints as <link rel="preload"> tags when referenced in a layout (typically inside <head>).
func (*Registry) ActionPrefix ¶
ActionPrefix returns the URL prefix for action endpoints. Defaults to DefaultActionPrefix.
func (*Registry) AttachComponents ¶
func (r *Registry) AttachComponents(c *ComponentRegistry)
Attach binds a ComponentRegistry to a Registry. Components are reachable via rc.Components() in the executor.
func (*Registry) ComponentLookup ¶
ComponentLookup is a convenience used by the executor and engines to find a component by name.
func (*Registry) Components ¶
func (r *Registry) Components() *ComponentRegistry
Components returns the attached ComponentRegistry, or nil.
func (*Registry) Compress ¶
func (r *Registry) Compress(cfg CompressConfig) *Registry
Compress enables gzip with the given config.
func (*Registry) CompressWriter ¶
func (r *Registry) CompressWriter(rc *RenderContext, w ByteWriter) (ByteWriter, func() error)
CompressWriter wraps w in a gzip writer when compression is enabled and the request accepts it.
func (*Registry) Conditional ¶
Conditional enables ETag/304.
func (*Registry) DefaultLoader ¶
func (r *Registry) DefaultLoader() SourceFunc
DefaultLoader returns the registry-level loader, or nil if none is set. Callers (notably the nano adapter) use this to populate the per-request RenderContext.Loader. Lock-free atomic load.
func (*Registry) Define ¶
func (r *Registry) Define(name string) *Definition
Define returns a fluent builder for a component. The component is registered with the ComponentRegistry when Register is called.
Usage:
cr.Define("NAVBAR").
WithFuncs(template.FuncMap{"currentUser": ...}).
RenderChildren(func(c *render.ComponentContext) error {
c.WriteString("<nav>")
c.WriteChildren()
c.WriteString("</nav>")
return nil
}).
Register(cr)
func (*Registry) Engines ¶
Engines returns the live slice of registered engines. The slice is a copy; callers may mutate it.
func (*Registry) FuncMap ¶
func (r *Registry) FuncMap(b FuncMapBuilder)
FuncMap registers a per-request FuncMap factory.
func (*Registry) HandleAction ¶
func (r *Registry) HandleAction(w http.ResponseWriter, req *http.Request)
HandleAction is the universal colocated-action HTTP handler. Mount it on any router:
// chi
r.Post("/_nano/action/*", reg.HandleAction)
// stdlib
mux.HandleFunc("/_nano/action/", reg.HandleAction)
Flow:
- Method must be POST (405 otherwise).
- HX-Request header must be "true" (403 otherwise).
- Origin or Referer host must match the request Host (403 otherwise). This is the CSRF baseline — HTTPS sites behind proxies may need to relax it by wrapping the handler.
- The path is parsed as {prefix}/{COMPONENT}/{action}. Unknown component or action → 404.
- The body is parsed (JSON → typed map, form → best-effort conversion) and passed as props.
- The action closure runs with a fresh RenderContext. Any HX-Trigger events it records are emitted on the response.
- The component re-renders with the post-action state. If the component declared WithOOB(id), the output is wrapped in an HTMX OOB chunk; otherwise raw HTML is returned so the client's hx-target (or triggering element) swaps it inline.
Action closures are slow-path by definition (they mutate a database); the 0 B/op guarantee is unaffected — it applies to the render hot path, not to mutation endpoints.
func (*Registry) Inject ¶
func (r *Registry) Inject(in DataInjector)
Inject registers a DataInjector. Copy-on-write CAS.
func (*Registry) InjectFuncMap ¶
func (r *Registry) InjectFuncMap(rc *RenderContext, data any)
InjectFuncMap runs all registered injectors and the FuncMap factory. Lock-free reads via atomic.Pointer.
func (*Registry) InvalidateTag ¶
InvalidateTag drops every cached (engine, name) pair associated with the given tag. This is a cold path (boot, hot-reload, ops tool calls) — the loop body is O(n) over the tagged set.
func (*Registry) Minify ¶
func (r *Registry) Minify() *MinifyConfig
Minify reports the active MinifyConfig, or nil. Lock-free.
func (*Registry) MustPreloadParts ¶
func (r *Registry) MustPreloadParts(rc *RenderContext, engine string, parts ...string)
MustPreloadParts is the boot-time version of PreloadParts. It panics with the underlying error if any part fails to load. Use this in main() to fail loudly at startup when a template is missing or misnamed.
The panic value is the wrapped error, so recover sites can introspect via errors.Is / errors.As:
defer func() {
if r := recover(); r != nil {
if err, ok := r.(error); ok {
switch {
case errors.Is(err, render.ErrEngineNotFound):
// missing engine
case errors.Is(err, render.ErrTemplateNotFound):
// missing template file
}
}
}
}()
reg.MustPreloadParts(rc, "jade", "layouts/app", "posts/show")
func (*Registry) Page ¶
func (r *Registry) Page(rc *RenderContext) *RenderBuilder
Page returns a fresh RenderBuilder bound to the given RenderContext and the Registry. The RenderContext is typically pooled (see AcquireContext); the caller owns its release unless Render is called via a nano adapter that manages the lifecycle.
func (*Registry) Part ¶
func (r *Registry) Part(rc *RenderContext, engine, name string) (*Program, error)
Part returns the compiled program for the given template, fetching it from the cache (or compiling on miss).
func (*Registry) Preload ¶
func (r *Registry) Preload(h PreloadHint)
Preload registers a preload hint at the registry level. The hint is emitted (in registration order) wherever a <PRELOADS/> component tag appears in a layout — typically inside <head>. Hints are deduplicated by (Href, As, Type); registering the same triple twice is a no-op.
// Boot time:
reg := render.New(render.WithEngines(engine.NewHTML()))
reg.Preload(render.PreloadHint{Href: "/static/app.css", As: "style", Type: "text/css"})
reg.Preload(render.PreloadHint{Href: "/static/app.js", As: "script"})
// In the layout:
// <html><head><PRELOADS/></head><body>...</body></html>
func (*Registry) PreloadParts ¶
func (r *Registry) PreloadParts(rc *RenderContext, engine string, parts ...string) error
PreloadParts ensures each named part is in the cache.
func (*Registry) Preloads ¶
func (r *Registry) Preloads() []PreloadHint
Preloads returns a snapshot of the registered hints. The returned slice is a copy; callers may mutate it freely.
func (*Registry) Render ¶
func (r *Registry) Render(rc *RenderContext, engine, name string, data any) error
Render fetches the named program from the cache (compiling on miss) and executes it against the given data.
func (*Registry) RenderComponent ¶
func (r *Registry) RenderComponent(w ByteWriter, rc *RenderContext, name string, props any) error
RenderComponent dispatches a named, registered component directly to w, bypassing any parent template or layout. This is the entry point for HTMX-style targeted swaps: a handler receives an `hx-get="/posts/123/card"` request and renders JUST the CARD component without re-rendering the surrounding page.
The component is resolved via the registry's ComponentRegistry (the same path the html/template `{{ component "Name" . }}` superpower uses). The lookup follows rc.ComponentRegistry() first (so per-request attachment via SetComponentRegistry wins), then the registry's built-in components.
Returns nil (no error) when the name is not registered — the same semantics as the html/template superpower. Missing components are not a render error; they are a configuration gap the caller may or may not care about.
func (*Registry) RenderComposition ¶
func (r *Registry) RenderComposition(rc *RenderContext, c Composition, data any) error
RenderComposition renders a layout + view + partials composition.
func (*Registry) RenderNamed ¶
func (r *Registry) RenderNamed(rc *RenderContext, engine EngineName, name string, data any) error
RenderNamed is the typed variant of Render. It takes an EngineName instead of a raw string, so callers use the constants or CustomEngine(name) — a typo'd name is a compile error.
func (*Registry) RenderPage ¶
func (r *Registry) RenderPage(rc *RenderContext, layout, view string, data any) error
RenderPage is the high-level "render a page" helper. It composes a layout + view + components in three steps:
- Render the view to a buffer.
- Stash the view bytes on the RenderContext (rc.ViewBytes) for the yield() superpower.
- Render the layout. Components (e.g. <NAVBAR/>) are dispatched via the SoA executor; the layout's {{ yield }} reads rc.ViewBytes via the FuncMap superpower.
Layout authors use `{{ yield }}` (FuncMap) or <YIELD/> (SoA component) where the body should go:
<html><body><NAVBAR/>
{{ yield }}
<FOOTER/>
</body></html>
View authors write the inner content as a flat HTML template:
<h1>{{.Title}}</h1>
<p>{{.Body}}</p>
RenderPage is the only entry point users typically need.
Errors returned by RenderPage are wrapped sentinels:
- ErrRenderPageInvalid for empty layout or view
- ErrLayoutMissing if the layout lookup fails
- ErrTemplateNotFound for missing view
- ErrLoaderMissing if the RenderContext has no loader
Routers can use errors.Is to translate these to HTTP responses.
func (*Registry) RenderPageEngines ¶
func (r *Registry) RenderPageEngines(rc *RenderContext, layoutEngine EngineName, layout string, viewEngine EngineName, view string, data any) error
RenderPageEngines composes a layout and view rendered by DIFFERENT engines — the GO-Portfolio dynamic-layout pattern. The handler names the view; the layout wraps it; partials are components.
Typical split: the layout is plain HTML (compiled to bytecode, ~85 ns) with <YIELD/> where the view goes, and the view is templ (or jade, or html-template):
reg.RenderPageEngines(rc,
render.EngineHTML, "layouts/app", // bytecode layout
render.EngineTempl, "posts/show", // templ view
data)
The view is rendered to a buffer, then the layout is rendered with a YIELD component injecting it. Components inside the layout (partials like <NAVBAR/>) dispatch through the ComponentRegistry.
func (*Registry) RenderPageWith ¶
func (r *Registry) RenderPageWith(rc *RenderContext, engine, layout, view string, data any) error
RenderPageWith is RenderPage with an explicit engine name. Use this to compose a layout + view with an engine other than the default plain HTML engine (e.g. "html-template" or "jade").
func (*Registry) RenderStream ¶
func (r *Registry) RenderStream(rc *RenderContext, engine EngineName, layout, view string, data any, chunkSize int) error
RenderStream renders a layout + view composition with progressive HTTP streaming. The layout's bytes are emitted in chunks of approximately chunkSize bytes; each chunk is pushed to the underlying response writer and signaled via http.Flusher so it hits the wire immediately. The browser starts parsing markup before the full template has been rendered — TTFB drops to the time the first chunk accumulates.
chunkSize of 0 disables chunking (writes pass through to the parent writer unchanged). Negative values are treated as 0.
When the layout engine is a PageComposer (e.g. templ), the layout's RenderPage handles streaming directly. For the engine-agnostic buffer-injection path, the per-flush writer wraps the response writer so the layout's bytecode writes stream incrementally.
RenderStream is the streaming primitive for HTTP/1.1 chunked encoding and HTTP/2 frame emission. Use it for slow-to-render pages with substantial static content.
func (*Registry) SetActionPrefix ¶
SetActionPrefix changes the URL prefix for action endpoints. The prefix must end with "/" — ActionURL appends {COMPONENT}/{action} directly.
func (*Registry) SetDefaultLoader ¶
func (r *Registry) SetDefaultLoader(loader SourceFunc)
SetDefaultLoader sets the registry-level loader. Lock-free; concurrent readers see the previous value until the next store.
func (*Registry) SetPipeline ¶
SetPipeline installs a custom Pipeline.
func (*Registry) SetTag ¶
SetTag associates a (engine, name) pair with one or more tags. Tags are used by InvalidateTag for category-based eviction.
func (*Registry) SetVariant ¶
SetVariant records a variant for a key in the registry tag table.
func (*Registry) Unpreload ¶
Unpreload removes every hint matching the given (As, Type) pair. Use this when a resource becomes unreachable (e.g. CDN purge). Matching is by As+Type only — Href is ignored because a single resource type often spans many URLs.
func (*Registry) WithMinify ¶
func (r *Registry) WithMinify(cfg MinifyConfig) *Registry
WithMinify attaches a MinifyConfig to the Registry. Engines that support minification (jade, html/template) check this and apply it in their Compile step. Lock-free.
type RenderBuilder ¶
type RenderBuilder struct {
// contains filtered or unexported fields
}
RenderBuilder is a fluent builder for a render call. It hides the Composition struct and the RenderContext lifecycle behind a chainable API:
reg.Page(rc).
Engine("jade").
Layout("layouts/app").
View("posts/show").
Parts("partials/nav", "partials/footer").
With(post).
Render()
The builder accumulates fields in a single struct; the chain methods mutate the receiver and return it, so no per-call allocations occur.
func (*RenderBuilder) Engine ¶
func (rb *RenderBuilder) Engine(name EngineName) *RenderBuilder
Engine sets the engine by name. Chainable. Use the typed constants (EngineJade, EngineHTML, ...) or CustomEngine(name) for a user-defined engine.
reg.Page(rc).Engine(EngineJade).View("home").Render()
func (*RenderBuilder) EngineInstance ¶
func (rb *RenderBuilder) EngineInstance(e Engine) *RenderBuilder
EngineInstance sets the engine by value. The engine's Name() is derived automatically, so custom engines need no name cast. Chainable.
reg.Page(rc).EngineInstance(engine.NewJade()).View("home").Render()
func (*RenderBuilder) Layout ¶
func (rb *RenderBuilder) Layout(name string) *RenderBuilder
Layout sets the layout template name. Chainable.
func (*RenderBuilder) Parts ¶
func (rb *RenderBuilder) Parts(parts ...string) *RenderBuilder
Parts adds partial template names to the composition. Multiple calls append. Chainable.
func (*RenderBuilder) Render ¶
func (rb *RenderBuilder) Render() error
Render executes the composition.
- If Layout is set, it renders via RenderPageWith (layout wraps view) using the configured engine.
- Otherwise it renders the view directly via Render.
Returns the underlying Registry error if any. If a cleanup hook was installed (via WithDone), it runs after the render completes.
func (*RenderBuilder) RenderComposition ¶
func (rb *RenderBuilder) RenderComposition() error
RenderComposition executes the full Composition (layout + view + parts). Use this when parts need to be rendered alongside the view.
func (*RenderBuilder) View ¶
func (rb *RenderBuilder) View(name string) *RenderBuilder
View sets the view template name. Chainable.
func (*RenderBuilder) With ¶
func (rb *RenderBuilder) With(data any) *RenderBuilder
With sets the render data. Chainable.
func (*RenderBuilder) WithDone ¶
func (rb *RenderBuilder) WithDone(fn func()) *RenderBuilder
WithDone installs a cleanup hook invoked after Render or RenderComposition returns. Returns the builder for chaining. Intended for adapters that manage pooled resources.
type RenderContext ¶
type RenderContext struct {
// Writer is the ByteWriter render writes into. The underlying
// io.Writer is typically the response writer wrapped by a router.
Writer ByteWriter
// Request is the live HTTP request. Headers are written by the
// caller before any render invocation so the buffer never holds
// head-of-line bytes.
Request *http.Request
// Loader resolves a template name to bytes. Typically set by the
// router middleware when the request starts.
Loader SourceFunc
// FuncMap is the per-request template function map. Engines
// apply it on top of (or instead of) the Program's default
// FuncMap. Frozen on the Program for compile-time defaults,
// overridden by the request at render time.
//
// This mirrors GO-Portfolio's per-render funcMap (render.go:83-106),
// which lets each handler inject closure-bound helpers.
FuncMap template.FuncMap
// Times is populated when the Debug stage is enabled. The map
// is keyed by stage name and stores the wall-clock duration of
// each stage. nil when Debug is off — opt-in only.
Times map[string]time.Duration
// Headers is the set of response headers to write before the body.
// Pre-set by the middleware (Content-Type, Cache-Control, etc.).
Headers http.Header
// Layout is the optional layout template name. When set, the
// Renderer renders the inner view then wraps it in the layout.
// This mirrors GO-Portfolio's two-pass layout pattern.
Layout string
// Variants are the per-request variant dimensions, e.g.
// {"locale": "fr", "theme": "dark"}. Resolved at Part() lookup
// to compose the cache key. Composed Layout = engine/name@locale.fr@theme.dark.
Variants map[string]string
// ViewBytes is the pre-rendered view HTML used by the yield()
// superpower. Set by RenderPage before the layout is executed.
// Engines that defer to html/template read this via the FuncMap.
ViewBytes []byte
// ComponentFuncMap is the per-component FuncMap set by the
// component dispatcher when rendering a component that
// implements ComponentWithFuncs. Components that compose via
// html/template can read this to expose component-specific
// helpers during their render.
ComponentFuncMap template.FuncMap
// contains filtered or unexported fields
}
RenderContext is the per-request context the Registry writes through. It owns the ByteWriter, the SourceFunc, the per-request FuncMap, and the optional Times map. Instances are pooled — use AcquireContext and ReleaseContext.
RenderContext is router-agnostic. The core render package has no dependency on any specific router (nanite, chi, gin, stdlib http). Adapters live in sub-packages (e.g. nano/, chi/, gin/).
func AcquireContext ¶
func AcquireContext(w ByteWriter, req *http.Request) *RenderContext
AcquireContext returns a fresh RenderContext bound to w and req. The caller must ReleaseContext when done.
func FromContext ¶
func FromContext(ctx context.Context) *RenderContext
FromContext returns the *RenderContext carried by ctx, or nil if none is present.
func (*RenderContext) AddHXTrigger ¶
func (rc *RenderContext) AddHXTrigger(name string)
AddHXTrigger records an HTMX client-side event to fire after this render completes. Components call this from their render function; the router reads the joined list via HXTriggers() at the end of the handler and writes it into the HX-Trigger response header.
Multiple events are deduplicated; AddHXTrigger is safe to call repeatedly with the same name. An empty name is ignored.
Example:
cr.Define("CARD").
WithOOB("card-slot").
Render(func(c *render.ComponentContext) error {
c.AddHXTrigger("card-updated")
c.SetState("count", c.GetState("count").(int)+1)
return c.WriteString("<div>...</div>")
}).
Register(cr)
In the handler:
if isHTMX := render.IsHTMXRequest(r); isHTMX {
reg.RenderComponent(w, rc, "CARD", nil)
rc.WriteHTMXHeaders(w)
}
func (*RenderContext) AddHXTriggerAfterSettle ¶
func (rc *RenderContext) AddHXTriggerAfterSettle(name string)
AddHXTriggerAfterSettle records an HTMX event to fire after the settle step. Populates the HX-Trigger-After-Settle response header.
func (*RenderContext) AddHXTriggerAfterSwap ¶
func (rc *RenderContext) AddHXTriggerAfterSwap(name string)
AddHXTriggerAfterSwap records an HTMX event to fire after the swap step. Populates the HX-Trigger-After-Swap response header. See AddHXTrigger for naming and dedup semantics.
func (*RenderContext) AddHXTriggerWithDetail ¶
func (rc *RenderContext) AddHXTriggerWithDetail(name string, detail any)
AddHXTriggerWithDetail records an event with a JSON-encodable detail payload. The client receives the detail on the event object via htmx:event-name.detail. See AddHXTrigger for dedup and lifecycle semantics.
func (*RenderContext) AddMeta ¶
func (rc *RenderContext) AddMeta(name, content string)
AddMeta adds a <meta name="..." content="..."> tag for this request. Last write wins per name (no duplicate emission). Spills to a heap slice past metaInlineCap.
func (*RenderContext) ClearAssets ¶
func (rc *RenderContext) ClearAssets()
ClearAssets resets the collected CSS/JS dependencies. Called on pool reuse; callers generally don't need it.
func (*RenderContext) ClearFormErrors ¶
func (rc *RenderContext) ClearFormErrors()
ClearFormErrors removes all flash validation errors. Called on pool reuse; callers generally don't need to invoke it directly.
func (*RenderContext) ClearHeadState ¶
func (rc *RenderContext) ClearHeadState()
ClearHeadState resets the title, meta tags, and id sequence. Called on pool reuse; callers generally don't need it.
func (*RenderContext) CloseSuspense ¶
func (rc *RenderContext) CloseSuspense()
CloseSuspense closes the coordinator: the writer drains pending payloads and exits. Workers already running continue; workers that haven't started are skipped because the channel will receive no more readers. Call from defer in the handler that opened the coordinator, after the render walk completes.
Note: CloseSuspense does NOT cancel the coordinator's context — that cancellation is owned by rc.Request.Context() and fires automatically on client disconnect. Cancelling here would kill in-flight workers before they deliver, defeating the point of suspense.
func (*RenderContext) ComponentRegistry ¶
func (rc *RenderContext) ComponentRegistry() *ComponentRegistry
ComponentRegistry returns the active ComponentRegistry, if any.
func (*RenderContext) ComponentState ¶
func (rc *RenderContext) ComponentState() *State
ComponentState returns the per-render state. The state is reset on each new render (via SetRenderState).
func (*RenderContext) ContextDepth ¶
func (rc *RenderContext) ContextDepth() int
ContextDepth returns the current stack depth. Useful for instrumentation / debugging; not used in the hot path.
func (*RenderContext) EffectiveFuncMap ¶
func (rc *RenderContext) EffectiveFuncMap(programDefaults template.FuncMap) template.FuncMap
EffectiveFuncMap returns the FuncMap that should be applied to the Program. Per-request FuncMap (when set) overrides the Program's default. If neither is set, returns nil.
func (*RenderContext) EnsureSuspense ¶
func (rc *RenderContext) EnsureSuspense(w ByteWriter) *suspenseCoordinator
EnsureSuspense lazily starts a suspense coordinator for this RenderContext, bound to w. Returns the coordinator (or the existing one if already started). Nil-safe: a nil receiver or nil writer means suspense is unavailable; the caller should fall back to synchronous rendering.
The coordinator inherits cancellation from rc.Request.Context() when rc.Request is non-nil; otherwise it uses context.Background().
func (*RenderContext) FormErrors ¶
func (rc *RenderContext) FormErrors() map[string]string
FormErrors returns all flash validation errors as a map[string]string (field → message). Returns nil when there are none. Allocates — for display/instrumentation, not the hot path.
func (*RenderContext) GetContext ¶
func (rc *RenderContext) GetContext(key string) any
GetContext scans the cascading-context stack backwards (newest first) and returns the value bound to the nearest matching key, or nil if no binding exists. Returns nil for a nil receiver.
O(n) in the stack depth — at inlineCtxCap the loop is fully cache-resident. Deeper trees still hit cache but pay an extra indirect on the spillover.
func (*RenderContext) GetFormError ¶
func (rc *RenderContext) GetFormError(key string) string
GetFormError returns the flash validation error for a form field, or "" if none was set for this request.
func (*RenderContext) HTMXResponse ¶
func (rc *RenderContext) HTMXResponse() HTMXResponse
HTMXResponse returns the active response-decision struct. Callers may read it for inspection; modifications should go through the Set* helpers above.
func (*RenderContext) HXTriggers ¶
func (rc *RenderContext) HXTriggers() HXTriggers
HXTriggers returns the accumulated trigger set. The router reads this after the render completes to populate response headers. The returned set is owned by the RenderContext; callers must not mutate it after the handler returns.
func (*RenderContext) HXTriggersAfterSettle ¶
func (rc *RenderContext) HXTriggersAfterSettle() HXTriggers
HXTriggersAfterSettle returns the after-settle trigger set.
func (*RenderContext) HXTriggersAfterSwap ¶
func (rc *RenderContext) HXTriggersAfterSwap() HXTriggers
HXTriggersAfterSwap returns the after-swap trigger set.
func (*RenderContext) OOBSink ¶
func (rc *RenderContext) OOBSink() ByteWriter
OOBSink returns the destination for OOB swap output. Falls back to Writer when no explicit sink is configured.
func (*RenderContext) PopContextTo ¶
func (rc *RenderContext) PopContextTo(savedPtr int)
PopContextTo sets the stack pointer back to a saved index. Used by ComponentContext to unwind pushes made during its render frame so bindings don't leak out of scope. Popped slots are zeroed so the GC can reclaim any `any` values the user pushed (without this, they'd stay reachable through the stack slot until the next push overwrites them).
func (*RenderContext) PushContext ¶
func (rc *RenderContext) PushContext(key string, val any)
PushContext pushes (key, val) onto the cascading-context stack. Nested bindings shadow outer ones (reverse-scan lookup).
The first inlineCtxCap pushes land in the inline array on RenderContext itself — zero allocation, persistent across pool reuse. Beyond that, pushes spill into overflowCtx (a heap slice) so genuinely deep trees don't panic. There is no hard cap; the framework never panics on depth.
Prefer ComponentContext.ProvideContext over calling this directly — it auto-pops on the component's render scope exit and gives you true cascading isolation.
func (*RenderContext) RequiresCSS ¶
func (rc *RenderContext) RequiresCSS(href string)
RequiresCSS registers a CSS dependency for this request. The path is emitted by <NANO_ASSETS/> as a <link rel="stylesheet"> tag. Deduplicated: registering the same path twice emits it once, keeping the FIRST occurrence so emission order = first render order (deterministic — the executor walks the tree in order).
Components rendered in loops (a card repeated 50 times) register their dependency once; the head gets one tag, and the browser downloads the asset once.
func (*RenderContext) RequiresJS ¶
func (rc *RenderContext) RequiresJS(src string)
RequiresJS registers a JS dependency for this request. The path is emitted by <NANO_ASSETS/> as a <script defer> tag. Deduplicated, first occurrence wins.
func (*RenderContext) SetComponentRegistry ¶
func (rc *RenderContext) SetComponentRegistry(c *ComponentRegistry)
SetComponentRegistry binds a ComponentRegistry to this context.
func (*RenderContext) SetFormError ¶
func (rc *RenderContext) SetFormError(key, msg string)
SetFormError records a flash validation error for a form field. The error is visible to the component's re-render in the same request (flash semantics: cleared on pool reuse). Writing to the same key twice — last write wins.
Flash lifecycle: the action calls SetFormError for each invalid field, returns ErrValidation, and HandleAction re-renders the component at 200 with the errors readable via GetFormError. The errors do NOT persist across requests — the component re-renders from its own data source; the errors are display state for this one pass.
Prefer ComponentContext.SetFormError inside components; this method is for action closures (which receive rc directly).
func (*RenderContext) SetHXLocation ¶
func (rc *RenderContext) SetHXLocation(url string)
SetHXLocation triggers client-side navigation without a history entry. Full URL required.
func (*RenderContext) SetHXPushURL ¶
func (rc *RenderContext) SetHXPushURL(url string)
SetHXPushURL pushes a URL into the history stack. Pass "true" to push the response URL; pass any other string as the URL to push. Empty clears the decision.
func (*RenderContext) SetHXRedirect ¶
func (rc *RenderContext) SetHXRedirect(url string)
SetHXRedirect triggers a client-side redirect to the given URL. Full URL required.
func (*RenderContext) SetHXRefresh ¶
func (rc *RenderContext) SetHXRefresh()
SetHXRefresh triggers a full client-side page refresh when set to "true". Any other value is treated as "true" by HTMX.
func (*RenderContext) SetHXReplaceURL ¶
func (rc *RenderContext) SetHXReplaceURL(url string)
SetHXReplaceURL replaces the current URL without a history entry. Pass "true" to replace with the response URL; pass any other string as the URL to replace with.
func (*RenderContext) SetHXReselect ¶
func (rc *RenderContext) SetHXReselect(selector string)
SetHXReselect sets the HX-Reselect response header. selector is a CSS selector telling HTMX which part of the response body to swap in. Use when the server returns a wrapper page and only an inner region should land in the DOM.
func (*RenderContext) SetHXResettle ¶
func (rc *RenderContext) SetHXResettle(value string)
SetHXResettle sets the HX-Resettle response header. pass "true" to force scroll reset to the top after the swap, "false" to preserve scroll position across the swap. Empty string clears.
func (*RenderContext) SetHXReswap ¶
func (rc *RenderContext) SetHXReswap(strategy string)
SetHXReswap overrides hx-swap on the triggered element. Examples: "outerHTML", "innerHTML swap:100ms", "none".
func (*RenderContext) SetHXRetarget ¶
func (rc *RenderContext) SetHXRetarget(selector string)
SetHXRetarget overrides hx-target on the triggered element. CSS selector string. Set during the handler; applied by WriteHTMXHeaders.
func (*RenderContext) SetOOBSink ¶
func (rc *RenderContext) SetOOBSink(w ByteWriter)
SetOOBSink redirects HTMX Out-of-Band swap output for components with WithOOB enabled. Pass nil to clear; OOBSink() then falls back to Writer.
Most handlers leave this nil — the main response is the swap — but routers that want to interleave OOB updates with a different stream (e.g. an SSE trailing chunk) can set it here.
func (*RenderContext) SetTitle ¶
func (rc *RenderContext) SetTitle(title string)
SetTitle sets the document <title> for this request. Visible to the layout's <NANO_HEAD/> injector on full page loads (the view renders before the layout in the two-pass pipeline, and the head state is transferred to the layout's context).
On HTMX partial swaps, HTMX hoists <title> tags from the response body automatically — components can write them directly to their output instead.
func (*RenderContext) SetUserData ¶
func (rc *RenderContext) SetUserData(d any)
SetUserData stores the wrapped data so engines can read it back.
func (*RenderContext) SetVariant ¶
func (rc *RenderContext) SetVariant(dim, value string)
SetVariant sets a single variant dimension. Used to drive locale/theme resolution at Part() lookup.
func (*RenderContext) SubmitAsync ¶
func (rc *RenderContext) SubmitAsync(id string, fn func(w ByteWriter, rc *RenderContext, data any) error, boundary ErrorBoundaryFunc, data any)
SubmitAsync spawns a worker goroutine that renders fn(data) into a pooled buffer and submits the result through the coordinator's channel. The worker selects on rc.ctx.Done() so a client disconnect aborts the render before it finishes — no wasted work, no leaked goroutines, no orphan channels.
id is the OOB target id (HTMX hx-swap-oob="true" wrapping). fn is the render function captured by the component definition. data is the per-call props.
boundary is the panic recovery callback. When non-nil, a panic in fn triggers boundary(bufWriter, panicVal) inside the worker; the boundary's output replaces fn's output as the OOB payload. A panic in the boundary itself is swallowed and a generic placeholder OOB is emitted so the page never crashes.
SubmitAsync is a no-op when the coordinator is nil or when the context is already cancelled.
func (*RenderContext) Suspense ¶
func (rc *RenderContext) Suspense() *suspenseCoordinator
Suspense returns the active coordinator, or nil if no async component has been encountered. Useful for callers that want to check whether suspense is in flight.
func (*RenderContext) Title ¶
func (rc *RenderContext) Title() string
Title returns the current document title ("" if unset). Useful for tests and for layouts that want to read it back.
func (*RenderContext) UseId ¶
func (rc *RenderContext) UseId() string
UseId returns a stable, unique identifier for this request, suitable for pairing <label for="x"> with <input id="x">. Returns "nano-1", "nano-2", … — the first 256 come from a precomputed static array (zero allocation); deeper requests fall back to strconv formatting.
Note the difference from React: React's useId is stable per component instance across renders; ours is a per-request sequence — uniqueness (the property the label/input pairing needs) is what's guaranteed, and ids never repeat within a request. The sequence is reset per request (pool reuse).
func (*RenderContext) UserData ¶
func (rc *RenderContext) UserData() any
UserData returns the (possibly wrapped) data the injectors last saw. The Registry uses this when the original data was not a map.
func (*RenderContext) WithDebug ¶
func (rc *RenderContext) WithDebug() *RenderContext
WithDebug enables the Times map so the Debug stage can populate it.
func (*RenderContext) WithFuncMap ¶
func (rc *RenderContext) WithFuncMap(fm template.FuncMap) *RenderContext
WithFuncMap returns rc with the given FuncMap attached. The receiver is returned unchanged for chaining.
func (*RenderContext) WithLayout ¶
func (rc *RenderContext) WithLayout(name string) *RenderContext
WithLayout returns rc with the given layout name attached. The receiver is returned unchanged for chaining.
func (*RenderContext) WithLoader ¶
func (rc *RenderContext) WithLoader(loader SourceFunc) *RenderContext
WithLoader returns rc with the given Loader attached. The receiver is returned unchanged for chaining.
func (*RenderContext) WriteHTMXHeaders ¶
func (rc *RenderContext) WriteHTMXHeaders(w http.ResponseWriter)
WriteHTMXHeaders applies all accumulated HTMX response headers to the response writer in one call. The router invokes this at the end of the handler. Nil-safe.
Headers written:
- HX-Trigger (from hxTriggers)
- HX-Trigger-After-Swap (from hxTriggersAfterSwap)
- HX-Trigger-After-Settle (from hxTriggersAfterSettle)
- HX-Retarget, HX-Reswap, HX-Push-Url, HX-Redirect, HX-Refresh, HX-Replace-Url, HX-Location
w may be nil (no-op). Headers whose source is empty are skipped so the response carries only what the handler actually set.
func (*RenderContext) WriteHeader ¶
func (rc *RenderContext) WriteHeader(key, value string)
WriteHeader writes h to the underlying writer. Headers are not buffered by ByteWriter; they are pushed directly to the wrapped io.Writer so the response is flushed before the body.
func (*RenderContext) WriteHeadersTo ¶
func (rc *RenderContext) WriteHeadersTo(w io.Writer) error
WriteHeadersTo writes the queued headers to the underlying writer. Called automatically by the ByteWriter the first time bytes are written.
type Slots ¶
Slots is a map of slot name to children content. Used for multi-slot composition (e.g. Card with header, body, footer).
type SourceFunc ¶
SourceFunc returns the raw template bytes for a given template name. The Registry calls this on cache miss. Implementations are typically fs.FS, embed.FS, or a path-based loader.
func NewFileLoader ¶
func NewFileLoader(root, ext string) SourceFunc
NewFileLoader returns a SourceFunc that reads templates from the given root directory. Names are resolved as root + "/" + name + ext.
func NewMapLoader ¶
func NewMapLoader(templates map[string][]byte) SourceFunc
NewMapLoader returns a SourceFunc that serves templates from an in-memory map. Useful for tests and small embedded sets.
func NewPrefixLoader ¶
func NewPrefixLoader(prefix string, fs interface { Open(name string) (io.ReadCloser, error) }) SourceFunc
NewPrefixLoader returns a SourceFunc that prepends prefix to name and reads the resulting path. Useful for embedding.
type Sourceless ¶
type Sourceless interface {
Engine
NoSource()
}
Sourceless is an optional interface an Engine implements to signal that Compile ignores the source bytes — the engine's programs come from elsewhere (e.g. templ components, which are pre-compiled to Go at build time and registered by name). The registry then does NOT require a Loader on the RenderContext for this engine.
type State ¶
type State struct {
// contains filtered or unexported fields
}
State is the per-render state shared across components in a single render. It enables React-style state management: a parent can set a value, and a child (rendered later in the same render) can read it. State is reset on each new render.
The state object is accessed via the FuncMap (useState, get, set) or directly via the RenderContext.
func (*State) Get ¶
Get returns the value for the given key. Returns nil if the key has not been set. The boolean indicates whether the key was present.
func (*State) UseState ¶
UseState returns the value for the given key, or initialValue if the key has not been set. The initialValue is stored for subsequent calls. This is the React-style useState hook:
count, _ := state.UseState("count", 0).(int)
On the first call, UseState returns initialValue and stores it. On subsequent calls, UseState returns the stored value.
type Watcher ¶
type Watcher struct {
// contains filtered or unexported fields
}
Watcher is an fsnotify-based hot-reload handler. It watches a set of template roots and emits invalidation events to a channel. The program cache subscribes to the channel and evicts matching keys when an event fires.
Hot-reload is enabled by default; production builds can disable it at construction time.
func NewWatcher ¶
NewWatcher returns a Watcher over the given roots. The returned watcher is started; call Close to shut it down.
Source Files
¶
- actions.go
- assets.go
- bytecode.go
- cache.go
- component.go
- compress.go
- conditional.go
- context.go
- context_bridge.go
- engine_name.go
- errors.go
- execute.go
- fluent.go
- formerrors.go
- head.go
- htmx.go
- inject.go
- loader.go
- minify.go
- options.go
- page.go
- parse_html.go
- pipeline.go
- preload.go
- props.go
- render.go
- render_builder.go
- soa.go
- state.go
- superpowers.go
- suspense.go
- time.go
- variants.go
- watcher.go
- writer.go