porang
The Arandu view layer: templ for markup, HTMX for interaction, Tailwind for
style. It is a binary, and it is never Node.
A project using it still runs with git clone && aru dev. No node_modules, no
package.json, no JavaScript lockfile, nothing installed beyond Go. Having a
build step is allowed; being Node is not.
What is in the binary
HTMX, Alpine and the compiled stylesheet are embedded with go:embed and served
from /_arandu/assets/<hash>/<name>.
That is not preference. SecurityHeaders sets script-src 'self', so loading
HTMX from a CDN would mean loosening the CSP — paying in security for
convenience. It also keeps the deploy at one binary: no asset publishing step,
no CDN to invalidate, no storage:link.
The hash comes from the content, so upgrading HTMX changes the URL and nobody
serves a stale script. A request carrying an old hash still gets the file, with
no-cache instead of immutable: a stale reference degrades into a slow page,
never a broken one.
The line that matters most
@layout.Base(layout.Props{Title: "Sign in", CSRFToken: token})
Base puts hx-headers on <body>, carrying the CSRF token into every HTMX
request made from anywhere on the page. Without it, every hx-post and
hx-delete fails the CSRF check — and the failure looks like a session problem,
so people spend an afternoon on it.
It lives here, in the framework's own layout, so nobody has to remember. aru doctor checks for it too.
Components are functions
@components.Button(components.ButtonProps{
Label: "Delete",
Variant: components.Danger,
HX: components.HX{Delete: "/invoices/1", Confirm: "Delete this invoice?"},
})
There is no <nx-modal /> and no web component registered at runtime. A custom
tag would be a second component system beside templ, resolved at runtime, with
errors that only appear on screen. templ already gives composition, types and a
compile error.
The HX struct exists for the same reason: hx-swep instead of hx-swap is a
typo that silently does nothing, and a review misses it.
Where Alpine is allowed
Only when the state is all three at once: purely client-side, ephemeral (dies on
reload with nothing lost), and invisible to the server.
Allowed: a dropdown being open, the selected tab, focus, an input mask, a
confirmation before submitting.
Not allowed: anything that reaches the server, anything persisted, anything
derived from data the server already has. If you are writing a business rule
in x-data, the component should have been an HTMX fragment.
Building the stylesheet
tailwindcss -i assets/app.src.css -o assets/app.css --minify
The standalone binary, a single file, managed by aru and pinned by version —
the same way Go handles its own toolchain. Never npx, never a package from npm.
What this package does not touch
The error page. It has to render when everything else is broken, including when
the asset build failed, so it stays as inline html/template in
observability/errorpage.
License
MIT, the same license Laravel uses. See LICENSE.md.