Skip to content
edge-language-tools
Esc
navigateopen⌘Jpreview
On this page

Client-side rendering (experimental)

Extract @client templates into typed browser render functions.

An explicit build-time path from @client-marked templates to a tiny browser module — no Edge compiler in the bundle, no eval, CSP-clean.

Mark templates

Add @client beside @types in the doc comment. Only templates you mark (and their @client dependency closure) are extracted.

{{--
@client
@types {
  variant?: 'primary' | 'outline'
}
--}}
<button {{ html.attrs({ class: 'btn', ...(variant ? { 'data-variant': variant } : {}) }) }}>
  {{{ await $slots.main() }}}
</button>

Every dependency must also be @client. A non-client component in the closure fails the build with the dependency chain.

Build

npx edge-client build templates --out client --format js
pnpm exec edge-client build templates --out client --format js
yarn edge-client build templates --out client --format js
bunx edge-client build templates --out client --format js

Emits:

  • runtime.js — micro-runtime (escape, html.attrs, $props, $slots, component glue)
  • templates.js — typed render* functions plus a templates map

Use --format ts when consumers typecheck the output; use js for direct browser <script type="module"> imports.

Render in the browser

import { renderButton } from '/client/templates.js'

const html = await renderButton(
  { variant: 'outline' },
  { main: async () => 'Save' },
)

Pair with a DOM update strategy (e.g. Idiomorph) for optimistic UI: re-render from the same .edge source the server uses, morph the result in, then confirm with the server.

See examples/basecoat-edge for a demo: bun run gen-client, then bun run demo — the Optimistic update section imports renderButton from the generated module.

Was this page helpful?