On this page
New / Options Register / Has / Components ValidateAll RenderPage RenderFragment String helpers Context variants ServeComponent ServePageComponent Mount WithDataMiddleware RegisterFunc RegisterDirective Missing prop handling ParseFile / Component Renderer Registry Directive interface DirectiveBinding / Context StyleCollector ScopeID / ScopeCSS Error types Sentinel errorsGo API Reference
Complete reference for every exported symbol in the htmlc package. Import path: github.com/dhamidi/htmlc.
Creating an Engine
New
func New(opts Options) (*Engine, error)
Creates an Engine from opts. If opts.ComponentDir is set the directory is walked recursively and all *.vue files are registered before the engine is returned.
engine, err := htmlc.New(htmlc.Options{
ComponentDir: "./components",
})
if err != nil {
log.Fatal(err)
}
Options
type Options struct {
ComponentDir string
Reload bool
FS fs.FS
Directives DirectiveRegistry
Debug bool
}
| Field | Description |
|---|---|
ComponentDir |
Directory walked recursively for *.vue files. Each file is registered by its base name without extension (Button.vue → Button). When two files share the same base name the last one in lexical order wins. |
Reload |
When true the engine checks the modification time of every registered file before each render and re-parses changed files automatically. Use during development; leave false in production. |
FS |
When set, all file reads and directory walks use this fs.FS instead of the OS filesystem. ComponentDir is interpreted as a path within the FS. Useful with //go:embed. Hot-reload requires the FS to also implement fs.StatFS. |
Directives |
Custom directives available to all components rendered by this engine. Keys are directive names without the v- prefix. Built-in directives cannot be overridden. |
Debug |
When true the rendered HTML is annotated with HTML comments describing component boundaries, expression values, and slot contents. Development use only. |
Rendering
RenderPage
func (e *Engine) RenderPage(w io.Writer, name string, data map[string]any) error
Renders the named component as a full HTML page and writes the result to w. Scoped styles are collected from the entire component tree and injected as a <style> block immediately before the first </head> tag. If no </head> is found the style block is prepended to the output.
Use RenderPage for page components that include <!DOCTYPE html>, <html>, <head>, and <body>.
var buf bytes.Buffer
err := engine.RenderPage(&buf, "HomePage", map[string]any{
"title": "Welcome",
})
RenderFragment
func (e *Engine) RenderFragment(w io.Writer, name string, data map[string]any) error
Renders the named component as an HTML fragment and prepends the collected <style> block to the output. Does not search for a </head> tag. Use for partial page updates such as HTMX responses or turbo-frame updates.
String helpers
func (e *Engine) RenderPageString(name string, data map[string]any) (string, error)
func (e *Engine) RenderFragmentString(name string, data map[string]any) (string, error)
Convenience wrappers around RenderPage and RenderFragment that return the result as a string instead of writing to an io.Writer.
Context variants
func (e *Engine) RenderPageContext(ctx context.Context, 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
Like RenderPage and RenderFragment but accept a context.Context. The render is aborted and ctx.Err() is returned if the context is cancelled or its deadline is exceeded.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err := engine.RenderPageContext(ctx, w, "HomePage", data)
HTTP Integration
ServeComponent
func (e *Engine) ServeComponent(name string, data func(*http.Request) map[string]any) http.HandlerFunc
Returns an http.HandlerFunc that renders name as a fragment on every request. The data function is called per-request to build the template scope; it may be nil. Data middleware registered via WithDataMiddleware is applied after the data function. Sets Content-Type: text/html; charset=utf-8.
mux.HandleFunc("GET /search-results", engine.ServeComponent("SearchResults", func(r *http.Request) map[string]any {
return map[string]any{"query": r.URL.Query().Get("q")}
}))
ServePageComponent
func (e *Engine) ServePageComponent(name string, data func(*http.Request) (map[string]any, int)) http.HandlerFunc
Returns an http.HandlerFunc that renders name as a full HTML page. The data function returns both the template scope and the HTTP status code to send. A status code of 0 is treated as 200. If data is nil a 200 OK response with no template data is used.
mux.HandleFunc("GET /post/{id}", engine.ServePageComponent("PostPage", func(r *http.Request) (map[string]any, int) {
post, err := db.GetPost(r.PathValue("id"))
if err != nil {
return map[string]any{"error": err.Error()}, http.StatusNotFound
}
return map[string]any{"post": post}, http.StatusOK
}))
Mount
func (e *Engine) Mount(mux *http.ServeMux, routes map[string]string)
Registers multiple component routes on mux at once. Keys are http.ServeMux patterns (e.g. "GET /{$}", "GET /about"); values are component names. Each component is served as a full page via ServePageComponent with no data function.
engine.Mount(mux, map[string]string{
"GET /{$}": "HomePage",
"GET /about": "AboutPage",
"GET /blog": "BlogPage",
})
WithDataMiddleware
func (e *Engine) WithDataMiddleware(fn func(*http.Request, map[string]any) map[string]any) *Engine
Adds a function that augments the data map on every HTTP-triggered render. Multiple middleware functions are called in registration order; later middleware can overwrite keys set by earlier ones. Returns the engine for chaining.
Data middleware applies only to the top-level render scope and is not automatically propagated into child component scopes. Pass shared values via RegisterFunc if child components need them.
engine.WithDataMiddleware(func(r *http.Request, data map[string]any) map[string]any {
data["currentUser"] = userFromRequest(r)
data["csrfToken"] = csrf.Token(r)
return data
})
Component Management
Register
func (e *Engine) Register(name, path string) error
Manually adds a component from path to the engine's registry under name, without requiring a directory scan. Useful for programmatically generated components or files outside ComponentDir.
Has
func (e *Engine) Has(name string) bool
Reports whether name is a registered component.
Components
func (e *Engine) Components() []string
Returns the names of all registered components in sorted order. Automatic lowercase aliases added by the engine are excluded.
ValidateAll
func (e *Engine) ValidateAll() []ValidationError
Checks every registered component for unresolvable child component references. Returns one ValidationError per problem; an empty slice means all components are valid. Call once at application startup to surface missing-component issues early.
if errs := engine.ValidateAll(); len(errs) != 0 {
for _, e := range errs {
log.Println(e)
}
os.Exit(1)
}
Customization
RegisterFunc
func (e *Engine) RegisterFunc(name string, fn func(...any) (any, error)) *Engine
Adds a per-engine function available in all template expressions. The function is callable from templates as name(). Engine functions have lower priority than user-provided data keys. They are propagated automatically into every child component's scope.
engine.RegisterFunc("formatDate", func(args ...any) (any, error) {
t, ok := args[0].(time.Time)
if !ok {
return "", fmt.Errorf("formatDate: expected time.Time")
}
return t.Format("Jan 2, 2006"), nil
})
RegisterDirective
func (e *Engine) RegisterDirective(name string, dir Directive)
Adds a custom directive under name (without the v- prefix). Replaces any previously registered directive with the same name. Panics if dir is nil.
engine.RegisterDirective("tooltip", &TooltipDirective{})
WithMissingPropHandler
func (e *Engine) WithMissingPropHandler(fn MissingPropFunc) *Engine
Sets the function called when any component has a missing prop. The default behaviour renders a visible [missing: <name>] placeholder. Use ErrorOnMissingProp for strict error behaviour or SubstituteMissingProp for legacy placeholder text.
// Abort rendering on any missing prop
engine.WithMissingPropHandler(htmlc.ErrorOnMissingProp)
// Substitute a legacy placeholder string
engine.WithMissingPropHandler(htmlc.SubstituteMissingProp)
Low-level API
ParseFile
func ParseFile(path, src string) (*Component, error)
Parses a .vue Single File Component from the string src. path is used only for error messages and the scope attribute hash. Returns a *Component whose Template field is a parsed HTML node tree. Only the top-level <template>, <script>, and <style> sections are extracted; <template> is required.
Component
type Component struct {
Template *html.Node // root of the parsed template node tree
Script string // raw <script> content (empty if absent)
Style string // raw <style> content (empty if absent)
Scoped bool // true when <style scoped> was present
Path string // source file path passed to ParseFile
Source string // raw source text (for error location reporting)
Warnings []string // non-fatal issues found during parsing
}
Holds the parsed representation of a .vue Single File Component. The Warnings field may contain notices about auto-corrected self-closing component tags.
Component.Props
func (c *Component) Props() []PropInfo
Walks the template AST and returns all top-level variable references the template uses. Identifiers starting with $ and v-for loop variables are excluded.
type PropInfo struct {
Name string // identifier name
Expressions []string // template expressions in which it appears
}
Registry
type Registry map[string]*Component
Maps component names to their parsed components. Keys may be PascalCase or kebab-case. Most callers use Engine, which builds and maintains a Registry automatically.
Renderer
func NewRenderer(c *Component) *Renderer
Creates a Renderer for c. Use the builder methods below to configure it before calling Render.
Renderer is the low-level rendering primitive. Most callers should use Engine via RenderPage or RenderFragment. Use NewRenderer when you need fine-grained control over style collection or registry attachment.
| Builder method | Description |
|---|---|
WithStyles(sc *StyleCollector) *Renderer |
Sets the StyleCollector that receives this component's style contribution. |
WithComponents(reg Registry) *Renderer |
Attaches a component registry, enabling component composition. |
WithMissingPropHandler(fn MissingPropFunc) *Renderer |
Sets the handler called when a template prop is absent from the scope. |
WithDirectives(dr DirectiveRegistry) *Renderer |
Attaches a custom directive registry. |
WithContext(ctx context.Context) *Renderer |
Attaches a context.Context; the render is aborted on cancellation. |
WithFuncs(funcs map[string]any) *Renderer |
Attaches engine-registered functions so they are available in expressions and propagated to all child renderers. |
Renderer.Render / RenderString
func (r *Renderer) Render(w io.Writer, scope map[string]any) error
func (r *Renderer) RenderString(scope map[string]any) (string, error)
Evaluates the component's template against scope and writes HTML to w (or returns it as a string). Prop validation and style collection happen here.
Package-level helpers
func Render(w io.Writer, c *Component, scope map[string]any) error
func RenderString(c *Component, scope map[string]any) (string, error)
Convenience wrappers that create a temporary Renderer for c. They do not collect styles or support component composition. Use NewRenderer with WithStyles and WithComponents when those features are needed.
Directives
Directive interface
type Directive interface {
Created(node *html.Node, binding DirectiveBinding, ctx DirectiveContext) error
Mounted(w io.Writer, node *html.Node, binding DirectiveBinding, ctx DirectiveContext) error
}
Implemented by custom directive types. Register with Engine.RegisterDirective or pass in Options.Directives. Only the Created and Mounted hooks are called because htmlc renders server-side.
Created— called before the element is rendered. Receives a shallow-cloned working node whoseAttrslice andDatafield may be freely mutated. Return a non-nil error to abort rendering.Mounted— called after the element's closing tag has been written tow. Bytes written towappear immediately after the element. Return a non-nil error to abort rendering.
DirectiveBinding / DirectiveContext
type DirectiveBinding struct {
Value any // result of evaluating the directive expression
RawExpr string // un-evaluated expression string from the template
Arg string // directive argument after the colon, e.g. "href" in v-bind:href
Modifiers map[string]bool // dot-separated modifiers, e.g. {"prevent": true}
}
type DirectiveContext struct {
Registry Registry // component registry the renderer is using
}
DirectiveRegistry
type DirectiveRegistry map[string]Directive
Maps directive names (without the v- prefix) to their implementations. Keys are lower-kebab-case; the renderer normalises names before lookup.
Style Collection
StyleCollector
type StyleCollector struct { /* unexported fields */ }
Accumulates StyleContribution values from one or more component renders into a single ordered list, deduplicating contributions from the same scoped component. Engine creates and manages a StyleCollector automatically on each render call.
func (sc *StyleCollector) Add(c StyleContribution)
func (sc *StyleCollector) All() []StyleContribution
Add— appendsc, skipping duplicates. Two contributions are duplicates when they share the same composite key (ScopeID + CSS).All— returns all contributions in the order they were added.
StyleContribution
type StyleContribution struct {
ScopeID string // scope attribute name (e.g. "data-v-a1b2c3d4"), empty for global styles
CSS string // stylesheet text, already rewritten by ScopeCSS for scoped components
}
ScopeID / ScopeCSS
func ScopeID(path string) string
func ScopeCSS(css, scopeAttr string) string
ScopeID— returns"data-v-"followed by 8 hex digits derived from the FNV-1a 32-bit hash ofpath.ScopeCSS— rewritescssso that every selector in every non-@-rule hasscopeAttrappended to its last compound selector.@-rules are passed through verbatim.
Error Handling
Error types
ParseError
type ParseError struct {
Path string // source file path
Msg string // human-readable description
Location *SourceLocation // source position, or nil if unknown
}
Returned by ParseFile when a .vue file cannot be parsed.
RenderError
type RenderError struct {
Component string // component name being rendered
Expr string // expression that triggered the error (may be empty)
Wrapped error // underlying error
Location *SourceLocation // source position, or nil if unknown
}
Returned by render methods when template evaluation fails. Implements Unwrap.
ValidationError
type ValidationError struct {
Component string // name of the component with the problem
Message string // description of the problem
}
One entry per problem in the slice returned by ValidateAll.
SourceLocation
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)
}
MissingPropFunc / handlers
type MissingPropFunc func(name string) (any, error)
func ErrorOnMissingProp(name string) (any, error)
func SubstituteMissingProp(name string) (any, error)
MissingPropFunc— signature for missing-prop handlers; receive the prop name, return a substitute value or an error.ErrorOnMissingProp— aborts rendering with an error whenever a prop is absent. Use for strict validation.SubstituteMissingProp— returns"MISSING PROP: <name>"as a placeholder string.
Sentinel errors
var ErrComponentNotFound = errors.New("htmlc: component not found")
var ErrMissingProp = errors.New("htmlc: missing required prop")
ErrComponentNotFound— wrapped inside the error returned by render methods when the requested component name is not registered.ErrMissingProp— returned (wrapped) when a required prop is absent and noMissingPropFunchas been set.
if errors.Is(err, htmlc.ErrComponentNotFound) {
http.NotFound(w, r)
return
}