Documentation
¶
Overview ¶
Package htmlc is a server-side Vue-style component engine for Go. It parses .vue Single File Components — each containing a <template>, an optional <script>, and an optional <style> section — and renders them to HTML strings ready to serve via net/http or any http.Handler-based framework.
[htmlc logo]
[htmlc logo]: ./logo.svg
Problem it solves ¶
Writing server-rendered HTML in Go typically means either concatenating strings, using html/template (which has no component model), or pulling in a full JavaScript runtime. htmlc gives you Vue's familiar component authoring format — scoped styles, template directives, and component composition — while keeping rendering purely in Go with no JavaScript dependency.
Mental model ¶
There are four main concepts:
Engine – the high-level entry point. It owns a Registry of parsed components discovered from a directory tree. Create one with New; call RenderPage or RenderFragment to produce HTML output. ServeComponent wraps a component as an http.Handler for use with net/http.
Component – the parsed representation of one .vue file, produced by ParseFile. Holds the template node tree, script text, style text, and scoped-style metadata.
Renderer – the low-level walker that evaluates a Component's template against a data scope and writes HTML. Most callers should use Engine instead.
StyleCollector – accumulates scoped-style contributions from all components rendered in one request so they can be emitted as a single <style> block at the end.
Template directives ¶
Every directive below is processed server-side. Client-only directives (@click, v-model) are stripped from the output because there is no JavaScript runtime.
{{ expr }}
Mustache text interpolation; the expression result is HTML-escaped.
Example: <p>{{ user.name }}</p>
v-text="expr"
Sets element text content; HTML-escaped; replaces any child nodes.
Example: <p v-text="msg"></p>
v-html="expr"
Sets element inner HTML; the value is NOT HTML-escaped.
Example: <div v-html="rawHTML"></div>
v-if / v-else-if / v-else
Conditional rendering; only the first truthy branch is emitted.
Example: <span v-if="score >= 90">A</span>
<span v-else-if="score >= 70">B</span>
<span v-else>C</span>
v-switch="expr" (on <template>)
Switch-statement conditional rendering. Evaluates the expression once;
the first child with a matching v-case is rendered; v-default renders
when no case matched. Not part of stable Vue.js; implements RFC #482.
Example: <template v-switch="user.role">
<Admin v-case="'admin'" />
<User v-default />
</template>
v-case="expr"
Child of <template v-switch>. Rendered when its expression equals the
parent switch value (Go == comparison).
v-default
Child of <template v-switch>. Rendered when no preceding v-case matched.
v-for="item in items"
Iterate over a slice or array. Use (item, i) in items for zero-based
index access.
Example: <li v-for="(item, i) in items">{{ i }}: {{ item }}</li>
v-for="n in N"
Integer range: n iterates 1 … N (inclusive).
Example: <span v-for="n in 3">{{ n }}</span>
v-for="(val, key) in obj"
Iterate map entries; val is the value, key is the string key.
Example: <dt v-for="(val, key) in obj">{{ key }}: {{ val }}</dt>
:attr="expr"
Dynamic attribute binding. Boolean attributes are omitted when the
expression is falsy, present without a value when truthy.
Example: <a :href="url">link</a>
:class="{ key: bool }"
Object-syntax class binding: keys whose values are truthy are
included; merged with any static class attribute.
Example: <div class="base" :class="{ active: isActive }">…</div>
:class="[...]"
Array-syntax class binding: non-empty string elements are included.
Example: <div :class="['btn', flag ? 'primary' : '']">…</div>
:style="{ camelKey: val }"
Inline style binding; camelCase keys are converted to kebab-case.
Example: <p :style="{ fontSize: '14px', color: 'red' }">…</p>
v-bind="obj"
Attribute spreading: each key-value pair in the map becomes an HTML
attribute on the element. Keys "class" and "style" are merged with
any static and dynamic class/style attributes. Boolean attribute
semantics apply per key. On child components, the map is spread into
the component's prop scope (explicit :prop bindings take precedence
over spread values).
Example: <div v-bind="attrs"></div>
<Button v-bind="buttonProps" :type="'submit'" />
v-show="expr"
Adds style="display:none" when the expression is falsy; the element
is always present in the output.
Example: <p v-show="visible">content</p>
v-pre
Skips all interpolation and directive processing for the subtree;
mustache syntax is emitted literally.
Example: <code v-pre>{{ raw }}</code>
@click, v-model
Client-side event and model directives; stripped on server render.
Example: <button @click="handler">click</button>
Component composition ¶
Components can include other components in their templates. A child component name must start with an uppercase letter to distinguish it from HTML elements.
## Registering components
There are two ways to make components available for composition:
1. Automatic discovery via ComponentDir: every .vue file in the directory tree is registered under its basename (without the .vue extension).
engine, err := htmlc.New(htmlc.Options{ComponentDir: "templates/"})
2. Manual registration via Engine.Register or by constructing a Registry directly and passing it to NewRenderer.WithComponents.
engine.Register("Card", cardComponent)
// Low-level API:
htmlc.NewRenderer(page).WithComponents(htmlc.Registry{"Card": card})
## Default slot
A child component declares <slot /> as a placeholder. The parent places inner HTML inside the component tag and it is injected at the slot site.
Card.vue:
<template><div class="card"><slot /></div></template>
Page.vue:
<template><Card><p>inner</p></Card></template>
Renders to: <div class="card"><p>inner</p></div>
## Named slots
A child can declare multiple slots by name using <slot name="…">. The parent fills each slot with a <template #name> element. Unmatched content goes to the default slot.
Layout.vue:
<template>
<div class="layout">
<slot name="header"></slot>
<main><slot></slot></main>
<slot name="footer"></slot>
</div>
</template>
Page.vue:
<template>
<Layout>
<template #header><h1>Title</h1></template>
<p>Content</p>
<template #footer><em>Footer</em></template>
</Layout>
</template>
## Scoped slots
The child passes data up to the parent via slot props. The parent destructures the props with v-slot="{ item, index }" or binds the whole map with v-slot="props".
List.vue:
<template>
<ul>
<li v-for="(item, i) in items">
<slot :item="item" :index="i"></slot>
</li>
</ul>
</template>
Page.vue (destructuring):
<template>
<List :items="items" v-slot="{ item, index }">
<span>{{ index }}: {{ item }}</span>
</List>
</template>
Page.vue (whole map bound to a variable):
<template>
<Child v-slot="props"><p>{{ props.user.name }}</p></Child>
</template>
## Slot fallback content
Children placed inside a <slot> element in the child component are rendered when the parent provides no content for that slot.
Card.vue:
<template> <div class="card"><slot><p>No content provided</p></slot></div> </template>
Page.vue (no slot content supplied):
<template><Card></Card></template>
Renders to: <div class="card"><p>No content provided</p></div>
## Component resolution
When the renderer encounters a component tag it resolves the name using proximity-based resolution, searching for the nearest matching .vue file relative to the calling component's directory.
Algorithm (applied at each directory level, starting at the caller's dir):
- Exact match — "my-card" matches "my-card.vue"
- Capitalise first letter — "card" matches "Card.vue"
- Kebab to PascalCase — "my-card" matches "MyCard.vue"
- Case-insensitive scan — "CARD" matches "card.vue"
If none of the four strategies finds a match in the current directory, the engine walks one level toward the ComponentDir root and repeats. After exhausting all directories it falls back to the flat registry (required for manually registered components and backward compatibility with single-directory projects).
Example directory tree:
templates/
Card.vue <- generic card used by root templates
blog/
Card.vue <- blog-specific card
PostPage.vue <- <Card> resolves to blog/Card.vue
admin/
Card.vue <- admin-specific card
Dashboard.vue <- <Card> resolves to admin/Card.vue
shop/
ProductPage.vue <- no Card.vue here; walk-up finds Card.vue at root
PostPage.vue and Dashboard.vue both use an unqualified <Card> tag. Because each has a same-named sibling, they resolve independently without any explicit import or path qualifier.
## Explicit cross-directory references
To target a component in a specific directory regardless of the caller's location, use a path-qualified is attribute on <component>:
<!-- always resolves to blog/Card.vue --> <component is="blog/Card" /> <!-- root-relative: always resolves to Card.vue at ComponentDir root --> <component is="/Card" /> <!-- dynamic version --> <component :is="'admin/Card'" />
Path-based references do not apply name-folding and return a render error if the named component is not found.
Proximity resolution is enabled automatically when ComponentDir is set. Manually registered components (via Engine.Register) are available through the flat-registry fallback regardless of directory.
Scope propagation ¶
Every component renders in an isolated scope that contains only the props explicitly passed to it as attributes. Parent scope variables are not inherited; this makes data flow visible and prevents accidental coupling.
Engine-level functions (registered with Engine.RegisterFunc) are the one exception: they are injected into every component's scope at every depth. Use RegisterFunc for helper functions — URL builders, route helpers, formatters — that need to be callable from any component without explicit prop threading.
WithDataMiddleware values are injected into the top-level render scope only. If a child component needs a middleware-supplied value, pass it down as an explicit prop.
// Good: helper functions via RegisterFunc — available everywhere
engine.RegisterFunc("url", buildURL)
engine.RegisterFunc("routeActive", checkActive)
// Good: per-request data via explicit props
// In Page.vue: <Shell :currentUser="currentUser" />
// In Shell.vue: {{ currentUser.Name }}
// Avoid: relying on middleware values inside child components
// — they are not automatically propagated.
Scoped styles ¶
Adding <style scoped> to a .vue file generates a unique data-v-XXXXXXXX attribute (derived from the file path) that is stamped on every element rendered by that component. The CSS is rewritten by ScopeCSS so every selector targets only elements bearing that attribute.
When using Engine, styles are collected automatically:
- RenderPage injects a <style> block immediately before </head>.
- RenderFragment prepends the <style> block to the output.
When using the low-level API, manage styles manually:
sc := &htmlc.StyleCollector{}
out, err := htmlc.NewRenderer(comp).WithStyles(sc).RenderString(nil)
items := sc.All() // []*htmlc.StyleItem; each has a CSS field
Missing prop handling ¶
By default, a prop name that appears in the template but is absent from the scope map causes a render error. Supply a handler to override this behaviour:
// Engine-wide:
engine.WithMissingPropHandler(htmlc.SubstituteMissingProp)
// Per-render with the low-level API:
out, err := htmlc.NewRenderer(comp).
WithMissingPropHandler(htmlc.SubstituteMissingProp).
RenderString(nil)
SubstituteMissingProp is the built-in handler; it emits "MISSING PROP: <name>" in place of the missing value. It is useful during development to surface missing data without aborting the render.
Embedded filesystems ¶
Components can be loaded from an embedded filesystem using go:embed:
//go:embed templates
var templateFS embed.FS
engine, err := htmlc.New(htmlc.Options{
FS: templateFS,
ComponentDir: "templates",
})
Note: Engine.Reload only works when the fs.FS also implements fs.StatFS. The standard os.DirFS satisfies this; embed.FS does not, so Reload is a no-op for embedded filesystems.
Low-level API ¶
Use ParseFile, NewRenderer, WithComponents, WithStyles, and WithDirectives directly when you need request-scoped control over component registration, style collection, or custom directives — for example, in tests or one-off renders outside of a long-lived Engine.
// Parse two components from strings.
card, _ := htmlc.ParseFile("Card.vue", `<template><div class="card"><slot /></div></template>`)
page, _ := htmlc.ParseFile("Page.vue", `<template><Card><p>inner</p></Card></template>`)
// Collect scoped styles while rendering.
sc := &htmlc.StyleCollector{}
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Card": card}).
WithStyles(sc).
RenderString(nil)
if err != nil { /* handle */ }
// Retrieve scoped CSS generated during the render.
for _, item := range sc.All() {
fmt.Println(item.CSS)
}
Custom directives ¶
htmlc supports a custom directive system inspired by Vue's custom directives (https://vuejs.org/guide/reusability/custom-directives). Types that implement the Directive interface can be registered under a v-name attribute and are invoked during server-side rendering.
Only the Created and Mounted hooks are supported because htmlc renders server-side only — there are no DOM updates or browser events.
- Created – called before the element is rendered; may mutate the element's tag (node.Data) and attributes (node.Attr).
- Mounted – called after the element's closing tag has been written; may write additional HTML to the output writer.
Register custom directives via Options.Directives or Engine.RegisterDirective:
engine, err := htmlc.New(htmlc.Options{
ComponentDir: "templates/",
Directives: htmlc.DirectiveRegistry{
"my-dir": &MyDirective{},
},
})
The built-in VHighlight directive is the canonical example: it sets the background colour of the host element to the CSS colour string supplied by the directive's expression — mirroring the v-highlight example from the Vue.js custom directives guide. VHighlight is not pre-registered; to use it, add it via Options.Directives:
engine, err := htmlc.New(htmlc.Options{
ComponentDir: "templates/",
Directives: htmlc.DirectiveRegistry{
"highlight": &htmlc.VHighlight{},
},
})
DirectiveWithContent ¶
A directive that wants to replace the element's children with custom HTML may implement the optional DirectiveWithContent interface in addition to Directive. After Created is called the renderer checks whether the directive implements DirectiveWithContent and, if InnerHTML() returns a non-empty string, writes it verbatim between the opening and closing tags instead of rendering the template children.
External Directives ¶
htmlc build discovers external directives automatically from the component tree. Any executable file whose base name (without extension) matches v-<name> (lower-kebab-case) is registered as an external directive under that name. The executable communicates with htmlc over newline-delimited JSON on stdin/stdout, receiving a request envelope for each hook invocation and responding with a result envelope. See the README for the full protocol description.
Tutorial ¶
The fastest path to a working server is Engine + RenderPage:
engine, err := htmlc.New(htmlc.Options{ComponentDir: "templates/"})
if err != nil { /* handle */ }
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := engine.RenderPage(w, "Page", map[string]any{"title": "Home"}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
Use RenderPage when the component template is a full HTML document (html/head/body); it injects collected <style> blocks before </head>. Use RenderFragment (or RenderFragmentString) for partial HTML snippets — for example, components rendered inside an existing layout or delivered over HTMX.
For development, enable hot-reload so changes to .vue files are picked up without restarting the server:
engine, err := htmlc.New(htmlc.Options{
ComponentDir: "templates/",
Reload: true,
})
Runtime introspection with expvar ¶
An Engine can publish its configuration and performance counters to the global expvar registry, making them accessible at /debug/vars (served automatically when net/http/pprof or expvar is imported). Call PublishExpvars with a unique prefix after constructing the engine:
engine, err := htmlc.New(htmlc.Options{ComponentDir: "templates/"})
if err != nil { /* handle */ }
engine.PublishExpvars("htmlc")
// Visiting http://localhost:8080/debug/vars now shows, under "htmlc":
// "reload": 0, "debug": 0, "renders": 42, "renderNanos": 1234567, …
Published variables:
reload – 1 if hot-reload is enabled, 0 otherwise debug – 1 if debug mode is enabled, 0 otherwise componentDir – the active component directory fs – the type name of the active fs.FS, or "<nil>" renders – total renderComponent calls (includes errors) renderErrors – total failed renders reloads – total hot-reload re-scans performed renderNanos – cumulative render time in nanoseconds components – number of unique registered components info.directives – sorted list of registered custom directive names
Two engines in the same process must use different prefixes:
adminEngine.PublishExpvars("htmlc/admin")
publicEngine.PublishExpvars("htmlc/public")
Runtime option mutation ¶
Reload and Debug can be toggled at runtime without restarting the server:
engine.SetReload(true) // enable hot-reload engine.SetDebug(false) // disable debug mode
The component directory and filesystem can be changed atomically; discovery is re-run under the engine's write lock and the engine's state is only updated on success:
if err := engine.SetComponentDir("templates/v2"); err != nil {
log.Printf("component dir change failed: %v", err)
}
if err := engine.SetFS(newFS); err != nil {
log.Printf("fs change failed: %v", err)
}
Package htmlc implements bidirectional conversion between htmlc .vue components and Go's standard html/template format.
vue→tmpl direction ¶
VueToTemplate converts a parsed *htmlc.Component to a string containing one or more {{define "name"}}…{{end}} blocks, suitable for parsing with html/template.New("").Parse(result).
Supported constructs:
- Text interpolation: {{ ident }} and {{ a.b.c }}
- Bound attributes: :attr="ident" and v-bind:attr="ident"
- v-if / v-else-if / v-else conditional chains
- v-for="item in list" loops (outer-scope refs produce ConversionError)
- v-show="ident" injects conditional style="display:none"
- v-html="ident" emits {{.ident}} with a warning (caller handles HTML safety)
- v-text="ident" emits {{.ident}}, discarding children
- v-bind="ident" spread emits {{ .ident }} with a warning
- <template v-switch="ident"> with v-case / v-default children
- <slot> and <slot name="N"> emit {{block}} blocks
- Zero-prop child components emit {{template "name" .}}
Unsupported constructs (return ConversionError):
- Complex expressions (anything beyond simple identifiers and dot-paths)
- Bound props on child components
- Custom directives
- Outer-scope variable references inside v-for loops
tmpl→vue direction ¶
TemplateToVue converts html/template source text to .vue component source. This direction is explicitly best-effort; see TemplateToVue for details.
Example ¶
Example demonstrates end-to-end use of the htmlc engine: create an Engine from a directory of .vue files, then render a component as an HTML fragment.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/dhamidi/htmlc"
)
func main() {
dir, err := os.MkdirTemp("", "htmlc-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
// Write a simple component with no scoped styles so the output is stable.
vue := `<template><p>Hello, {{ name }}!</p></template>`
if err := os.WriteFile(filepath.Join(dir, "Greeting.vue"), []byte(vue), 0644); err != nil {
log.Fatal(err)
}
engine, err := htmlc.New(htmlc.Options{ComponentDir: dir})
if err != nil {
log.Fatal(err)
}
out, err := engine.RenderFragmentString("Greeting", map[string]any{"name": "World"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p>Hello, World!</p>
Index ¶
- Constants
- Variables
- func DotPrefix(expr string) (string, error)
- func ErrorOnMissingProp(name string) (any, error)
- func Render(w io.Writer, c *Component, scope map[string]any) error
- func RenderString(c *Component, scope map[string]any) (string, error)
- func ScopeCSS(css, scopeAttr string) string
- func ScopeID(path string) string
- func Snippet(src string, line int) string
- func SubstituteMissingProp(name string) (any, error)
- type Component
- type ComponentErrorHandler
- type ConversionError
- type Directive
- type DirectiveBinding
- type DirectiveContext
- type DirectiveRegistry
- type DirectiveWithContent
- type Engine
- func (e *Engine) CompileToTemplate(componentName string) (*htmltmpl.Template, error)
- func (e *Engine) Components() []string
- func (e *Engine) Has(name string) bool
- func (e *Engine) Mount(mux *http.ServeMux, routes map[string]string)
- func (e *Engine) PublishExpvars(prefix string) *Engine
- func (e *Engine) Register(name, path string) error
- func (e *Engine) RegisterDirective(name string, dir Directive)
- func (e *Engine) RegisterFunc(name string, fn func(...any) (any, error)) *Engine
- func (e *Engine) RegisterTemplate(name string, tmpl *htmltmpl.Template) error
- func (e *Engine) RenderFragment(w io.Writer, name string, data map[string]any) error
- func (e *Engine) RenderFragmentContext(ctx context.Context, w io.Writer, name string, data map[string]any) error
- func (e *Engine) RenderFragmentString(name string, data map[string]any) (string, error)
- func (e *Engine) RenderPage(w io.Writer, name string, data map[string]any) error
- func (e *Engine) RenderPageContext(ctx context.Context, w io.Writer, name string, data map[string]any) error
- func (e *Engine) RenderPageString(name string, data map[string]any) (string, error)
- func (e *Engine) ServeComponent(name string, data func(*http.Request) map[string]any) http.HandlerFunc
- func (e *Engine) ServePageComponent(name string, data func(*http.Request) (map[string]any, int)) http.HandlerFunc
- func (e *Engine) SetComponentDir(dir string) error
- func (e *Engine) SetDebug(enabled bool)
- func (e *Engine) SetFS(fsys fs.FS) error
- func (e *Engine) SetReload(enabled bool)
- func (e *Engine) TemplateText(componentName string) (text string, warnings []string, err error)
- func (e *Engine) ValidateAll() []ValidationError
- func (e *Engine) WithDataMiddleware(fn func(*http.Request, map[string]any) map[string]any) *Engine
- func (e *Engine) WithMissingPropHandler(fn MissingPropFunc) *Engine
- type ExprKind
- type MapProps
- type MissingPropFunc
- type Options
- type ParseError
- type PropInfo
- type Props
- type Registry
- type RenderError
- type Renderer
- func (r *Renderer) Render(w io.Writer, scope map[string]any) error
- func (r *Renderer) RenderString(scope map[string]any) (string, error)
- func (r *Renderer) WithComponentErrorHandler(h ComponentErrorHandler) *Renderer
- func (r *Renderer) WithComponentPath(path []string) *Renderer
- func (r *Renderer) WithComponents(reg Registry) *Renderer
- func (r *Renderer) WithContext(ctx context.Context) *Renderer
- func (r *Renderer) WithDirectives(dr DirectiveRegistry) *Renderer
- func (r *Renderer) WithFuncs(funcs map[string]any) *Renderer
- func (r *Renderer) WithLogger(l *slog.Logger) *Renderer
- func (r *Renderer) WithMissingPropHandler(fn MissingPropFunc) *Renderer
- func (r *Renderer) WithNSComponents(ns map[string]map[string]*Component, componentDir string) *Renderer
- func (r *Renderer) WithStyles(sc *StyleCollector) *Renderer
- type SlotDefinition
- type SourceLocation
- type StructProps
- type StyleCollector
- type StyleContribution
- type TemplateToVueResult
- type VHighlight
- type ValidationError
- type VueToTemplateResult
Examples ¶
- Package
- Component.Props
- Engine (ScopedStyles)
- Engine.CompileToTemplate
- Engine.RegisterTemplate
- Engine.RenderPage
- Engine.ServeComponent
- ParseFile
- Render (ComponentProps)
- Render (ComponentSlot)
- Render (EventPassthrough)
- Render (Interpolation)
- Render (NamedSlots)
- Render (ScopedSlots)
- Render (SingleVariableSlotBinding)
- Render (SlotFallbackContent)
- Render (VBind)
- Render (VBindClass)
- Render (VBindStyle)
- Render (VFor)
- Render (VForObject)
- Render (VHtml)
- Render (VIf)
- Render (VPre)
- Render (VShow)
- Render (VText)
- Renderer.WithMissingPropHandler
Constants ¶
const MsgComponentFailed = "component render failed"
MsgComponentFailed is the slog message emitted at LevelError when a component render fails.
const MsgComponentRendered = "component rendered"
MsgComponentRendered is the slog message emitted at LevelDebug when a component renders successfully.
Variables ¶
var ( // ErrComponentNotFound is returned when the requested component name is not // registered in the engine. ErrComponentNotFound = errors.New("htmlc: component not found") // ErrMissingProp is returned when a required prop is absent from the render // scope and no MissingPropFunc has been set. ErrMissingProp = errors.New("htmlc: missing required prop") // ErrConversion is returned (wrapped) when an html/template conversion fails. // Callers can use errors.As to extract the underlying *ConversionError: // // var cerr *ConversionError // if errors.As(err, &cerr) { // fmt.Println(cerr.Location) // } ErrConversion = errors.New("htmlc: conversion failed") )
Sentinel errors returned by Engine methods.
Functions ¶
func DotPrefix ¶ added in v0.7.0
DotPrefix converts an htmlc expression to a Go template dot-accessor.
"name" → ".name" "a.b.c" → ".a.b.c" "." → "."
Returns an error for ExprComplex inputs.
func ErrorOnMissingProp ¶ added in v0.2.0
ErrorOnMissingProp is a MissingPropFunc that aborts rendering with an error whenever a prop is missing. Use it to restore strict validation:
renderer.WithMissingPropHandler(htmlc.ErrorOnMissingProp)
func Render ¶
Render is a convenience wrapper that creates a temporary Renderer for c and writes the rendered HTML directly to w. It does not collect styles or support component composition; use NewRenderer with WithStyles and WithComponents for those features.
Example (ComponentProps) ¶
ExampleRender_componentProps shows a parent passing a dynamic prop via :title="expr" and a static string prop via class="x" to a child component.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
header, _ := htmlc.ParseFile("Header.vue", `<template><h1>{{ title }}</h1></template>`)
page, _ := htmlc.ParseFile("Page.vue", `<template><Header :title="heading" class="main"></Header></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Header": header}).
RenderString(map[string]any{"heading": "My Site"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <h1>My Site</h1>
Example (ComponentSlot) ¶
ExampleRender_componentSlot shows a parent component passing inner HTML content into a child component's <slot /> placeholder.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
card, _ := htmlc.ParseFile("Card.vue", `<template><div class="card"><slot /></div></template>`)
page, _ := htmlc.ParseFile("Page.vue", `<template><Card><p>inner</p></Card></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Card": card}).
RenderString(nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <div class="card"><p>inner</p></div>
Example (EventPassthrough) ¶
ExampleRender_eventPassthrough shows that client-side directives such as @click and v-model are stripped from server-rendered output because there is no client-side Vue runtime to process them.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><button @click="handler">click</button><input v-model="name"></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <button>click</button><input>
Example (Interpolation) ¶
ExampleRender_interpolation shows {{ expr }} text interpolation: member access, arithmetic, and ternary expressions all evaluated at render time.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><p>{{ user.name }}, total {{ price * qty }}, active: {{ active ? "yes" : "no" }}</p></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, map[string]any{
"user": map[string]any{"name": "Alice"},
"price": float64(10),
"qty": float64(3),
"active": true,
})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p>Alice, total 30, active: yes</p>
Example (NamedSlots) ¶
ExampleRender_namedSlots demonstrates named slots: a Layout component declares header and footer named slots plus a default slot for body content, and the caller fills each slot with a <template #name> element.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
layout, _ := htmlc.ParseFile("Layout.vue",
`<template><div class="layout"><slot name="header"></slot><main><slot></slot></main><slot name="footer"></slot></div></template>`)
page, _ := htmlc.ParseFile("Page.vue",
`<template><Layout><template #header><h1>Title</h1></template><p>Content</p><template #footer><em>Footer</em></template></Layout></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Layout": layout}).
RenderString(nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <div class="layout"><h1>Title</h1><main><p>Content</p></main><em>Footer</em></div>
Example (ScopedSlots) ¶
ExampleRender_scopedSlots demonstrates scoped slots: a List component passes each item and its index to the caller via slot props, and the caller uses v-slot="{ item, index }" to render a custom item template.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
list, _ := htmlc.ParseFile("List.vue",
`<template><ul><li v-for="(item, i) in items"><slot :item="item" :index="i"></slot></li></ul></template>`)
page, _ := htmlc.ParseFile("Page.vue",
`<template><List :items="items" v-slot="{ item, index }"><span>{{ index }}: {{ item }}</span></List></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"List": list}).
RenderString(map[string]any{"items": []any{"a", "b"}})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <ul><li><span>0: a</span></li><li><span>1: b</span></li></ul>
Example (SingleVariableSlotBinding) ¶
ExampleRender_singleVariableSlotBinding demonstrates v-slot="slotProps" binding: the entire slot props map is bound to a single variable, allowing the caller to access any prop via slotProps.key.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
child, _ := htmlc.ParseFile("Child.vue",
`<template><div><slot :user="theuser" :count="total"></slot></div></template>`)
page, _ := htmlc.ParseFile("Page.vue",
`<template><Child :theuser="u" :total="n" v-slot="props"><p>{{ props.user.name }}: {{ props.count }}</p></Child></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Child": child}).
RenderString(map[string]any{
"u": map[string]any{"name": "Alice"},
"n": float64(3),
})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <div><p>Alice: 3</p></div>
Example (SlotFallbackContent) ¶
ExampleRender_slotFallbackContent demonstrates slot fallback content: when the caller provides no content for a slot, the child component's fallback children inside <slot>…</slot> are rendered instead.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
card, _ := htmlc.ParseFile("Card.vue",
`<template><div class="card"><slot><p>No content provided</p></slot></div></template>`)
page, _ := htmlc.ParseFile("Page.vue", `<template><Card></Card></template>`)
out, err := htmlc.NewRenderer(page).
WithComponents(htmlc.Registry{"Card": card}).
RenderString(nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <div class="card"><p>No content provided</p></div>
Example (VBind) ¶
ExampleRender_vBind shows :attr dynamic attribute binding, and how boolean attributes are handled: :disabled="false" omits the attribute entirely while :disabled="true" emits it without a value.
package main
import (
"fmt"
"github.com/dhamidi/htmlc"
)
func main() {
// Dynamic href binding.
c1, _ := htmlc.ParseFile("t.vue", `<template><a :href="url">link</a></template>`)
o1, _ := htmlc.RenderString(c1, map[string]any{"url": "https://example.com"})
fmt.Println(o1)
// :disabled="false" — attribute is omitted.
c2, _ := htmlc.ParseFile("t.vue", `<template><button :disabled="false">enabled</button></template>`)
o2, _ := htmlc.RenderString(c2, nil)
fmt.Println(o2)
// :disabled="true" — attribute is present without a value.
c3, _ := htmlc.ParseFile("t.vue", `<template><button :disabled="true">disabled</button></template>`)
o3, _ := htmlc.RenderString(c3, nil)
fmt.Println(o3)
}
Output: <a href="https://example.com">link</a> <button>enabled</button> <button disabled>disabled</button>
Example (VBindClass) ¶
ExampleRender_vBindClass shows :class with object syntax (keys whose values are truthy are included) and array syntax, both merged with a static class.
package main
import (
"fmt"
"github.com/dhamidi/htmlc"
)
func main() {
// Object syntax merged with static class="base".
c1, _ := htmlc.ParseFile("t.vue", `<template><div class="base" :class="{ active: isActive, hidden: isHidden }">x</div></template>`)
o1, _ := htmlc.RenderString(c1, map[string]any{"isActive": true, "isHidden": false})
fmt.Println(o1)
// Array syntax: non-empty strings are included.
c2, _ := htmlc.ParseFile("t.vue", `<template><div :class="['btn', flag ? 'primary' : '']">y</div></template>`)
o2, _ := htmlc.RenderString(c2, map[string]any{"flag": true})
fmt.Println(o2)
}
Output: <div class="base active">x</div> <div class="btn primary">y</div>
Example (VBindStyle) ¶
ExampleRender_vBindStyle shows :style with an object whose camelCase keys are automatically converted to kebab-case CSS property names.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><p :style="{ color: 'red', fontSize: '14px' }">styled</p></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p style="color:red;font-size:14px">styled</p>
Example (VFor) ¶
ExampleRender_vFor shows v-for iterating over an array, with (item, index) destructuring, and over an integer range (n in N produces 1..N).
package main
import (
"fmt"
"github.com/dhamidi/htmlc"
)
func main() {
// Plain array.
c1, _ := htmlc.ParseFile("t.vue", `<template><li v-for="item in items">{{ item }}</li></template>`)
o1, _ := htmlc.RenderString(c1, map[string]any{"items": []any{"a", "b", "c"}})
fmt.Println(o1)
// Array with zero-based index.
c2, _ := htmlc.ParseFile("t.vue", `<template><li v-for="(item, i) in items">{{ i }}:{{ item }}</li></template>`)
o2, _ := htmlc.RenderString(c2, map[string]any{"items": []any{"x", "y"}})
fmt.Println(o2)
// Integer range: n iterates 1, 2, 3.
c3, _ := htmlc.ParseFile("t.vue", `<template><span v-for="n in 3">{{ n }}</span></template>`)
o3, _ := htmlc.RenderString(c3, nil)
fmt.Println(o3)
}
Output: <li>a</li><li>b</li><li>c</li> <li>0:x</li><li>1:y</li> <span>1</span><span>2</span><span>3</span>
Example (VForObject) ¶
ExampleRender_vForObject shows v-for iterating map entries using the (value, key) destructuring form.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><dt v-for="(value, key) in obj">{{ key }}: {{ value }}</dt></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
// Use a single-entry map so iteration order is deterministic.
out, err := htmlc.RenderString(comp, map[string]any{
"obj": map[string]any{"lang": "Go"},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <dt>lang: Go</dt>
Example (VHtml) ¶
ExampleRender_vHtml shows v-html="expr" which renders a raw HTML string as element content without escaping angle brackets or other HTML characters.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><div v-html="raw"></div></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, map[string]any{"raw": "<b>bold</b>"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <div><b>bold</b></div>
Example (VIf) ¶
ExampleRender_vIf shows v-if/v-else-if/v-else conditional rendering: only the first truthy branch produces output; the rest are skipped entirely.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><span v-if="score >= 90">A</span><span v-else-if="score >= 70">B</span><span v-else>C</span></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, map[string]any{"score": float64(75)})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <span>B</span>
Example (VPre) ¶
ExampleRender_vPre shows v-pre: mustache syntax inside the element is emitted literally without any interpolation or directive processing.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><code v-pre>{{ raw }}</code></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, map[string]any{"raw": "ignored"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <code>{{ raw }}</code>
Example (VShow) ¶
ExampleRender_vShow shows v-show: a falsy expression injects style="display:none" while the element is still present in the DOM; a truthy expression renders the element normally.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><p v-show="false">hidden</p><p v-show="true">visible</p></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p style="display:none">hidden</p><p>visible</p>
Example (VText) ¶
ExampleRender_vText shows v-text="expr" which sets element text content with HTML escaping, replacing any child nodes.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
const src = `<template><p v-text="msg"></p></template>`
comp, err := htmlc.ParseFile("t.vue", src)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.RenderString(comp, map[string]any{"msg": "Hello & World"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p>Hello & World</p>
func RenderString ¶
RenderString is a convenience wrapper that creates a temporary Renderer for c and renders it against scope, returning the result as a string. It does not collect styles or support component composition; use NewRenderer with WithStyles and WithComponents for those features.
func ScopeCSS ¶
ScopeCSS rewrites the CSS text so that every selector in every non-@-rule has scopeAttr appended to its last compound selector. scopeAttr should be a full attribute selector string, e.g. "[data-v-a1b2c3d4]".
@-rules (such as @media or @keyframes) are passed through verbatim, including their nested blocks.
func ScopeID ¶
ScopeID computes the scope attribute name for a component at the given file path. The result is "data-v-" followed by the 8 lower-case hex digits of the FNV-1a 32-bit hash of path.
func Snippet ¶ added in v0.7.0
Snippet returns a ≈3-line context window around line (1-based) in src, with a ">" marker on the target line — matches existing htmlc error style.
func SubstituteMissingProp ¶
SubstituteMissingProp returns a placeholder string "MISSING PROP: <name>" for any missing prop.
Types ¶
type Component ¶
type Component struct {
// Template is the root of the parsed HTML node tree for the <template> section.
Template *html.Node
// Script is the raw text content of the <script> section (empty if absent).
Script string
// Style is the raw text content of the <style> section (empty if absent).
Style string
// Scoped reports whether the <style> tag carried the scoped attribute.
Scoped bool
// Path is the source file path passed to ParseFile.
Path string
// Source is the raw source text of the file, stored for location-aware error reporting.
Source string
// Warnings holds non-fatal issues found during parsing, such as self-closing
// custom component tags that were automatically rewritten.
Warnings []string
}
Component holds the parsed representation of a .vue Single File Component.
func ParseFile ¶
ParseFile parses a .vue Single File Component from src and returns a Component. Only the top-level <template>, <script>, and <style> sections are extracted. <script> and <style> are optional; <template> is required. The template HTML is parsed into a node tree accessible via Component.Template.
Example ¶
ExampleParseFile parses an inline .vue source string and inspects the resulting Component's path.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
comp, err := htmlc.ParseFile("Greeting.vue", `<template><p>Hello!</p></template>`)
if err != nil {
log.Fatal(err)
}
fmt.Println(comp.Path)
}
Output: Greeting.vue
func (*Component) Props ¶
Props walks the component's parsed template AST and returns all top-level variable references (props) that the template uses.
Identifiers starting with '$' are excluded. v-for loop variables are excluded within their subtree.
Example ¶
ExampleComponent_Props shows how to call Props() to discover the prop names a component template uses and the expressions in which they appear.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
comp, err := htmlc.ParseFile("t.vue", `<template><p>{{ message }}</p></template>`)
if err != nil {
log.Fatal(err)
}
props := comp.Props()
fmt.Println(len(props))
fmt.Println(props[0].Name)
fmt.Println(props[0].Expressions[0])
}
Output: 1 message message
type ComponentErrorHandler ¶ added in v0.8.0
ComponentErrorHandler is called when a child component fails to render. w is the writer at the failure site; path is the full component path from the page root to the failing component. err is the render error.
Return nil to write a placeholder and continue rendering sibling nodes. Return a non-nil error to abort the entire render immediately.
func HTMLErrorHandler ¶ added in v0.8.0
func HTMLErrorHandler() ComponentErrorHandler
HTMLErrorHandler returns a ComponentErrorHandler that renders a visible <div> placeholder for each failed component. It is intended for development use. The generated markup uses the class "htmlc-error" for easy targeting with CSS. path and err are HTML-escaped before inclusion.
type ConversionError ¶ added in v0.7.0
type ConversionError struct {
Component string // component name, e.g. "PostPage"
Directive string // directive name, e.g. "v-if" (may be empty)
Message string // human-readable cause
Location *SourceLocation // source position; may be nil
Cause error // underlying error; may be nil
}
ConversionError is returned when a .vue→tmpl or tmpl→.vue conversion encounters an unsupported construct. It is always returned wrapped together with ErrConversion so callers can detect it with either errors.Is or errors.As:
var cerr *htmlc.ConversionError
if errors.As(err, &cerr) {
log.Printf("conversion failed at %s:%d: %s", cerr.Location.File, cerr.Location.Line, cerr.Message)
}
Conversion halts on the first unsupported construct; no partial output is produced.
func (*ConversionError) Error ¶ added in v0.7.0
func (e *ConversionError) Error() string
func (*ConversionError) Unwrap ¶ added in v0.7.0
func (e *ConversionError) Unwrap() error
type Directive ¶
type Directive interface {
// Created is called before the element is rendered. The hook receives a
// shallow-cloned working node whose Attr slice and Data field may be
// freely mutated; mutations affect what the renderer emits for this
// element but do not modify the shared parsed template.
//
// Common uses:
// - Add, remove, or rewrite attributes (node.Attr).
// - Change the element tag (node.Data) to redirect rendering to a
// different component.
// - Return a non-nil error to abort rendering of this element.
Created(node *html.Node, binding DirectiveBinding, ctx DirectiveContext) error
// Mounted is called after the element's closing tag has been written to w.
// The hook may write additional HTML after the element.
//
// w is the same writer the renderer uses; bytes written here appear
// immediately after the element in the output stream.
//
// Return a non-nil error to abort rendering.
Mounted(w io.Writer, node *html.Node, binding DirectiveBinding, ctx DirectiveContext) error
}
Directive is the interface implemented by custom directive types.
Register a custom directive with Engine.RegisterDirective or Renderer.WithDirectives. In a template, reference it as v-<name>:
<div v-my-directive="someExpr">…</div>
Only the Created and Mounted hooks are called because htmlc renders server- side. There are no DOM updates, component unmounting, or browser events.
type DirectiveBinding ¶
type DirectiveBinding struct {
// Value is the result of evaluating the directive expression against the
// current scope. For example, v-switch="item.type" yields item.type's value.
Value any
// RawExpr is the un-evaluated expression string from the template attribute.
RawExpr string
// Arg is the directive argument after the colon, e.g. "href" in v-bind:href.
// Empty string when no argument is present.
Arg string
// Modifiers is the set of dot-separated modifiers, e.g. {"prevent": true}
// from v-on:click.prevent. Empty map when no modifiers are present.
Modifiers map[string]bool
}
DirectiveBinding holds the evaluated binding for a custom directive invocation.
type DirectiveContext ¶
type DirectiveContext struct {
// Registry is the component registry the renderer is using. Directives
// can use this to verify or resolve component names.
Registry Registry
// RenderedChildHTML is the fully rendered inner HTML of the directive's
// host element, with all template expressions evaluated and child
// components expanded. It is empty for void elements.
// Available in both Created and Mounted hooks.
RenderedChildHTML string
}
DirectiveContext provides directive hooks read-only access to renderer state.
type DirectiveRegistry ¶
DirectiveRegistry maps directive names (without the "v-" prefix) to their implementations. Keys are lower-kebab-case; the renderer normalises names before lookup.
type DirectiveWithContent ¶ added in v0.5.0
type DirectiveWithContent interface {
Directive
// InnerHTML returns the raw HTML to use as the element's inner content.
// Return ("", false) to fall back to normal child rendering.
InnerHTML() (html string, ok bool)
}
DirectiveWithContent is an optional extension of the Directive interface. When a directive's Created hook wants to replace the element's children with custom HTML it should implement this interface.
The renderer checks for this interface after calling Created. If InnerHTML returns a non-empty string the element's template children are skipped and the string is written verbatim between the opening and closing tags (equivalent to v-html on the element itself).
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the entry point for rendering .vue components. Create one with New; call RenderPage or RenderFragment to produce HTML. ServeComponent wraps a component as a net/http handler so it can be mounted directly in an http.Handler-based server.
Engine is safe for concurrent use. All render methods may be called from multiple goroutines simultaneously.
Example (ScopedStyles) ¶
ExampleEngine_scopedStyles shows that a component with <style scoped> adds a unique data attribute to each HTML element and scopes its CSS selectors to match only elements within that component.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
// ParseFile with a fixed path gives a deterministic scope ID.
const path = "Button.vue"
comp, err := htmlc.ParseFile(path, `<template><p>hello</p></template><style scoped>p{color:red}</style>`)
if err != nil {
log.Fatal(err)
}
sc := &htmlc.StyleCollector{}
out, err := htmlc.NewRenderer(comp).WithStyles(sc).RenderString(nil)
if err != nil {
log.Fatal(err)
}
items := sc.All()
fmt.Println(out)
fmt.Println(items[0].CSS)
}
Output: <p data-v-6fc690bb>hello</p> p[data-v-6fc690bb]{color:red}
func New ¶
New creates an Engine configured by opts. If opts.ComponentDir is set the directory is walked recursively and all *.vue files are registered.
func (*Engine) CompileToTemplate ¶ added in v0.7.0
CompileToTemplate compiles the named component (and all components it statically references) into a single *html/template.Template.
The root component becomes the primary named template; all sub-components are added as named {{ define }} blocks in the same template set. Template names follow Go convention: the component name is lowercased (e.g. "Card" → "card").
Scoped <style> blocks are stripped from the output. Non-recoverable conversion errors (unsupported directives, complex expressions) are returned as *ConversionError with source location information, wrapped together with ErrConversion so callers can test with either errors.Is or errors.As.
The returned *html/template.Template is safe to call with Execute or ExecuteTemplate for any data value compatible with the component's props.
Example ¶
ExampleEngine_CompileToTemplate demonstrates compiling a .vue component to a *html/template.Template and executing it with Go template data.
The component name is lowercased to form the template name: "Hello" → "hello". Sub-components referenced by the root are included as named {{ define }} blocks in the same template set.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/dhamidi/htmlc"
)
func main() {
dir, err := os.MkdirTemp("", "htmlc-ex-compile-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
// A .vue component with a simple text interpolation.
vue := `<template><p>{{ message }}</p></template>`
if err := os.WriteFile(filepath.Join(dir, "Hello.vue"), []byte(vue), 0644); err != nil {
log.Fatal(err)
}
engine, err := htmlc.New(htmlc.Options{ComponentDir: dir})
if err != nil {
log.Fatal(err)
}
// CompileToTemplate converts the .vue component to a *html/template.Template.
tmpl, err := engine.CompileToTemplate("Hello")
if err != nil {
log.Fatal(err)
}
var buf strings.Builder
if err := tmpl.Execute(&buf, map[string]any{"message": "Hello"}); err != nil {
log.Fatal(err)
}
fmt.Println(buf.String())
}
Output: <p>Hello</p>
func (*Engine) Components ¶
Components returns the names of all registered components in sorted order. Lowercase aliases added automatically by the engine are excluded.
func (*Engine) Mount ¶
Mount registers a set of component routes on mux. The routes map keys are patterns accepted by http.ServeMux (e.g. "GET /{$}", "GET /about"), and values are component names. Each component is served as a full HTML page via ServePageComponent with no data function (use WithDataMiddleware to inject common data, or register routes manually for per-route data).
func (*Engine) PublishExpvars ¶ added in v0.7.0
PublishExpvars registers the engine's configuration and performance counters in the global expvar registry under the given prefix. The prefix must be unique across all engines in the process; calling PublishExpvars with a prefix that is already registered panics (same as expvar.NewMap).
After calling PublishExpvars, the engine's vars are accessible at /debug/vars under the key prefix, and the following sub-keys are available:
reload – 1 if hot-reload is enabled, 0 otherwise debug – 1 if debug mode is enabled, 0 otherwise componentDir – the current component directory fs – the type name of the current fs.FS, or "<nil>" renders – total number of renderComponent calls renderErrors – total number of failed renders reloads – total number of hot-reload re-scans performed renderNanos – cumulative render time in nanoseconds components – number of unique registered components info.directives – sorted list of registered custom directive names
PublishExpvars returns the Engine so calls can be chained.
func (*Engine) Register ¶
Register manually adds a component from path to the engine's registry under name, without requiring a directory scan. This is useful when components are generated programmatically or loaded from locations outside ComponentDir.
func (*Engine) RegisterDirective ¶
RegisterDirective adds a custom directive to the engine under the given name (without the "v-" prefix). It replaces any previously registered directive with the same name. Panics if dir is nil.
func (*Engine) RegisterFunc ¶
RegisterFunc adds a per-engine function available in all template expressions rendered by this engine. The function can be called from templates as name(). Engine-level functions act as lower-priority builtins: the render data scope takes precedence over them, which in turn takes precedence over the global expr.RegisterBuiltin table.
Functions registered here are propagated automatically into every child component's scope, so they are available at any nesting depth without being threaded through as explicit props.
RegisterFunc returns the Engine so calls can be chained.
func (*Engine) RegisterTemplate ¶ added in v0.7.0
RegisterTemplate registers an existing *html/template.Template as a virtual htmlc component under name. The template is converted to htmlc's internal representation using the tmpl→vue conversion.
All named {{ define }} blocks within tmpl are also registered as components accessible by their block names.
If conversion fails (unsupported template constructs), RegisterTemplate returns a *ConversionError wrapped with ErrConversion and does not register anything.
RegisterTemplate validates the template at registration time (fail-fast behaviour); it does not defer validation to render time.
When called with a name already in use, the new registration wins ("last write wins"), consistent with flat-registry behaviour.
Example ¶
ExampleEngine_RegisterTemplate demonstrates registering an existing *html/template.Template as an htmlc component so it can be used inside .vue component trees.
After registration, the component name resolves normally inside any .vue file: <site-header /> renders the registered template's output.
package main
import (
"fmt"
htmltemplate "html/template"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
engine, err := htmlc.New(htmlc.Options{})
if err != nil {
log.Fatal(err)
}
// An existing html/template from a legacy codebase.
headerTmpl := htmltemplate.Must(
htmltemplate.New("site-header").Parse(`<header>Site Title</header>`),
)
// Register it as an htmlc component. All named {{ define }} blocks within
// headerTmpl are also registered under their own names.
if err := engine.RegisterTemplate("site-header", headerTmpl); err != nil {
log.Fatal(err)
}
fmt.Println(engine.Has("site-header"))
}
Output: true
func (*Engine) RenderFragment ¶
RenderFragment renders name as an HTML fragment, writing the result to w, and prepends the collected <style> block to the output. Unlike RenderPage, it does not search for a </head> tag — it simply places the styles before the HTML, making it suitable for partial page updates (e.g. HTMX responses, turbo frames, or any context where a complete HTML document structure is not present).
For full HTML documents that include a <head> section, use RenderPage instead so that styles are injected in the document head.
func (*Engine) RenderFragmentContext ¶
func (e *Engine) RenderFragmentContext(ctx context.Context, w io.Writer, name string, data map[string]any) error
RenderFragmentContext is like RenderFragment but accepts a context.Context. The render is aborted and ctx.Err() is returned if the context is cancelled or its deadline is exceeded during rendering.
func (*Engine) RenderFragmentString ¶
RenderFragmentString renders name as an HTML fragment and returns the result as a string. It is a convenience wrapper around RenderFragment for callers that need a string rather than writing to an io.Writer.
func (*Engine) RenderPage ¶
RenderPage renders name as a full HTML page, writing the result to w. It collects all scoped styles from the component tree and inserts them as a <style> block immediately before the first </head> tag, keeping styles in the document head where browsers expect them. If the output contains no </head> the style block is prepended to the output instead.
Use RenderPage when rendering a complete HTML document (e.g. a page component that includes <!DOCTYPE html>, <html>, <head>, and <body>). For partial HTML — such as HTMX responses or turbo-frame updates — use RenderFragment instead, which prepends styles without searching for </head>.
Example ¶
ExampleEngine_RenderPage demonstrates full-page rendering: collected <style> blocks are injected immediately before </head>.
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"github.com/dhamidi/htmlc"
)
func main() {
dir, err := os.MkdirTemp("", "htmlc-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
const vue = `<template><html><head><title>Demo</title></head><body><p>Hello</p></body></html></template><style>body{margin:0}</style>`
if err := os.WriteFile(filepath.Join(dir, "Page.vue"), []byte(vue), 0644); err != nil {
log.Fatal(err)
}
engine, err := htmlc.New(htmlc.Options{ComponentDir: dir})
if err != nil {
log.Fatal(err)
}
out, err := engine.RenderPageString("Page", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <html><head><title>Demo</title><style>body{margin:0}</style></head><body><p>Hello</p></body></html>
func (*Engine) RenderPageContext ¶
func (e *Engine) RenderPageContext(ctx context.Context, w io.Writer, name string, data map[string]any) error
RenderPageContext is like RenderPage but accepts a context.Context. The render is aborted and ctx.Err() is returned if the context is cancelled or its deadline is exceeded during rendering.
func (*Engine) RenderPageString ¶
RenderPageString renders name as a full HTML page and returns the result as a string. It is a convenience wrapper around RenderPage for callers that need a string rather than writing to an io.Writer.
func (*Engine) ServeComponent ¶
func (e *Engine) ServeComponent(name string, data func(*http.Request) map[string]any) http.HandlerFunc
ServeComponent returns an http.HandlerFunc that renders name as a fragment and writes it with content-type "text/html; charset=utf-8". The data function is called on every request to obtain the data map passed to the template; it may be nil (in which case no data is provided).
Data middleware registered via WithDataMiddleware is applied after the data function, allowing common data (e.g. the current user or CSRF token) to be injected globally.
Example ¶
ExampleEngine_ServeComponent shows how ServeComponent wraps a component as an http.HandlerFunc, demonstrated with an httptest round-trip.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"github.com/dhamidi/htmlc"
)
func main() {
dir, err := os.MkdirTemp("", "htmlc-example-*")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
if err := os.WriteFile(filepath.Join(dir, "Hello.vue"), []byte(`<template><p>hello</p></template>`), 0644); err != nil {
log.Fatal(err)
}
engine, err := htmlc.New(htmlc.Options{ComponentDir: dir})
if err != nil {
log.Fatal(err)
}
h := engine.ServeComponent("Hello", nil)
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
h(rec, req)
fmt.Println(rec.Code)
fmt.Println(rec.Header().Get("Content-Type"))
fmt.Println(rec.Body.String())
}
Output: 200 text/html; charset=utf-8 <p>hello</p>
func (*Engine) ServePageComponent ¶
func (e *Engine) ServePageComponent(name string, data func(*http.Request) (map[string]any, int)) http.HandlerFunc
ServePageComponent returns an http.HandlerFunc that renders name as a full HTML page (using RenderPage, which injects styles into </head>) and writes it with content-type "text/html; charset=utf-8".
The data function is called on every request to obtain the data map and the HTTP status code to send. If the data function is nil, a 200 OK response with no template data is used. A status code of 0 is treated as 200.
Data middleware registered via WithDataMiddleware is applied after the data function.
func (*Engine) SetComponentDir ¶ added in v0.7.0
SetComponentDir changes the component directory at runtime, re-running discovery atomically under the engine's write lock. If discovery fails, the engine's state is unchanged and the error is returned.
func (*Engine) SetDebug ¶ added in v0.7.0
SetDebug enables or disables debug render mode at runtime. When enabled, rendered HTML is annotated with HTML comments describing component boundaries, expression values, and slot contents. The change takes effect on the next render call.
func (*Engine) SetFS ¶ added in v0.7.0
SetFS changes the fs.FS used for component discovery and file reads at runtime, re-running discovery atomically under the engine's write lock. If discovery fails, the engine's state is unchanged and the error is returned.
func (*Engine) SetReload ¶ added in v0.7.0
SetReload enables or disables hot-reload at runtime. When enabled, the engine checks component file modification times before each render and automatically re-parses changed files. The change takes effect on the next render call.
func (*Engine) TemplateText ¶ added in v0.7.0
TemplateText returns the raw html/template-compatible text for componentName and all its statically-referenced sub-components. The text consists of {{ define }} blocks suitable for html/template.New("").Parse(text).
This is the text form of CompileToTemplate; see that method for full semantics. warnings contains any non-fatal conversion warnings emitted by the html/template conversion (for example, data-contract notices for v-html and v-bind spread).
Error types follow the same conventions as CompileToTemplate: ErrComponentNotFound when componentName is not registered, and *ConversionError (wrapped with ErrConversion) when a directive or expression cannot be converted.
func (*Engine) ValidateAll ¶
func (e *Engine) ValidateAll() []ValidationError
ValidateAll checks every registered component for unresolvable child component references and returns a slice of ValidationError (one per problem). An empty slice means all components are valid.
ValidateAll uses the same proximity-based resolution as the renderer: a reference that can be resolved via the proximity walk or the flat registry is considered valid. Only references that cannot be found by either mechanism are reported as errors.
ValidateAll is intended to be called once at application startup to surface missing-component problems early ("fail fast").
func (*Engine) WithDataMiddleware ¶
WithDataMiddleware adds a function that is called on every HTTP-triggered render to augment the data map. Middleware functions are called in registration order; later middleware can overwrite keys set by earlier ones.
Data middleware applies only to the HTTP-triggered top-level render and is not automatically propagated into child component scopes. If child components need access to values injected by middleware, those values must be passed as explicit props or the same values should be registered via RegisterFunc.
WithDataMiddleware returns the Engine so calls can be chained.
func (*Engine) WithMissingPropHandler ¶
func (e *Engine) WithMissingPropHandler(fn MissingPropFunc) *Engine
WithMissingPropHandler sets the function called when any component rendered by this engine has a missing prop. The default behaviour (when no handler is set) is to render a visible "[missing: <name>]" placeholder in place of the prop value. Use ErrorOnMissingProp to restore strict error behaviour, or SubstituteMissingProp to use the legacy "MISSING PROP: <name>" format.
type ExprKind ¶ added in v0.7.0
type ExprKind int
ExprKind classifies a template expression string.
func ClassifyExpr ¶ added in v0.7.0
ClassifyExpr inspects expr and returns its kind. "." alone is treated as ExprSimpleIdent (maps to "." in html/template).
type MapProps ¶ added in v0.8.0
type MapProps struct {
// contains filtered or unexported fields
}
MapProps wraps map[string]any and implements Props.
type MissingPropFunc ¶
MissingPropFunc is called when a prop expected by the component's template is not present in the render scope. It receives the prop name and returns a substitute value, or an error to abort rendering.
type Options ¶
type Options struct {
// ComponentDir is the directory to scan recursively for *.vue files.
// Components are discovered by walking the tree in lexical order; each file
// is registered by its base name without extension (e.g. "Button.vue"
// becomes "Button").
//
// When ComponentDir is set the engine uses proximity-based resolution:
// a tag reference in a template is first looked up in the same directory
// as the calling component, then walks toward the root one level at a time.
// This allows same-named components in different subdirectories to coexist
// without conflict. See the README for details and examples.
//
// When two files share the same base name and directory the last one
// encountered in lexical-order traversal wins in the flat registry
// (backward-compatibility fallback path).
ComponentDir string
// Reload enables hot-reload for development use. When true, the engine
// checks the modification time of every registered component file before
// each render and automatically re-parses any file that has changed since
// it was last loaded. This lets you edit .vue files and see the results
// without restarting the server. For production, leave Reload false and
// create the Engine once at startup.
//
// When FS is also set, reload only works if the FS implements fs.StatFS.
// If it does not, reload is silently skipped for all entries.
Reload bool
// FS, when set, is used instead of the OS filesystem for all file reads
// and directory walks. ComponentDir is then interpreted as a path within
// this FS. This allows callers to use embedded filesystems (//go:embed),
// in-memory virtual filesystems, or any other fs.FS implementation.
//
// When FS is nil, the OS filesystem is used (default behaviour).
FS fs.FS
// Directives registers custom directives available to all components rendered
// by this engine. Keys are directive names without the "v-" prefix
// (e.g. "switch" handles v-switch). Built-in directives (v-if, v-for, etc.)
// cannot be overridden.
Directives DirectiveRegistry
// Debug enables debug render mode. When true, the rendered HTML is
// annotated with HTML comments describing component boundaries, expression
// values, conditional branch outcomes, and slot contents.
// Intended for development use only; never enable in production.
Debug bool
// Logger, if non-nil, receives one structured log record per component
// rendered. Records are emitted at slog.LevelDebug for successful renders
// and slog.LevelError for failed renders. Each record includes the
// component name, render duration (subtree), bytes written (subtree), and
// any error. The nil value (default) disables all slog output.
Logger *slog.Logger
// ComponentErrorHandler, if non-nil, is called in place of aborting the
// render when a child component fails. The handler writes an HTML
// placeholder to w and returns nil to continue rendering, or returns a
// non-nil error to abort. When the handler returns nil for all failures,
// the partial page (with placeholders) is written to the io.Writer passed
// to RenderPage. The nil value (default) preserves the existing behaviour:
// the first component error aborts the render and w receives nothing.
ComponentErrorHandler ComponentErrorHandler
}
Options holds configuration for creating a new Engine.
type ParseError ¶
type ParseError struct {
// Path is the source file path.
Path string
// Msg is the human-readable description of the parse failure.
Msg string
// Location holds the source position of the error, or nil if unknown.
Location *SourceLocation
}
ParseError is returned when a .vue file cannot be parsed.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
type PropInfo ¶
PropInfo describes a single top-level prop (variable reference) found in a component's template, together with the expression strings in which it appears.
type Props ¶ added in v0.8.0
Props is the interface htmlc demands of any value used as component props.
type Registry ¶
Registry maps component names to their parsed components. Keys may be PascalCase (e.g., "Card") or kebab-case (e.g., "my-card"). Registry is part of the low-level API; most callers should use Engine, which builds and maintains a Registry automatically from a component directory.
type RenderError ¶
type RenderError struct {
// Component is the component name being rendered when the error occurred.
Component string
// ComponentPath is the ordered path of component names from the page root
// to the failing component (e.g. ["HomePage", "Layout", "Sidebar"]).
// It is populated at each component boundary as the error travels up the
// call stack. The last element matches Component.
ComponentPath []string
// Expr is the template expression that triggered the error (may be empty).
Expr string
// Wrapped is the underlying error.
Wrapped error
// Location holds the source position of the error, or nil if unknown.
Location *SourceLocation
}
RenderError is returned when template rendering fails for a named component.
func (*RenderError) Error ¶
func (e *RenderError) Error() string
func (*RenderError) Unwrap ¶
func (e *RenderError) Unwrap() error
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer walks a component's parsed template and produces HTML output. It is the low-level rendering primitive — most callers should use Engine (via RenderPage or RenderFragment) rather than constructing a Renderer directly. Use NewRenderer when you need fine-grained control over style collection or registry attachment.
Engine-level functions (from Engine.RegisterFunc) are available in child components only when the renderer is created by Engine — Engine calls WithFuncs automatically. Callers using the low-level NewRenderer API who want engine functions to propagate into child components must call WithFuncs explicitly.
func NewRenderer ¶
NewRenderer creates a Renderer for c. Call WithStyles and WithComponents before Render to enable style collection and component composition. Call WithFuncs to make engine-registered functions available in this component and all child components rendered from it.
func (*Renderer) Render ¶
Render evaluates the component's template against the given data scope and writes the rendered HTML directly to w.
func (*Renderer) RenderString ¶
RenderString evaluates the component's template against the given data scope and returns the rendered HTML as a string. It is a convenience wrapper around Render.
func (*Renderer) WithComponentErrorHandler ¶ added in v0.8.0
func (r *Renderer) WithComponentErrorHandler(h ComponentErrorHandler) *Renderer
WithComponentErrorHandler sets the handler called when a child component fails to render. When nil (default), the first component error aborts the render. Returns the Renderer for chaining.
func (*Renderer) WithComponentPath ¶ added in v0.8.0
WithComponentPath sets the ordered component path from the page root to this renderer's component. Returns the Renderer for chaining.
func (*Renderer) WithComponents ¶
WithComponents attaches a component registry to this renderer, enabling component composition. Returns the Renderer for chaining.
func (*Renderer) WithContext ¶
WithContext attaches a context.Context to the renderer. The render is aborted and ctx.Err() is returned if the context is cancelled or its deadline is exceeded. Returns the Renderer for chaining.
func (*Renderer) WithDirectives ¶
func (r *Renderer) WithDirectives(dr DirectiveRegistry) *Renderer
WithDirectives attaches a custom directive registry. Directives registered here are invoked when the renderer encounters v-<name> attributes that are not built-in directives. Returns the Renderer for chaining.
func (*Renderer) WithFuncs ¶ added in v0.2.2
WithFuncs attaches engine-registered functions to this renderer so they are available in template expressions and propagated to all child renderers. Returns the Renderer for chaining.
func (*Renderer) WithLogger ¶ added in v0.7.0
WithLogger attaches a *slog.Logger to this renderer. When non-nil, one structured log record is emitted per child component dispatch. Returns the Renderer for chaining.
func (*Renderer) WithMissingPropHandler ¶
func (r *Renderer) WithMissingPropHandler(fn MissingPropFunc) *Renderer
WithMissingPropHandler sets a handler that is called when a prop referenced in the template is not present in the render scope. Returns the Renderer for chaining.
Example ¶
ExampleRenderer_WithMissingPropHandler shows SubstituteMissingProp substituting a placeholder string when a required template prop is absent.
package main
import (
"fmt"
"log"
"github.com/dhamidi/htmlc"
)
func main() {
comp, err := htmlc.ParseFile("t.vue", `<template><p>{{ name }}</p></template>`)
if err != nil {
log.Fatal(err)
}
out, err := htmlc.NewRenderer(comp).
WithMissingPropHandler(htmlc.SubstituteMissingProp).
RenderString(nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <p>MISSING PROP: name</p>
func (*Renderer) WithNSComponents ¶ added in v0.6.0
func (r *Renderer) WithNSComponents(ns map[string]map[string]*Component, componentDir string) *Renderer
WithNSComponents attaches a namespaced component registry and the engine's ComponentDir to this renderer, enabling proximity-based component resolution. ns maps forward-slash relative directory paths to local component names to parsed components; componentDir is the value of Options.ComponentDir used when the engine was created.
When set, resolveComponent walks up the directory tree from the caller component's location before falling back to the flat registry. Returns the Renderer for chaining.
func (*Renderer) WithStyles ¶
func (r *Renderer) WithStyles(sc *StyleCollector) *Renderer
WithStyles sets sc as the StyleCollector that will receive this component's style contribution when Render is called. It returns the Renderer for chaining.
type SlotDefinition ¶
type SlotDefinition struct {
Nodes []*html.Node
ParentScope map[string]any
BindingVar string
Bindings []string
// Component is the component that authored this slot content.
// It is used to stamp the correct scope attribute on slot elements.
// May be nil when the parent has no component context (rare).
Component *Component
// SlotDefs holds the slot definitions that were active in the authoring
// component at the time this slot content was captured. When the slot
// content itself contains <slot /> elements they must be resolved against
// this map, not against the consuming component's slot definitions.
SlotDefs map[string]*SlotDefinition
}
SlotDefinition captures everything needed to defer slot rendering: the AST nodes from the caller's template, the caller's scope at invocation time, and the parsed binding information from the v-slot / # directive.
type SourceLocation ¶ added in v0.2.0
type SourceLocation struct {
File string // source file path
Line int // 1-based line number (0 = unknown)
Column int // 1-based column (0 = unknown)
Snippet string // ≈3-line context around the error (may be empty)
}
SourceLocation describes a position within a source file.
type StructProps ¶ added in v0.8.0
type StructProps struct {
// contains filtered or unexported fields
}
StructProps wraps a dereferenced reflect.Value of kind Struct and implements Props lazily — no upfront map allocation.
func (StructProps) Get ¶ added in v0.8.0
func (p StructProps) Get(key string) (any, bool)
Get resolves a key using three-step lookup:
- Exact json tag match (case-sensitive).
- Exact Go field name match (case-sensitive).
- First-rune-lowercased Go field name match — only when no json tag is present.
Returns nil, true when the resolved field value is a typed nil pointer.
func (StructProps) Keys ¶ added in v0.8.0
func (p StructProps) Keys() []string
Keys enumerates canonical keys using the two-pass algorithm: direct (non-anonymous) fields first, then anonymous embedded fields. The first-rune-lowercase alias is never added to Keys — it is a lookup-only affordance.
type StyleCollector ¶
type StyleCollector struct {
// contains filtered or unexported fields
}
StyleCollector accumulates StyleContributions from one or more component renders into a single ordered list, deduplicating repeated contributions from the same scoped component. It is part of the low-level API; Engine creates and manages a StyleCollector automatically on each render call.
func (*StyleCollector) Add ¶
func (sc *StyleCollector) Add(c StyleContribution)
Add appends c to the collector, skipping duplicates. Two contributions are considered duplicates when they share the same composite key (ScopeID + CSS), so the same scoped component rendered N times contributes its CSS only once, while different components or differing global CSS blocks are each kept.
func (*StyleCollector) All ¶
func (sc *StyleCollector) All() []StyleContribution
All returns all StyleContributions in the order they were added. The slice is nil when no contributions have been collected.
type StyleContribution ¶
type StyleContribution struct {
// ScopeID is the scope attribute name (e.g. "data-v-a1b2c3d4") for a
// scoped component's styles, or empty for global (non-scoped) styles.
ScopeID string
// CSS is the stylesheet text. For scoped components it has already been
// rewritten by ScopeCSS; for global styles it is passed through verbatim.
CSS string
}
StyleContribution holds a style block contributed by a component during render.
type TemplateToVueResult ¶ added in v0.7.0
TemplateToVueResult holds the converted .vue source text and any non-fatal warnings generated during conversion.
func TemplateToVue ¶ added in v0.7.0
func TemplateToVue(src, componentName string) (*TemplateToVueResult, error)
TemplateToVue converts the text of an html/template file to .vue syntax. src is the raw template source; componentName is used only for error messages.
The conversion is explicitly best-effort: only constructs with unambiguous .vue equivalents are translated. The first unsupported construct halts conversion and returns a *ConversionError; no partial output is produced.
Supported constructs ¶
- Text nodes: emitted verbatim
- {{.ident}} and {{.a.b.c}}: converted to {{ ident }} and {{ a.b.c }}
- {{if .cond}}…{{end}}: wraps body in <div v-if="cond">
- {{if .cond}}…{{else}}…{{end}}: emits v-if and v-else on synthetic <div>
- {{range .items}}…{{end}}: wraps body in <ul><li v-for="item in items">
- {{block "name" .}}…{{end}}: emits <slot name="name"> (or <slot> for "default")
- {{template "Name" .}}: emits <Name />
Unsupported constructs (return ConversionError) ¶
- {{.items | len}} or any multi-command pipeline
- {{with .x}}
- Variable assignments ($x := …)
- Actions whose pipeline is not a single FieldNode
- {{template "Name" expr}} where expr is not .
Note: {{block "name" .}} is desugared by the template parser into a {{define "name"}} block plus a {{template "name" .}} call. TemplateToVue detects this by checking whether a parsed sub-tree with that name exists alongside the root tree in the parse result.
type VHighlight ¶ added in v0.3.0
type VHighlight struct{}
VHighlight is an example custom directive that sets the background colour of the host element. It is the htmlc equivalent of the v-highlight directive shown in the Vue.js custom directives guide (https://vuejs.org/guide/reusability/custom-directives.html).
Register it on an engine and then use v-highlight in templates:
engine.RegisterDirective("highlight", &htmlc.VHighlight{})
Template usage:
<p v-highlight="'yellow'">Highlight this text bright yellow</p>
The expression must evaluate to a non-empty CSS colour string. If the host element already has a style attribute, the background property is appended; existing style declarations are preserved.
func (*VHighlight) Created ¶ added in v0.3.0
func (v *VHighlight) Created(node *html.Node, binding DirectiveBinding, ctx DirectiveContext) error
Created merges `background: <colour>` into the host element's style attribute.
func (*VHighlight) Mounted ¶ added in v0.3.0
func (v *VHighlight) Mounted(_ io.Writer, _ *html.Node, _ DirectiveBinding, _ DirectiveContext) error
Mounted is a no-op for VHighlight.
type ValidationError ¶
type ValidationError struct {
// Component is the name of the component that has the problem.
Component string
// Message describes the problem.
Message string
}
ValidationError describes a single problem found by Engine.ValidateAll.
func (ValidationError) Error ¶
func (e ValidationError) Error() string
type VueToTemplateResult ¶ added in v0.7.0
VueToTemplateResult holds the converted template text and any non-fatal warnings generated during conversion.
func VueToTemplate ¶ added in v0.7.0
func VueToTemplate(tmpl *html.Node, componentName string) (*VueToTemplateResult, error)
VueToTemplate converts a parsed .vue component template tree to Go html/template syntax. tmpl is the root *html.Node from the component's Template field (i.e. the parsed <template> section).
The result is a string containing one {{define "componentName"}}…{{end}} block, suitable for combining with other such blocks and parsing via html/template.New("").Parse(combined).
componentName is the base name used for the {{define}} block. It is passed through unchanged; callers that require lowercase names (as Engine.CompileToTemplate does) must lowercase before calling.
Non-fatal issues (for example, data-contract warnings for v-html and v-bind spread) are returned in VueToTemplateResult.Warnings. Fatal issues return nil and a non-nil *ConversionError on the first unsupported construct encountered; no partial output is produced.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
expvar-demo
command
Command expvar-demo runs a small HTTP server that demonstrates the htmlc expvar integration.
|
Command expvar-demo runs a small HTTP server that demonstrates the htmlc expvar integration. |
|
htmlc
command
|
|
|
v-syntax-highlight
module
|
|
|
Package expr implements the expression language used in htmlc templates.
|
Package expr implements the expression language used in htmlc templates. |
|
Package htmlctest provides helpers for testing htmlc components.
|
Package htmlctest provides helpers for testing htmlc components. |