On this page
OverviewInstallationQuick startTutorialInterpolationExpressionsDirectivesComponent systemPropsSlotsScoped stylesCustom element componentsAll directivesCLI referenceComponent APICustom Elementshtmlc
A server-side Go template engine that uses Vue.js Single File Component (.vue) syntax for authoring but renders entirely in Go with no JavaScript runtime.
This is a static rendering engine. There is no reactivity, virtual DOM, or client-side hydration. Templates are evaluated once per request and produce plain HTML.
Installation
CLI
go install github.com/dhamidi/htmlc/cmd/htmlc@latest
Go package
go get github.com/dhamidi/htmlc
Quick start
Create a component file:
<!-- templates/Greeting.vue --> <template> <p>Hello, {{ name }}!</p> </template>
Render it:
$ htmlc render -dir ./templates Greeting -props '{"name":"world"}' <p>Hello, world!</p>
Render as a full HTML page:
$ htmlc page -dir ./templates Greeting -props '{"name":"world"}' <!DOCTYPE html> <p>Hello, world!</p>
Text interpolation
{{ expr }} evaluates the expression against the current render scope and HTML-escapes the result.
<p>Hello, {{ name }}!</p> <p>{{ a }} + {{ b }} = {{ a + b }}</p>
Expression language
| Category | Operators / Syntax |
|---|---|
| Arithmetic | + - * / % ** |
| Comparison | === !== > < >= <= == != |
| Logical | && || ! |
| Nullish coalescing | ?? |
| Optional chaining | obj?.key arr?.[i] |
| Ternary | condition ? then : else |
| Member access | obj.key arr[i] arr.length |
| Function calls | fn(args) via engine.RegisterFunc |
| Array literals | [a, b, c] |
| Object literals | { key: value } |
Use .length to measure collections — it works on strings, slices, arrays, and maps:
<span>{{ items.length }}</span>
Directives overview
| Directive | Supported | Description |
|---|---|---|
v-if | Yes | Renders element only when expression is truthy |
v-else-if | Yes | Must follow v-if or v-else-if |
v-else | Yes | Must follow v-if or v-else-if |
v-for | Yes | Repeats element for each item |
v-show | Yes | Toggles display:none |
v-bind | Yes | Dynamically binds attribute or prop |
v-html | Yes | Sets inner HTML (unescaped) |
v-text | Yes | Sets text content (HTML-escaped) |
v-pre | Yes | Skips interpolation and directives for element and descendants |
v-switch / v-case | Yes | Switch/case conditional; use with v-case and v-default on child elements |
v-slot | Yes | Named and scoped slots |
v-model | Stripped | Client-side only; removed from output |
@event | Stripped | Client-side only; removed from output |
See the full directives reference for detailed examples.
Component system
Components are .vue Single File Components with up to three sections:
<template>— required; the HTML template with directives<script>and<script setup>— not supported; using either causes a parse error. htmlc renders on the server in Go — script blocks serve no purpose and are rejected to prevent silent misconfiguration.<script customelement>is the exception: it defines a client-side Web Component class and is supported. See Custom element components below.<style>— optional; global or scoped CSS
<!-- templates/Card.vue --> <template> <div class="card"> <h2>{{ title }}</h2> <slot>Default content</slot> </div> </template> <style scoped> .card { border: 1px solid #ccc; border-radius: 8px; padding: 1rem; } </style>
Props
Props are passed as a JSON map at render time. In htmlc build, props come from sibling .json files and _data.json files in parent directories.
$ htmlc render -dir ./templates Card -props '{"title":"Hello"}'
// In Go html, err := engine.RenderFragmentString("Card", map[string]any{ "title": "Hello", })
Slots
Default slot:
<!-- In Card.vue --> <slot>Fallback content</slot> <!-- Usage --> <Card title="Hello"> <p>This goes into the slot.</p> </Card>
Named slots:
<!-- In Layout.vue --> <header><slot name="header" /></header> <main><slot /></main> <footer><slot name="footer" /></footer> <!-- Usage --> <Layout> <template #header><nav>...</nav></template> <p>Main content</p> <template #footer><p>© 2024</p></template> </Layout>
Scoped styles
Add scoped to <style> to keep styles contained to the component. The engine rewrites CSS selectors and adds a scope attribute to matching elements automatically.
<style scoped> .card { background: white; } p { color: gray; } </style>
Becomes (approximately):
<style> .card[data-v-3a2b1c] { background: white; } p[data-v-3a2b1c] { color: gray; } </style>
Custom element components
A .vue component can include a <script customelement> block to define a Web Component. htmlc derives a kebab-case tag name from the component's file path and wraps the server-rendered template in that custom element tag. The browser then activates the class when the page loads.
<!-- components/ui/Counter.vue --> <template> <button>Count: {{ count }}</button> </template> <script customelement> class UICounter extends HTMLElement { connectedCallback() { /* client-side upgrade logic */ } } customElements.define('ui-counter', UICounter); </script>
The server renders the template wrapped in <ui-counter>...</ui-counter>. Scripts are collected and written to scripts/ (static build) or served from memory (dev server) automatically — no extra CLI flags required. See the Custom Elements reference for full API details and the how-to guide for practical examples.