On this pageSynopsisInstallationrenderpagebuildpropsasthelpStatic sitesLayoutsData filesPage partialsOverviewDiscoveryProtocolcreated hookmounted hook

CLI 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

-dir stringDirectory containing .vue components. Default: .
-props stringJSON object of props to pass to the component.
-layout stringWrap the fragment in a layout component.
-debugAnnotate output with HTML comments showing render trace.
-strictAbort on missing props.

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

-dir stringDirectory containing shared .vue components. Default: .
-pages stringRoot of the page tree. Default: ./pages
-out stringOutput directory. Created if missing. Default: ./out
-layout stringLayout component (from -dir) to wrap every page.
-dev stringStart a dev server at addr with live rebuild (e.g. :8080).
-strictAbort on missing props; validate all components before rendering.
-debugAnnotate output with diagnostic HTML comments.

Data files

Props for each page are loaded by merging JSON data files in order (later wins):

  1. pages/_data.json — root defaults (all pages)
  2. pages/subdir/_data.json — subdirectory defaults
  3. pages/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:

ConditionRule
NameBase name without extension matches v-<directive-name>
Directive name formatLower-kebab-case: [a-z][a-z0-9-]*
ExecutableFile 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>"
}
FieldEffect
idRequired. Must match the request id.
tagIf non-empty, replaces the element's tag name.
attrsIf present, replaces all element attributes.
inner_htmlIf non-empty, replaces the element's children with this HTML verbatim. Template children are discarded.
errorIf 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>"
}
FieldEffect
idRequired. Must match the request id.
htmlIf non-empty, written verbatim after the element's closing tag.
errorIf non-empty, aborts rendering and logs the message.