Documentation
¶
Overview ¶
Package alacris renders alacris web components from Go and templ.
alacris (https://github.com/bmartel/alacris) is a ~6 kB web component library built on signals and fine-grained DOM updates. This package is the server half: it renders the tags, gets the props across the boundary correctly, and serves the runtime.
What the server can and cannot render ¶
A component's shadow content is produced by its setup() function when the element connects, in the browser. There is no server-side rendering of component internals and no declarative shadow DOM support. What Go renders is the element itself:
<user-card name="Ada" age="36"> <h3 slot="title">Ada Lovelace</h3> <!-- light DOM, visible immediately --> </user-card>
Slot content is real HTML in the document, so it is in the first paint and in the crawler's view of the page. Everything inside the shadow root appears once the module loads. See Pending for the stylesheet that keeps that transition from flashing.
Props ¶
Every prop crosses as an attribute, including objects and arrays: alacris coerces an attribute using the type of the prop's default, and JSON.parse is what it uses for object and array defaults. That means a fully-formed component needs no post-load property assignment, and the page works with JavaScript still in flight.
The encoding rules are in EncodeProp. Two of them exist because of sharp edges in the runtime:
- Booleans are always written out as "true" or "false" rather than by presence. Removing an attribute runs coerce(null, default), which returns false even when the declared default is true.
- Integers beyond JavaScript's safe range are an error, not a rounding. Send large identifiers as strings.
Layers ¶
The package divides into three, each usable on its own, plus an optional desktop host in a nested module:
alacris render elements and serve the runtime. Stateless. gen generate typed Go wrappers from your define() calls. live push prop changes from the server and receive component events. app the same live handler in an OS webview (github.com/bmartel/alacris-go/app).
The first two are ordinary request/response rendering. The third makes the server authoritative over component state: because a prop is a signal, "the server changed something" compiles to a single property write and a single DOM node update, with no HTML on the wire and no morphing.
Alacris UI ships in the ui subpackage: sixty-eight Material Design 3 components, enabled with Config.UI. A zero Config.Theme still applies the Material default scheme.
Content Security Policy ¶
alacris registers a Trusted Types policy named by TrustedTypesPolicy for template parsing, and contains no eval. The live client registers a second, named alacris-live, which it uses only for Handle.SetHTML. Under a trusted-types directive, allow the ones you use:
Content-Security-Policy: trusted-types alacris alacris-live; require-trusted-types-for 'script'
Config.Nonce (or templ.WithNonce on the context) puts a nonce on every script tag this package emits.
Index ¶
- Constants
- Variables
- func AcceptsGzip(r *http.Request) bool
- func Assets() fs.FS
- func AttrName(prop string) string
- func EncodeAttr(v any) (s string, bare bool, ok bool, err error)
- func EncodeProp(v any) (s string, ok bool, err error)
- func IsZero(v any) bool
- func MatchETag(ifNoneMatch, etag string) bool
- func RuntimeHandler() http.Handler
- func Scripts(cfg Config) templ.Component
- func SetCacheHeaders(h http.Header, r *http.Request, etag string)
- func ValidAttrName(name string) error
- func ValidElementName(name string) error
- func ValidTagName(name string) error
- type Config
- type Element
- func (e *Element) Apply(vars VarSet, values map[string]string) *Element
- func (e *Element) Attr(name string, v any) *Element
- func (e *Element) Attrs(attrs templ.Attributes) *Element
- func (e *Element) Children(c templ.Component) *Element
- func (e *Element) Class(names ...string) *Element
- func (e *Element) ClassIf(cond bool, names ...string) *Element
- func (e *Element) Err() error
- func (e *Element) ID(id string) *Element
- func (e *Element) On(event, action string) *Element
- func (e *Element) Prop(name string, v any) *Element
- func (e *Element) PropRaw(name, value string) *Element
- func (e *Element) Props(props map[string]any) *Element
- func (e *Element) Render(ctx context.Context, w io.Writer) error
- func (e *Element) Slot(name string, c templ.Component) *Element
- func (e *Element) SlotAs(tag, name string, c templ.Component) *Element
- func (e *Element) Style(property, value string) *Element
- func (e *Element) Tag() string
- func (e *Element) Text(s string) *Element
- func (e *Element) Var(name, value string) *Element
- func (e *Element) Vars(vars map[string]string) *Element
- type Pending
- type Theme
- type ThemeColors
- type ThemeOverrides
- type VarSet
Examples ¶
Constants ¶
const ( AssetCore = "alacris.js" AssetCoreDev = "alacris.dev.js" AssetStore = "store.js" AssetContext = "context.js" AssetSignal = "signal.js" AssetLive = "live.js" AssetUI = "ui/index.js" AssetUITheme = "ui/theme/index.js" )
Asset file names served by RuntimeHandler.
const DefaultBase = "/_alacris/"
DefaultBase is where the runtime is expected to be mounted.
const MaxSafeInteger = 1<<53 - 1
MaxSafeInteger is JavaScript's Number.MAX_SAFE_INTEGER. Integers beyond this magnitude cannot survive the trip through alacris' numeric coercion (`+v`), which produces a float64. Encoding one is an error rather than a silent rounding: send large identifiers as strings instead.
const RuntimeVersion = "0.11.3"
RuntimeVersion is the version of the alacris npm package vendored in assets/. Regenerate with `go generate ./...` after bumping it.
const TrustedTypesPolicy = "alacris"
TrustedTypesPolicy is the Trusted Types policy name alacris registers for template parsing. Under a trusted-types CSP directive it has to be allowed.
const UIVersion = "0.4.0"
UIVersion is the version of the @alacris/ui package vendored in assets/ui/.
Variables ¶
var ErrNonFiniteFloat = errors.New("alacris: NaN and Inf cannot be encoded as a prop")
ErrNonFiniteFloat is returned for NaN and ±Inf, which have no attribute form.
var ErrUnsafeInteger = errors.New("alacris: integer exceeds JavaScript's safe integer range; encode it as a string")
ErrUnsafeInteger is returned when an integer prop cannot be represented exactly as a JavaScript number.
Functions ¶
func AcceptsGzip ¶ added in v0.3.0
AcceptsGzip reports whether the request says gzip is an acceptable content coding. A quality of zero is a refusal, not an acceptance.
func Assets ¶
Assets returns the vendored alacris runtime as a filesystem, for projects that would rather copy the files into their own asset pipeline than serve them from Go.
func AttrName ¶
AttrName converts a component's JavaScript prop name to the attribute name alacris observes for it.
It mirrors define.js exactly:
const kebab = s => s.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
including its sharp edges — a leading capital produces a leading dash, and an acronym expands letter by letter ("URL" becomes "-u-r-l"). Reproducing the quirks matters more than being tasteful: the attribute has to match what the element is listening for.
func EncodeAttr ¶
EncodeAttr renders v as an ordinary HTML attribute value, following HTML rules rather than alacris' — a true bool becomes a bare attribute and a false one disappears. bare is true when the attribute should be written without a value.
func EncodeProp ¶
EncodeProp renders v as the attribute text for an alacris prop.
The encoding is chosen from v's Go type, and must line up with the type of the prop's default in the component's define() call, because that default is what drives coercion on the other side (define.js, coerce):
Go string, fmt.Stringer, time.Time -> text (default: a string) Go bool -> "true"/"false" (default: a boolean) Go integers and floats -> a number (default: a number) everything else -> JSON (default: an object or array)
ok is false when v is nil or a nil pointer, meaning the attribute should be left off entirely so the component keeps its declared default.
func IsZero ¶
IsZero reports whether v is its type's zero value. Generated wrappers use it to decide whether a prop of a named or struct type was set at all, where there is no operator they can rely on.
func MatchETag ¶ added in v0.3.0
MatchETag reports whether an If-None-Match header matches etag. Unlike a substring check it honours the header's real grammar: a comma-separated list, an optional W/ weakness prefix, and "*" matching anything.
func RuntimeHandler ¶
RuntimeHandler serves the vendored alacris runtime.
It resolves requests by file name only, so it behaves the same at any mount point and does not need http.StripPrefix:
mux.Handle("/_alacris/", alacris.RuntimeHandler())
func SetCacheHeaders ¶
SetCacheHeaders applies the caching policy for a served asset.
An asset URL with no version in it must be revalidated: it is the same URL before and after a deploy, so a long max-age means a fixed bug stays broken in every browser that already has the old copy. Revalidation costs one conditional request and almost always answers 304.
Config.Version puts a ?v= on the URLs it emits, and that is what makes an asset safe to cache for a year: a new version is a new URL.
func ValidAttrName ¶
ValidAttrName reports whether name can be written into an HTML start tag.
An attribute name is structure, not content: it goes into the tag verbatim, because there is no escaping that would leave it meaning the same thing. So this is an allowlist rather than a list of characters to avoid — a name that is not recognisably a name is refused, and the caller finds out at render rather than the browser finding out at parse.
A leading '-' is permitted: AttrName produces one for a prop whose name begins with a capital, matching define.js, and the element really is listening for that attribute.
func ValidElementName ¶ added in v0.1.2
ValidElementName reports whether name can be written as a tag.
Used for the wrapper a slot's content is placed in. Raw-text elements are refused outright: a slot wrapper has no business being one, and content that is escaped everywhere else would not be escaped inside it.
func ValidTagName ¶
ValidTagName reports whether name is a valid custom element name — the same rule customElements.define enforces, so a typo fails at render time on the server rather than silently rendering an unknown element in the browser.
Types ¶
type Config ¶
type Config struct {
// Base is the URL prefix RuntimeHandler is mounted at. Defaults to
// DefaultBase.
Base string
// Dev swaps in the unminified build, which carries readable errors.
Dev bool
// Modules are your own ES module entry points, loaded after the import
// map. This is where the file that calls define() for your components
// goes.
Modules []string
// Imports adds or overrides import map entries, for pulling in component
// packages of your own by bare specifier.
Imports map[string]string
// Live loads the live client and connects it. Page must be the id of a
// session created by the live package for this page render.
//
// The page id is not a secret and does not need protecting: it says which
// of a browser's pages is talking, and it is useless without the cookie
// the live package sets, which script cannot read and which never appears
// in a page or a URL.
Live bool
Page string
// Endpoint is where the live client connects. Defaults to Base + "live".
Endpoint string
// NoRecover turns off the live client's automatic recovery. By default,
// when the patch stream fails permanently — the server restarted, the
// session expired — the client probes the endpoint until the server
// answers, reattaches if the session survived, and reloads the page once
// for a fresh session if it did not. Set NoRecover when the application
// listens for 'alacris:live' window events and owns that decision itself.
NoRecover bool
// Nonce is the CSP nonce for the emitted script tags. When empty, the
// nonce carried on the context by templ.WithNonce is used.
Nonce string
// Version is appended to every asset URL as ?v=, which makes each release
// a distinct URL and lets the handler cache it for a year instead of
// revalidating it. Any string that changes with a deploy works: a release
// tag, a build id, a commit.
Version string
// UI loads Alacris UI — every Material Design 3 component, the token
// system, and applyTheme. The typed wrappers live in the ui subpackage.
//
// A zero Theme still applies the default Material scheme (seed #e8ad18,
// Google Sans Flex, light/dark from the OS). Set Theme to re-skin the
// page; every component follows because they consume system tokens.
UI bool
Theme Theme
}
Config describes the script tags a page needs.
func (Config) ImportMap ¶
ImportMap returns the import map this configuration produces, so the same specifiers can be reused by a bundler or a test.
func (Config) Scripts ¶
Scripts renders the import map, your module entry points, and, when configured, the live client.
It belongs in <head>, before any other module script: an import map has to precede the first module import it applies to.
Example ¶
Scripts emits the import map and module tags a page needs; with Version set, every asset URL is release-specific and cacheable for a year.
package main
import (
"context"
"os"
alacris "github.com/bmartel/alacris-go"
)
func main() {
cfg := alacris.Config{
Version: "1.0.0",
Modules: []string{"/static/app.js"},
}
_ = cfg.Scripts().Render(context.Background(), os.Stdout)
}
Output: <script type="importmap">{"imports":{"alacris":"/_alacris/alacris.js?v=1.0.0","alacris/context":"/_alacris/context.js?v=1.0.0","alacris/signal":"/_alacris/signal.js?v=1.0.0","alacris/store":"/_alacris/store.js?v=1.0.0"}}</script><script type="module" src="/static/app.js"></script>
type Element ¶
type Element struct {
// contains filtered or unexported fields
}
Element is a server-rendered alacris custom element: a tag, its props, its ordinary HTML attributes, and its light-DOM children.
It implements templ.Component, so it can be used directly in a .templ file:
@alacris.E("user-card").Prop("name", "Ada").Prop("tags", []string{"a", "b"}) {
<h3 slot="title">Ada Lovelace</h3>
}
What the server can render is the tag, its attributes and its children. A component's shadow content is built by setup() in the browser and never appears in the HTML — see the package documentation.
Builder methods collect errors instead of panicking; Render reports them.
func E ¶
E starts an element with the given custom element tag name. An invalid tag name is recorded as an error and surfaces when the element is rendered.
Example ¶
Every prop crosses as an attribute, objects and arrays included, so the element is complete before any JavaScript has run.
package main
import (
"context"
"os"
alacris "github.com/bmartel/alacris-go"
)
func main() {
card := alacris.E("user-card").
Prop("name", "Ada").
Prop("tags", []string{"math", "code"}).
ID("ada")
_ = card.Render(context.Background(), os.Stdout)
}
Output: <user-card name="Ada" tags="["math","code"]" id="ada"></user-card>
func (*Element) Apply ¶
Apply sets custom properties from a component's theming contract, rejecting anything the contract does not declare. It is Vars with the safety net:
@ui.Chip(props).Apply(ui.ChipVars, map[string]string{"--chip-bg": "#ffe9a8"})
func (*Element) Attr ¶
Attr sets an ordinary HTML attribute, using HTML's rules rather than alacris' — a true bool renders bare and a false one is omitted.
"class" appends to the element's class list. "style" is rejected in favour of Style and Var, which sanitize their input.
func (*Element) Attrs ¶
func (e *Element) Attrs(attrs templ.Attributes) *Element
Attrs spreads a templ attribute map onto the element. Keys are applied in sorted order.
func (*Element) Children ¶
Children sets the element's light-DOM children explicitly, instead of taking them from the templ block that encloses the element.
func (*Element) ID ¶
ID sets the element's id. The live package addresses elements by id, so any element the server intends to patch needs one.
func (*Element) On ¶
On forwards a CustomEvent the component emits to a named server action, handled by the live package. It renders as a data-ala-on attribute; without the live client script on the page it does nothing.
func (*Element) Prop ¶
Prop sets a component prop, converting name to the attribute alacris observes for it and encoding the value for that prop's declared type. A nil value leaves the attribute off so the component keeps its own default.
Prop values are encoded from their Go type (see EncodeProp), so the Go type has to agree with the type of the prop's default in define(). Generated wrappers guarantee that; hand-written calls are on their own.
func (*Element) PropRaw ¶
PropRaw sets a prop to an already-encoded attribute value, skipping the encoding rules. The caller is responsible for matching the prop's declared type — a prop whose default is an object needs valid JSON here, and alacris silently falls back to the default when JSON.parse throws.
func (*Element) Render ¶
Render writes the element. Props come first, then class and style, then any remaining attributes, so output is stable and diffable.
func (*Element) Slot ¶
Slot renders c as light-DOM children inside a <div slot="name">, filling the component's named slot.
The wrapper element is real and participates in layout. When that matters, use SlotAs to pick the tag, or put the slot attribute on your own markup — generated packages export the slot names as constants for exactly that.
func (*Element) Style ¶
Style adds one declaration to the element's style attribute. Both the property and the value are checked; anything that could escape the declaration is an error rather than a silent substitution.
func (*Element) Var ¶
Var sets a CSS custom property on the element, which is how a themed alacris component is configured from outside its shadow root. A leading "--" is optional.
This is the supported way to push a runtime value into a component's styling; rebuilding a stylesheet per state change is the thing to avoid.
type Pending ¶
type Pending struct {
// Tags to hide. Usually every alacris tag the page renders.
Tags []string
// Style is the declaration applied while undefined.
// Defaults to visibility: hidden.
Style map[string]string
// Nonce for the emitted <style> element. Falls back to the context nonce.
Nonce string
}
Pending renders the stylesheet that hides custom elements until they are defined.
It is worth having. An alacris component's shadow content is built by setup() in the browser, so between first paint and the module loading, the element is present but empty. Without this the page paints, then reflows.
The elements still occupy no space until they are defined; give them a reserved size in your own CSS if layout stability matters.
type Theme ¶ added in v0.4.0
type Theme struct {
// Seed is the single colour a whole scheme is grown from. Ignored for a
// role that Colors names explicitly.
Seed string
// Colors names key palettes directly. Any subset is fine; omitted roles
// are derived from Seed (or the Material default).
Colors ThemeColors
// Typography is a preset name ("google-sans-flex", "google-sans",
// "roboto", "system") or a CSS font family. Empty keeps Google Sans Flex.
Typography string
// Radius multiplies the Material shape scale. nil keeps 1; 0 is square;
// 2 is extra round. A pointer so 0 is distinct from "unset".
Radius *float64
// Motion multiplies durations. nil keeps 1; 0 is instant.
Motion *float64
// Density is 0, -1 or -2. nil keeps 0.
Density *int
// Scheme pins light or dark, or "auto" (the default) to follow the OS.
Scheme string
// LoadFonts is whether applyTheme injects the theme's typeface stylesheet.
// nil means yes. Set to false when faces are self-hosted or already on
// the page.
LoadFonts *bool
// Overrides are raw token writes, applied last. Keys are token names
// without the `--ui-` prefix (`color-primary`, `radius-md`).
Overrides ThemeOverrides
}
Theme is the Go form of @alacris/ui's applyTheme config. A zero Theme with Config.UI still applies Material Design 3 defaults: seed #e8ad18, Google Sans Flex, density 0, and light/dark following the OS.
Re-theming is one stylesheet write. Every component consumes system tokens, so a new seed (or an explicit primary) re-skins the page without touching a component.
type ThemeColors ¶ added in v0.4.0
type ThemeColors struct {
Primary, Secondary, Tertiary string
Neutral, NeutralVariant string
Error, Success, Warning, Info string
}
ThemeColors is the explicit-palette form of Theme.Seed. Empty fields are derived.
type ThemeOverrides ¶ added in v0.4.0
type ThemeOverrides struct {
Common map[string]string
Light map[string]string
Dark map[string]string
}
ThemeOverrides are last-write token maps, matching createTheme's `overrides: { common, light, dark }`.
type VarSet ¶
type VarSet struct {
// contains filtered or unexported fields
}
VarSet is the Go side of a component's theming contract: the custom properties declared by alacris' vars(prefix, defaults).
Names are derived exactly as style.js derives them, so a Go constant and the component's own stylesheet cannot drift apart:
vars('btn', { bg: '#111' }) -> --btn-bg
func NewVarSet ¶
NewVarSet declares a contract the way alacris' vars() does: a prefix plus camelCase keys. NewVarSet("btn", "bg", "borderRadius") is the contract for vars('btn', { bg: ..., borderRadius: ... }), giving --btn-bg and --btn-border-radius.
Both the short key and the full property name work as keys in Apply.
func Vars ¶
Vars declares a contract from full custom property names, which is what generated packages use: a component's @cssprop tags name the properties outright rather than a prefix and keys.
func (VarSet) Apply ¶
Apply sets the given values on an element. A key outside the contract is recorded as an error on the element rather than written out, so a rename in the component surfaces here instead of silently theming nothing.
Directories
¶
| Path | Synopsis |
|---|---|
|
app
module
|
|
|
cmd
|
|
|
alacris-go
command
Command alacris-go generates typed Go wrappers for alacris web components and hosts a desktop app around the same live handler.
|
Command alacris-go generates typed Go wrappers for alacris web components and hosts a desktop app around the same live handler. |
|
examples
|
|
|
todo
command
Command todo is the alacris-go example — a live board.
|
Command todo is the alacris-go example — a live board. |
|
todo/model
Package model is the example's server-side state.
|
Package model is the example's server-side state. |
|
todo/ui
Package ui renders the project's alacris components from Go.
|
Package ui renders the project's alacris components from Go. |
|
Package gen turns alacris component definitions into typed Go wrappers.
|
Package gen turns alacris component definitions into typed Go wrappers. |
|
internal
|
|
|
appmeta
Package appmeta is the desktop app metadata and bundler used by `alacris-go app`.
|
Package appmeta is the desktop app metadata and bundler used by `alacris-go app`. |
|
docsgen
command
Command docsgen renders the documentation site's Go examples.
|
Command docsgen renders the documentation site's Go examples. |
|
genui
command
Command genui generates the ui package from the vendored @alacris/ui sources.
|
Command genui generates the ui package from the vendored @alacris/ui sources. |
|
vendorjs
command
Command vendorjs refreshes the vendored alacris runtime and @alacris/ui sources in assets/ from the npm registry.
|
Command vendorjs refreshes the vendored alacris runtime and @alacris/ui sources in assets/ from the npm registry. |
|
Package live makes the server authoritative over component state.
|
Package live makes the server authoritative over component state. |
|
livetest
Package livetest makes live action handlers unit-testable.
|
Package livetest makes live action handlers unit-testable. |
|
Package ui is the typed Go surface of Alacris UI — sixty-eight Material Design 3 components, a three-tier token system, and a theme engine.
|
Package ui is the typed Go surface of Alacris UI — sixty-eight Material Design 3 components, a three-tier token system, and a theme engine. |