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 jspnpm exec edge-client build templates --out client --format jsyarn edge-client build templates --out client --format jsbunx edge-client build templates --out client --format jsEmits:
runtime.js— micro-runtime (escape,html.attrs,$props,$slots, component glue)templates.js— typedrender*functions plus atemplatesmap
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.