On this page
SynopsisInstallationrenderpagebuildpropsasthelpStatic sitesLayoutsData filesPage partialsOverviewDiscoveryProtocolcreated hookmounted hookCLI Reference
htmlc renders Vue Single File Components (.vue) to HTML entirely in Go — no Node.js, no browser, no JavaScript runtime.
Installation
go install github.com/dhamidi/htmlc/cmd/htmlc@latest
render
Renders a .vue component as an HTML fragment (no <!DOCTYPE>). Scoped styles are prepended as a <style> block.
htmlc render [-strict] [-dir <path>] [-layout <name>] [-debug] [-props <json>] <ComponentName>
Flags
.vue components. Default: .Examples
# Render a greeting fragment htmlc render -dir ./templates Greeting -props '{"name":"world"}' # Render with layout htmlc render -dir ./templates Article -layout AppLayout -props '{"title":"Hello"}' # Pipe props from stdin echo '{"name":"world"}' | htmlc render -dir ./templates Greeting
page
Like render, but outputs a full HTML page (adds <!DOCTYPE html> and injects scoped styles into <head>).
htmlc page [-strict] [-dir <path>] [-layout <name>] [-debug] [-props <json>] <ComponentName>
$ htmlc page -dir ./templates HomePage -props '{"title":"My site"}' <!DOCTYPE html> <html> <head><title>My site</title></head> <body><h1>My site</h1></body> </html>
build
Walks the pages directory recursively, renders every .vue file as a full HTML page, and writes results to the output directory. The directory hierarchy is preserved. Supports external directives for custom element transformations.
htmlc build [-strict] [-dir <path>] [-pages <path>] [-out <path>] [-layout <name>] [-debug] [-dev <addr>]
Flags
.vue components. Default: ../pages./out-dir) to wrap every page.addr with live rebuild (e.g. :8080).Data files
Props for each page are loaded by merging JSON data files in order (later wins):
pages/_data.json— root defaults (all pages)pages/subdir/_data.json— subdirectory defaultspages/subdir/hello.json— page-level props (highest priority)
Shared page partials
Any .vue file whose base name starts with _ is treated as a shared partial and is skipped during page discovery. Partials are not rendered as standalone HTML pages; they exist solely to be referenced as child components by other pages.
pages/ _Header.vue ← skipped (partial, not a page) _Footer.vue ← skipped (partial, not a page) index.vue → dist/index.html about.vue → dist/about.html blog/ _PostCard.vue ← skipped (partial) index.vue → dist/blog/index.html hello-world.vue → dist/blog/hello-world.html
The partial is still registered as a component and can be used inside other pages:
<!-- pages/index.vue --> <template> <_Header :title="siteTitle" /> <main>…</main> <_Footer /> </template>
The leading _ is part of the component name when referenced in templates. Use it to signal to readers that the file is a layout aid rather than a user-facing page.
Examples
# Build with defaults (components in ., pages in ./pages, output to ./out) htmlc build # Explicit paths htmlc build -dir ./templates -pages ./pages -out ./dist # With a shared layout htmlc build -dir ./templates -pages ./pages -out ./dist -layout AppLayout # Development server with live rebuild htmlc build -dir ./templates -pages ./pages -out ./dist -dev :8080
Layouts
Two patterns for layouts:
Pattern 1 — Component-embedded layout: The page component references the layout directly using slots. No CLI flag needed.
<!-- templates/PostPage.vue --> <template> <AppLayout :title="title"> <article>{{ body }}</article> </AppLayout> </template>
Pattern 2 — -layout flag: The page renders as a fragment; htmlc passes the HTML as content prop to the layout. The page needs no knowledge of the layout.
<!-- templates/AppLayout.vue --> <template> <html> <body> <main v-html="content"></main> </body> </html> </template>
htmlc build -dir ./templates -pages ./pages -out ./dist -layout AppLayout
props
Lists the props referenced by a component — useful for discovering what data a component expects.
htmlc props [-dir <path>] <ComponentName>
$ htmlc props -dir ./templates Card title body author
Export as shell variables:
$ htmlc props -dir ./templates Card -export export title="" export body="" export author=""
ast
Prints the parsed template as a JSON AST. Useful for debugging parsing problems or understanding how htmlc sees a template.
htmlc ast [-dir <path>] <ComponentName>
help
htmlc help [<subcommand>]
# Show general help htmlc help # Show help for a specific subcommand htmlc help build
External directives
External directives extend htmlc build with custom element transformations. They are standalone executables that communicate with the build via newline-delimited JSON (NDJSON) over stdin/stdout.
Discovery
During build, htmlc walks the component directory (-dir) and registers every file that satisfies all three conditions:
| Condition | Rule |
|---|---|
| Name | Base name without extension matches v-<directive-name> |
| Directive name format | Lower-kebab-case: [a-z][a-z0-9-]* |
| Executable | File mode has at least one executable bit set (mode & 0111 != 0) |
Hidden directories (names starting with .) are skipped. Extensions are ignored, so v-foo, v-foo.sh, and v-foo.py all register as directive foo.
v-syntax-highlight → directive name: syntax-highlight v-upper.sh → directive name: upper v-toc-builder.py → directive name: toc-builder
Each directive is started once at the beginning of the build and stopped when the build finishes. A non-zero exit code is treated as a warning; the build continues.
Protocol
Communication is NDJSON: one JSON object per line, no pretty-printing. Requests flow from htmlc to the directive on stdin; responses flow back on stdout. Requests are sent sequentially. The directive's stderr is forwarded verbatim to htmlc's stderr.
Request envelope (sent for every element carrying the directive's attribute). Both text and inner_html are populated from the element's fully pre-rendered children — all template expressions are already evaluated before the directive hooks run.
{ "hook": "created" | "mounted", "id": "<opaque string>", "tag": "<element tag name>", "attrs": { "<name>": "<value>", ... }, "text": "<plain text extracted from rendered children>", "inner_html": "<fully rendered inner HTML of the element's children>", "binding": { "value": "<evaluated expression>", "raw_expr": "<unevaluated expression string>", "arg": "<directive argument, or empty string>", "modifiers": { "<modifier>": true, ... } } }
created hook
Called before the element is rendered. The response may mutate the element's tag, attributes, or inner content.
{ "id": "<same id as request>", "tag": "<optional: replacement tag name>", "attrs": { "<name>": "<value>", ... }, "inner_html": "<optional: verbatim HTML to use as element content>", "error": "<optional: non-empty string aborts rendering of this element>" }
| Field | Effect |
|---|---|
id | Required. Must match the request id. |
tag | If non-empty, replaces the element's tag name. |
attrs | If present, replaces all element attributes. |
inner_html | If non-empty, replaces the element's children with this HTML verbatim. Template children are discarded. |
error | If non-empty, aborts rendering of this element. |
mounted hook
Called after the element's closing tag has been written.
{ "id": "<same id as request>", "html": "<optional: HTML injected immediately after the closing tag>", "error": "<optional: non-empty string aborts rendering>" }
| Field | Effect |
|---|---|
id | Required. Must match the request id. |
html | If non-empty, written verbatim after the element's closing tag. |
error | If non-empty, aborts rendering and logs the message. |