Generating wrappers
The generator reads the define() call itself. There is no second file to keep
in step, and no manifest to maintain until you want one.
alacris-go generate ./web/components -o ./internal/componentsWhat comes out
Section titled “What comes out”For each component: a tag constant, a props struct, a constructor, slot and event constants, typed event details, and the theming contract.
const UserCardTag = "user-card"
const ( UserCardSlotTitle = "title")
const ( UserCardEventGreet = "greet")
type UserCardGreetDetail struct { Name string `json:"name"`}
var UserCardVars = alacris.Vars( "--card-bg", // the background (defaults to #fff))
type UserCardProps struct { Name string Age int Tags []string}
func UserCard(p UserCardProps) *alacris.ElementPlus a shared file with Tags, the list every component in the package
registers, which is what alacris.Pending wants:
@alacris.Pending{Tags: components.Tags}// What ui.TodoList(...) expands to. The generator knows every prop's// default, so Filter: "all" would have been left off the element.return alacris.E("ala-todo-list"). ID("todos"). Prop("items", []todoItem{{ID: 1, Text: "Read the docs"}}). Prop("filter", "active"). On("add", "add-todo"). On("toggle", "toggle-todo")<ala-todo-list items="[{"id":1,"text":"Read the docs","done":false}]" filter="active" id="todos" data-ala-on="add:add-todo toggle:toggle-todo"></ala-todo-list>What a generated wrapper expands to.
Telling it what define() cannot
Section titled “Telling it what define() cannot”define() says a prop’s name and, through its default, how it is coerced. It
does not say that tags holds strings, that a greet event exists, or which
slots a component renders. JSDoc above the call does.
/** * A person, at a glance. * * @prop {string[]} tags the labels shown under the name * @prop {integer} age whole years * @fires greet {name: string} - the user said hello * @slot title - replaces the heading * @cssprop [--card-bg=#fff] - the background * @goname PersonCard */define('user-card', { props: { name: 'anon', age: 0, tags: [] }, setup,});Without @prop {string[]} tags, an empty array default gives []any, because
an empty array says nothing about its elements. The tag is how you say
otherwise. Every tag is listed in the JSDoc reference.
What it refuses to guess
Section titled “What it refuses to guess”The scanner is a scanner, not a JavaScript engine. A props object it cannot read as literal data is an error, never a guess:
define('x-y', { props: buildProps(), setup });// t.js:1: <x-y>: props is expression, not an object literal;// declare the props in a manifest instead
define('x-y', { props: { ...base, a: 1 }, setup });// t.js:1: <x-y>: props holds a spread or a computed key, so the full set of// props is not visible here; declare this component in a manifest insteadGenerating the props it can see would produce a struct quietly missing fields, which is worse than stopping.
The manifest escape hatch
Section titled “The manifest escape hatch”When a component builds its props at runtime, describe it by hand:
-
Write out what the scanner found:
Terminal window alacris-go manifest ./web -o alacris.components.json -
Edit it. Add the component the scanner refused, fix a type, name a Go type for an event detail.
-
Generate from it, alone or mixed with sources:
Terminal window alacris-go generate ./alacris.components.json ./web/other -o ./internal/components
The manifest is the same shape the scanner produces, so nothing is lost by switching one component to it.
Naming
Section titled “Naming”A tag becomes a Go identifier: user-card → UserCard. Recognised
initialisms keep their casing, so avatarUrl → AvatarURL and itemId →
ItemID.
A project prefix on every tag would put it on every identifier too, so strip it:
alacris-go generate ./web -o ./internal/components -strip ala-# <ala-counter> generates Counter, not AlaCounterCollisions are refused, including ones stripping introduces:
alacris-go: with -strip "ala-", <ala-todo-item> and <todo-item> both generate TodoItem@goname settles it.
Do not generate your own wrappers into a package also named ui if you import
github.com/bmartel/alacris-go/ui. That is
the design system. Put app wrappers in ./internal/components (or similar),
or alias one of the imports.
In your build
Section titled “In your build”//go:generate go run github.com/bmartel/alacris-go/cmd/alacris-go generate ./web -o ./internal/components -strip ala-//go:generate go run github.com/a-h/templ/cmd/templ@latest generateThe wrappers first. The templates use them.
In CI, check instead:
- name: Generated wrappers are up to date run: go run ./cmd/alacris-go check ./web -o ./internal/components -strip ala-check compares byte for byte and reports what differs, including files left
behind by a component that was renamed or deleted. Output does not depend on
the directory you run from, so the same command works locally and in CI.
Layout
Section titled “Layout”Directoryweb/
- components.js your
define()calls
- components.js your
Directoryinternal/components/ generated app wrappers; do not edit
- components_gen.go
- alacris_gen.go
Tags, package docs
- alacris.components.json optional, only for what the scanner cannot read
Generated files carry a Code generated by alacris-go … DO NOT EDIT. header and
are marked linguist-generated by the repository’s .gitattributes, so review
diffs stay readable.