Documentation
¶
Overview ¶
Package gotmx is a component-based HTML template engine for Go.
It enables server-side rendering with templates that remain valid HTML. Templates use standard HTML attributes (g-* or data-g-*) instead of custom syntax, making them viewable and editable in any HTML editor and previewable in browsers.
Getting Started ¶
Use the Engine API to create and render templates:
engine, err := gotmx.New(gotmx.WithTemplateDir("templates"))
if err != nil {
log.Fatal(err)
}
defer engine.Close()
// Render a template
engine.Render(ctx, w, "my-template", data)
Advanced Customization ¶
For advanced use cases, Engine supports custom registries and resolvers:
engine, err := gotmx.New(
gotmx.WithCustomRegistry(myRegistry),
gotmx.WithCustomResolver(myResolver),
)
Template Attributes ¶
Templates use special attributes to control rendering:
- g-if: Conditional rendering
- g-outer-repeat, g-inner-repeat: Iteration over collections
- g-inner-text, g-inner-html: Content replacement
- g-use: Component composition
- g-define-slot, g-use-slot: Slot-based content injection
For more information on available attributes, see the attribute reference in the docs/ directory.
Index ¶
- Constants
- type AmbiguousTemplateError
- type AttributeMap
- type ComponentCreationError
- type ComponentNotFoundError
- type DuplicateTemplateError
- type Engine
- func (e *Engine) Close() error
- func (e *Engine) Component(templateName string, data any) (Renderable, error)
- func (e *Engine) ComponentWithSlots(templateName string, data any, slots map[string][]Renderable) (Renderable, error)
- func (e *Engine) HasTemplate(templateName string) bool
- func (e *Engine) LoadFS(fsys fs.FS, prefix string) error
- func (e *Engine) LoadFile(path string) error
- func (e *Engine) LoadHTML(html string) error
- func (e *Engine) Logger() Logger
- func (e *Engine) MustComponent(templateName string, data any) Renderable
- func (e *Engine) NewRenderContext(ctx context.Context) *RenderContext
- func (e *Engine) Preload(patterns ...string) error
- func (e *Engine) RegisterFunc(name string, fn any)
- func (e *Engine) Render(ctx context.Context, w io.Writer, templateName string, data any, ...) error
- func (e *Engine) RenderString(ctx context.Context, templateName string, data any, opts ...RenderOption) (string, error)
- type FileError
- type GoTemplateRegistry
- type HasAttributes
- type HasChildren
- type InvalidPathError
- type LazyTemplateLoader
- type Logger
- type MaxNestingDepthExceededError
- type ModelPathResolver
- type ModelPathResolverDefault
- type Namespace
- type NilComponentError
- type Option
- func WithCustomRegistry(registry TemplateRegistry) Option
- func WithCustomResolver(resolver ModelPathResolver) Option
- func WithDeterministicOutput(enabled bool) Option
- func WithDevDebounce(d time.Duration) Option
- func WithDevMode(enabled bool) Option
- func WithEagerExtensions(exts ...string) Option
- func WithFS(fsys fs.FS) Option
- func WithIgnore(patterns ...string) Option
- func WithLazyExtensions(exts ...string) Option
- func WithLogger(logger Logger) Option
- func WithMaxNestingDepth(depth int) Option
- func WithReloadCallback(fn func(err error)) Option
- func WithTemplateDir(dir string) Option
- type RenderContext
- type RenderError
- type RenderOption
- type RenderType
- type Renderable
- type Slots
- type SlottedRenderables
- type Template
- type TemplateLoadError
- type TemplateName
- type TemplateNotFoundError
- type TemplateRef
- type TemplateRegistry
- type TemplateRegistryDefault
- func (tr *TemplateRegistryDefault) ClearTemplates()
- func (tr *TemplateRegistryDefault) GetTemplate(templateRef TemplateRef) (Template, error)
- func (tr *TemplateRegistryDefault) GetTemplateExt(namespace Namespace, templateName TemplateName) (Template, error)
- func (tr *TemplateRegistryDefault) LogTemplates()
- func (tr *TemplateRegistryDefault) RegisterFunc(name string, f interface{})
- func (tr *TemplateRegistryDefault) RegisterGoTemplate(name TemplateName, template string, sourceFile string) error
- func (tr *TemplateRegistryDefault) RegisterTemplate(template Template) error
- func (tr *TemplateRegistryDefault) ReplaceFrom(source *TemplateRegistryDefault)
- func (tr *TemplateRegistryDefault) SetLazyTemplateLoader(ltl LazyTemplateLoader)
- func (tr *TemplateRegistryDefault) SetLogger(logger Logger)
- type TemplateRetrievalError
- type TemplateSourceNotFoundError
- type VoidElementChildError
Examples ¶
Constants ¶
const DefaultMaxNestingDepth = 64
DefaultMaxNestingDepth is the default maximum template nesting depth. This prevents stack overflow from circular template references.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AmbiguousTemplateError ¶
type AmbiguousTemplateError struct {
Name TemplateName
Namespaces []Namespace
}
AmbiguousTemplateError is returned when a template name matches multiple templates across different namespaces and no namespace was specified to disambiguate.
func (*AmbiguousTemplateError) Error ¶
func (e *AmbiguousTemplateError) Error() string
type AttributeMap ¶
type ComponentCreationError ¶
type ComponentCreationError struct {
TemplateName TemplateRef
Cause error
}
ComponentCreationError is returned when a component fails to be created from a template. It wraps the underlying error and provides context about which template failed.
func (*ComponentCreationError) Error ¶
func (e *ComponentCreationError) Error() string
func (*ComponentCreationError) Unwrap ¶
func (e *ComponentCreationError) Unwrap() error
type ComponentNotFoundError ¶
type ComponentNotFoundError struct {
ComponentRef string
TemplateName TemplateName
Cause error
}
ComponentNotFoundError is returned when a component cannot be created, typically because the referenced template does not exist.
func (*ComponentNotFoundError) Error ¶
func (e *ComponentNotFoundError) Error() string
func (*ComponentNotFoundError) Unwrap ¶
func (e *ComponentNotFoundError) Unwrap() error
type DuplicateTemplateError ¶
type DuplicateTemplateError struct {
Name TemplateName
Namespace Namespace
}
DuplicateTemplateError is returned when attempting to register a template that already exists with the same name and namespace.
func (*DuplicateTemplateError) Error ¶
func (e *DuplicateTemplateError) Error() string
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the main entry point for the gotmx template engine. It provides template loading, rendering, and lifecycle management. Create one with New() and pass it around your application.
func New ¶
New creates a new template engine with the given options. Returns an error if the configuration is invalid.
func (*Engine) Close ¶
Close releases any resources held by the engine (file watchers, etc.) Should be called via defer after New().
func (*Engine) Component ¶
func (e *Engine) Component(templateName string, data any) (Renderable, error)
Component returns a Renderable for the given template. Useful when you need to pass a component as slot content.
func (*Engine) ComponentWithSlots ¶
func (e *Engine) ComponentWithSlots(templateName string, data any, slots map[string][]Renderable) (Renderable, error)
ComponentWithSlots creates a Renderable and populates its slots. This is a convenience method combining Component() and AddChildren().
func (*Engine) HasTemplate ¶
HasTemplate checks if a template exists without triggering lazy loading. Useful for conditional rendering logic.
func (*Engine) LoadFS ¶
LoadFS loads templates from a filesystem with an optional prefix. Useful for the registration pattern where packages register their own templates. The prefix is prepended to template namespaces for disambiguation.
engine.LoadFS(users.TemplateFS, "users") // templates namespaced as "users/..." engine.LoadFS(shared.TemplateFS, "") // no prefix
func (*Engine) LoadFile ¶
LoadFile parses a single HTML file and registers any template definitions. The file path becomes the namespace for the templates.
func (*Engine) LoadHTML ¶
LoadHTML parses HTML string(s) and registers any template definitions found. Templates are available immediately after this call. Returns an error if parsing fails.
func (*Engine) MustComponent ¶
func (e *Engine) MustComponent(templateName string, data any) Renderable
MustComponent is like Component but panics on error. Use only for templates known to exist at development time.
func (*Engine) NewRenderContext ¶
func (e *Engine) NewRenderContext(ctx context.Context) *RenderContext
NewRenderContext creates a new RenderContext that wraps this Engine's capabilities. This context is created once per render call and passed through the entire rendering tree, avoiding per-node allocations while decoupling renderables from the Engine type.
The ctx parameter is Go's standard context.Context for request cancellation, timeouts, and passing request-scoped values. HTTP handlers should pass r.Context() to enable proper request lifecycle handling.
Use this method when you need to call Renderable.Render() directly on a component obtained via Component(). For normal template rendering, use Render() or RenderString() which create the context automatically.
Example:
component, _ := engine.Component("my-template", data)
renderCtx := engine.NewRenderContext(r.Context())
component.Render(renderCtx, writer, gotmx.RenderOuter)
func (*Engine) Preload ¶
Preload forces immediate loading of templates matching the given patterns. Useful for preloading critical templates while keeping others lazy. Patterns support glob syntax: "components/*.htm", "layouts/**/*.html"
func (*Engine) RegisterFunc ¶
RegisterFunc registers a function for use in Go templates. Must be called before templates using the function are parsed.
func (*Engine) Render ¶
func (e *Engine) Render(ctx context.Context, w io.Writer, templateName string, data any, opts ...RenderOption) error
Render writes the rendered template to the given writer. Uses HTML escaping by default. Returns an error if template not found. The ctx parameter is Go's standard context.Context for request cancellation, timeouts, and passing request-scoped values. HTTP handlers should pass r.Context().
Example ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<div data-g-define="greeting" data-g-inner-text="[[ .Name ]]">placeholder</div>`)
engine.Render(context.Background(), os.Stdout, "greeting", map[string]any{"Name": "World"})
}
Output: <div>World</div>
Example (Composition) ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`
<button data-g-define="btn" class="btn"><span data-g-define-slot="">Click</span></button>
<div data-g-define="page"><div data-g-use="btn">Submit</div></div>
`)
engine.Render(context.Background(), os.Stdout, "page", nil)
}
Output: <div><button class="btn"><span>Submit</span></button></div>
Example (Conditional) ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<div data-g-define="status"><span data-g-if="[[ .Active ]]">Active</span><span data-g-if="[[ .Inactive ]]">Inactive</span></div>`)
engine.Render(context.Background(), os.Stdout, "status", map[string]any{"Active": true, "Inactive": false})
}
Output: <div><span>Active</span></div>
Example (Escaping) ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<div data-g-define="safe" data-g-inner-text="[[ .Input ]]">placeholder</div>`)
// g-inner-text always escapes HTML, protecting against XSS
engine.Render(context.Background(), os.Stdout, "safe", map[string]any{
"Input": `<script>alert("xss")</script>`,
})
}
Output: <div><script>alert("xss")</script></div>
Example (Iteration) ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<ul data-g-define="list"><li data-g-outer-repeat="[[ .Items ]]" data-g-inner-text="[[ . ]]">placeholder</li></ul>`)
data := map[string]any{
"Items": []string{"Alice", "Bob", "Carol"},
}
engine.Render(context.Background(), os.Stdout, "list", data)
}
Output: <ul><li>Alice</li><li>Bob</li><li>Carol</li></ul>
Example (WithLayout) ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`
<div data-g-define="layout"><nav>Menu</nav><main data-g-define-slot="">default</main></div>
<p data-g-define="page">Hello from page</p>
`)
engine.Render(context.Background(), os.Stdout, "page", nil,
gotmx.WithLayout("layout", nil),
)
}
Output: <div><nav>Menu</nav><main><p>Hello from page</p></main></div>
func (*Engine) RenderString ¶
func (e *Engine) RenderString(ctx context.Context, templateName string, data any, opts ...RenderOption) (string, error)
RenderString renders a template to a string. Convenience wrapper around Render() using a pooled bytes.Buffer. The ctx parameter is Go's standard context.Context for request cancellation, timeouts, and passing request-scoped values.
Example ¶
package main
import (
"context"
"fmt"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<span data-g-define="badge" data-g-inner-text="[[ .Label ]]">x</span>`)
result, _ := engine.RenderString(context.Background(), "badge", map[string]any{"Label": "New"})
fmt.Println(result)
}
Output: <span>New</span>
Example (WithSlots) ¶
package main
import (
"context"
"fmt"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<div data-g-define="card"><header data-g-define-slot="header">Default</header><main data-g-define-slot="">Body</main></div>`)
result, _ := engine.RenderString(context.Background(), "card", nil,
gotmx.Slot("header", "<h1>Title</h1>"),
gotmx.Slot("", "<p>Content</p>"),
)
fmt.Println(result)
}
Output: <div><header><h1>Title</h1></header><main><p>Content</p></main></div>
type FileError ¶
type FileError struct {
Path string // The file path that caused the error
Operation string // The operation that failed (e.g., "stat", "open", "read")
Cause error // The underlying error
}
FileError is returned when a file system operation fails. It wraps the underlying error and provides context about which file and operation failed.
type GoTemplateRegistry ¶
type GoTemplateRegistry interface {
// RegisterGoTemplate adds a native Go template to the registry.
// Returns an error if the template cannot be registered.
RegisterGoTemplate(templateName TemplateName, template string, sourceFile string) error
// RegisterFunc registers a function that can be used within Go templates.
// The function must be registered before any templates that use it are parsed.
RegisterFunc(name string, fun interface{})
}
GoTemplateRegistry is an additional interface that template registries can implement to handle native Go templates. Go templates may reference each other, and in such cases they need to be in the same textTemplate.Template or htmlTemplate.Template instance. Therefore, they need to be treated separately from regular gotmx templates.
Note: Golang templates require you to register any functions BEFORE the templates are actually parsed.
type HasAttributes ¶
type HasAttributes interface {
SetAttributes(attributes AttributeMap)
}
HasAttributes is an interface for a Renderable that can have attributes.
type HasChildren ¶
type HasChildren interface {
AddChild(slot string, child Renderable)
AddChildren(slottedChildren SlottedRenderables)
}
HasChildren is an interface for a Renderable that can have children (in slots).
type InvalidPathError ¶
type InvalidPathError struct {
Path string // The problematic path
ExpectedType string // What was expected: "file" or "directory"
ActualType string // What was found: "file" or "directory"
}
InvalidPathError is returned when a file system path is invalid for the expected operation. For example, when a file path is provided where a directory is expected, or vice versa.
func (*InvalidPathError) Error ¶
func (e *InvalidPathError) Error() string
type LazyTemplateLoader ¶
type LazyTemplateLoader interface {
Init()
// Load is called by the template registry to lazily load a template.
// It is intentional that this method does not return those templates. Instead, the template loader must
// register them with the registry. We do this to have a more flexible approach what the template loader needs to
// do when a template is requested.
Load(namespace Namespace, name TemplateName) error
}
type Logger ¶
type Logger interface {
// Debug logs a message at debug level with optional key-value pairs.
// This method is used for detailed troubleshooting information.
Debug(msg string, keysAndValues ...any)
// Info logs a message at info level with optional key-value pairs.
// This method is used for general operational information.
Info(msg string, keysAndValues ...any)
// Error logs a message at error level with optional key-value pairs.
// This method is used for error conditions that should be investigated.
Error(msg string, keysAndValues ...any)
}
Logger is the interface used by gotmx for all logging operations.
The Logger interface provides a consistent logging API within the gotmx library while allowing users to integrate their own logging implementations. This design enables gotmx to work with any logging library or framework that can be adapted to this interface.
Users can provide their own Logger implementation via WithLogger(). If no logger is provided, a default no-operation logger is used, which silently discards all log messages. The slog.Logger from the golang standard library can be used as-is.
Example usage with a custom logger:
type MyCustomLogger struct {
// Your logger fields
}
func (l *MyCustomLogger) Debug(msg string, keysAndValues ...any) {
// Your implementation
}
func (l *MyCustomLogger) Info(msg string, keysAndValues ...any) {
// Your implementation
}
func (l *MyCustomLogger) Error(msg string, keysAndValues ...any) {
// Your implementation
}
// Then in your application:
engine, _ := gotmx.New(gotmx.WithLogger(&MyCustomLogger{}))
type MaxNestingDepthExceededError ¶
type MaxNestingDepthExceededError struct {
TemplateName string // The template that was being rendered when the limit was exceeded
CurrentDepth int // The current nesting depth when the error occurred
MaxDepth int // The configured maximum allowed depth
}
MaxNestingDepthExceededError is returned when template nesting depth exceeds the configured limit. This typically indicates circular template references (e.g., template A uses template B, and B uses A) or excessively deep template hierarchies.
func (*MaxNestingDepthExceededError) Error ¶
func (e *MaxNestingDepthExceededError) Error() string
type ModelPathResolver ¶
type ModelPathResolver interface {
// TryResolve is a convenience function that attempts to extract a model value from an expression.
// If the expression is not recognized as a path, it returns false as the second argument.
// If it is a valid path, it returns the resolved model value as the first return value and true as the second return value.
// For example, the implementation in ModelPathResolverDefault returns false as the second return value if
// the expression does not start with "[[" and does not end with "]]".
TryResolve(expression string, data any) (any, bool)
// Resolve processes the given path and returns the corresponding value from the model.
// The path parameter should be the actual plain path, without any markers like
// "[[" prefix or "]]" suffix that are used in the ModelPathResolverDefault implementation.
Resolve(path string, data any) any
}
ModelPathResolver is an interface that handles the resolution of model paths within templates. It provides methods to extract and resolve values from data models using path expressions. The default implementation is ModelPathResolverDefault, which handles paths enclosed in [[ ]] delimiters.
func NewModelPathResolverDefault ¶
func NewModelPathResolverDefault( referenceResolver empaths.ReferenceResolver, ) ModelPathResolver
NewModelPathResolverDefault creates a new ModelPathResolverDefault
type ModelPathResolverDefault ¶
type ModelPathResolverDefault struct {
// contains filtered or unexported fields
}
ModelPathResolverDefault is a default implementation of ModelPathResolver. it uses the empaths library for resolving model paths: https://github.com/authentic-devel/empaths
func (*ModelPathResolverDefault) Resolve ¶
func (r *ModelPathResolverDefault) Resolve(path string, data any) any
Resolve processes the given path string and returns the corresponding value from the model. The path should be the actual plain path, without "[[" prefix or "]]" suffix.
func (*ModelPathResolverDefault) TryResolve ¶
func (r *ModelPathResolverDefault) TryResolve(path string, data any) (any, bool)
TryResolve attempts to extract a model value from an expression. It returns the resolved value and true if the expression is a valid path (starts with "[[" and ends with "]]"). Otherwise, it returns nil and false.
type Namespace ¶
type Namespace string
Namespace identifies the source of a template, typically the file path it was loaded from. Example: "components/button.html"
type NilComponentError ¶
type NilComponentError struct {
TemplateName TemplateRef
}
NilComponentError is returned when a template's NewRenderable method returns nil without an error. This indicates a bug in the template implementation.
func (*NilComponentError) Error ¶
func (e *NilComponentError) Error() string
type Option ¶
type Option func(*engineConfig) error
Option configures the Engine during construction.
func WithCustomRegistry ¶
func WithCustomRegistry(registry TemplateRegistry) Option
WithCustomRegistry allows using a custom TemplateRegistry implementation. Most users should not need this.
func WithCustomResolver ¶
func WithCustomResolver(resolver ModelPathResolver) Option
WithCustomResolver allows using a custom ModelPathResolver implementation. Most users should not need this.
func WithDeterministicOutput ¶
WithDeterministicOutput enables sorted attribute output for deterministic HTML. When enabled, HTML attributes are sorted alphabetically on each element. This is useful for testing where you need predictable output for assertions.
By default, attributes are rendered in map iteration order (faster, non-deterministic). Production code should leave this disabled for better performance.
Example:
// For tests: engine, _ := gotmx.New(gotmx.WithDeterministicOutput(true))
func WithDevDebounce ¶
WithDevDebounce sets the debounce duration for dev mode file watching. When template files change rapidly (e.g., during a save-all operation), the engine waits this long after the last change before reloading. This prevents redundant reloads. Default: 1 second. Only effective when WithDevMode(true) is set.
Example:
gotmx.WithDevDebounce(500 * time.Millisecond) // Faster feedback gotmx.WithDevDebounce(2 * time.Second) // Less churn
func WithDevMode ¶
WithDevMode enables or disables development mode with automatic template reloading. When enabled (true), the engine watches template directories for changes. Should be disabled (false) in production due to overhead. Accepts a boolean for easy integration with config flags:
gotmx.WithDevMode(config.IsDev)
gotmx.WithDevMode(os.Getenv("ENV") == "development")
func WithEagerExtensions ¶
WithEagerExtensions specifies which file extensions are parsed at startup. Templates in these files are loaded when New() is called and can be referenced by simple name (e.g., "button" instead of "components/button.htm#button"). Default: []string{".htm"}
func WithFS ¶
WithFS adds an embedded or virtual filesystem as a template source. IMPORTANT: The engine automatically walks the ENTIRE filesystem and discovers all template files (*.htm, *.html) at any depth. You do NOT need to specify where templates are located within the FS - they are found automatically.
This enables the "single embed.FS" pattern where one embed directive with glob patterns captures templates scattered across the codebase:
//go:embed */*.htm */*/*.htm */*/*.html var templateFs embed.FS
Can be called multiple times to add multiple filesystems if needed. Templates are loaded lazily on first access by default.
func WithIgnore ¶
WithIgnore specifies directory patterns to skip when loading templates. Commonly used to ignore "node_modules", "vendor", ".git", etc.
func WithLazyExtensions ¶
WithLazyExtensions specifies which file extensions are loaded on demand. Templates in these files are only parsed when first requested and must be referenced by fully qualified name (e.g., "pages/home.html#home-page"). This enables faster startup for large template sets. Default: []string{".html"}
func WithLogger ¶
WithLogger sets the logger for all engine components. The logger is automatically propagated to all internal components. Compatible with slog.Logger from the standard library.
func WithMaxNestingDepth ¶
WithMaxNestingDepth sets the maximum allowed template nesting depth. This prevents stack overflow from circular template references (e.g., template A uses B, and B uses A).
When a template uses another template via g-use or g-inner-use, the nesting depth increases. If the depth exceeds this limit, rendering will fail with MaxNestingDepthExceededError.
The default is 64, which is sufficient for most use cases while providing protection against accidental circular references. Set to 0 to disable the limit (not recommended).
Example:
// Allow deeper nesting for complex component hierarchies engine, _ := gotmx.New(gotmx.WithMaxNestingDepth(128)) // Use a lower limit for stricter protection engine, _ := gotmx.New(gotmx.WithMaxNestingDepth(32))
func WithReloadCallback ¶
WithReloadCallback sets a callback that is invoked after every dev mode template reload. If the reload succeeded, err is nil. If it failed, err describes what went wrong. The previous templates remain intact on failure (see atomic reload). Only effective when WithDevMode(true) is set.
Example:
gotmx.WithReloadCallback(func(err error) {
if err != nil {
log.Printf("Template reload failed: %v", err)
} else {
log.Println("Templates reloaded successfully")
}
})
func WithTemplateDir ¶
WithTemplateDir adds a directory as a template source. Can be called multiple times to add multiple directories - templates can be scattered across the codebase (e.g., next to their corresponding Go code). In dev mode: all directories are watched for changes. In prod mode: templates are loaded lazily on first access.
type RenderContext ¶
type RenderContext struct {
// Context is Go's standard context.Context for request cancellation, timeouts,
// and passing request-scoped values through the rendering pipeline.
// HTTP handlers should pass r.Context() to enable proper request lifecycle handling.
Context context.Context
// ResolveText resolves a value (which may contain model path expressions like "[[ .Name ]]")
// to a string. The value is evaluated against the provided data context.
// If escaped is true, HTML special characters in the result are escaped for XSS protection.
//
// Example: ResolveText("[[ .User.Name ]]", userData, true) might return "John & Jane"
ResolveText func(value string, data any, escaped bool) (string, error)
// ResolveValue resolves a value as a model path expression and returns the raw result.
// Returns (resolved value, true) if the value was a model path expression that was resolved,
// or (original value, false) if the value was not a model path expression.
//
// This is useful when you need the actual typed value rather than a string representation,
// for example when resolving iteration targets or conditional expressions.
//
// Example: ResolveValue("[[ .Items ]]", data) might return ([]Item{...}, true)
ResolveValue func(value string, data any) (any, bool)
// CreateRenderable creates a new Renderable for the given template reference.
// The template reference can be either a simple name (e.g., "myTemplate") or a
// fully qualified name with namespace (e.g., "templates/page.html#myTemplate").
//
// Returns an error if the template is not found or if component creation fails.
CreateRenderable func(name TemplateRef, data any) (Renderable, error)
// Logger provides logging capabilities during rendering for debug, info, and error messages.
// This is the same logger configured on the Engine instance.
Logger Logger
// CurrentNestingDepth tracks the current depth of nested template rendering.
// This is incremented when entering a nested template (via g-use) and decremented when exiting.
// Used to prevent stack overflow from circular template references.
CurrentNestingDepth int
// MaxNestingDepth is the maximum allowed nesting depth for template rendering.
// If CurrentNestingDepth reaches this limit, rendering will fail with MaxNestingDepthExceededError.
// A value of 0 means no limit (not recommended). Default is 64.
MaxNestingDepth int
// DeterministicOutput controls whether HTML attributes are sorted alphabetically.
// When true, attributes are sorted for predictable output (useful for testing).
// When false (default), attributes render in map iteration order (faster).
DeterministicOutput bool
// Escaped controls whether text content is HTML-escaped by default.
// This is set once at the start of rendering based on the render options.
// Individual attributes may override this — for example, g-inner-text always
// escapes regardless of this flag, while g-inner-html never escapes.
Escaped bool
// CurrentTemplate tracks the template being rendered, for error diagnostics.
// Updated when entering a new template via g-use or g-inner-use.
CurrentTemplate string
}
RenderContext provides the rendering capabilities needed during template rendering. A single context is created when rendering begins (via Engine.Render or Engine.RenderString) and passed by pointer through the entire rendering tree. This design:
- Avoids per-node allocations (only one context object is created per render call)
- Decouples Renderable implementations from Engine, breaking bi-directional coupling
- Makes testing easier by allowing mock implementations of individual functions
Implementations of Renderable should NOT create new RenderContext instances; they should pass the same context pointer to any child renderables they create or invoke.
type RenderError ¶
type RenderError struct {
Template string // The template being rendered (may be empty if unknown)
Element string // The HTML element tag name (e.g., "div", "span")
Attribute string // The attribute being processed when the error occurred (may be empty)
Cause error // The underlying error
}
RenderError provides context about where an error occurred during rendering. It includes the template name, element tag, and optionally the attribute that caused the error. This makes debugging template issues much easier by showing the exact location in the template hierarchy where the error occurred.
func (*RenderError) Error ¶
func (e *RenderError) Error() string
func (*RenderError) Unwrap ¶
func (e *RenderError) Unwrap() error
type RenderOption ¶
type RenderOption func(*renderConfig)
RenderOption configures individual render calls.
func Slot ¶
func Slot(name string, content any) RenderOption
Slot is a convenience function for creating a single-slot Slots map.
func Unescaped ¶
func Unescaped() RenderOption
Unescaped disables HTML escaping for the render call. Use only when rendering trusted content. By default, all text is HTML-escaped.
Example:
engine.Render(ctx, w, "template", trustedData, gotmx.Unescaped())
Example ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`<div data-g-define="raw" data-g-inner-html="[[ .Html ]]">placeholder</div>`)
// Use g-inner-html for trusted HTML content
engine.Render(context.Background(), os.Stdout, "raw", map[string]any{
"Html": "<strong>Bold</strong>",
})
}
Output: <div><strong>Bold</strong></div>
func WithLayout ¶
func WithLayout(layoutTemplate string, layoutData any) RenderOption
WithLayout wraps the rendered template inside a layout template. The rendered content is placed into the layout's default slot (empty name). Use WithLayoutSlot to target a named slot instead.
Example:
engine.Render(ctx, w, "dashboard-page", pageData,
gotmx.WithLayout("main-layout", layoutData),
)
Example ¶
package main
import (
"context"
"os"
"github.com/authentic-devel/gotmx"
)
func main() {
engine, _ := gotmx.New()
engine.LoadHTML(`
<html data-g-define="base"><head><title data-g-inner-text="[[ .Title ]]">T</title></head><body data-g-define-slot="">default</body></html>
<article data-g-define="page">Welcome!</article>
`)
engine.Render(context.Background(), os.Stdout, "page", nil,
gotmx.WithLayout("base", map[string]any{"Title": "Home"}),
)
}
Output: <!DOCTYPE html> <html><head><title>Home</title></head><body><article>Welcome!</article></body></html>
func WithLayoutSlot ¶
func WithLayoutSlot(slotName string) RenderOption
WithLayoutSlot sets which slot in the layout receives the rendered content. By default, content goes to the default slot (empty name). Must be used together with WithLayout.
Example:
engine.Render(ctx, w, "dashboard-page", pageData,
gotmx.WithLayout("main-layout", layoutData),
gotmx.WithLayoutSlot("content"),
)
func WithSlots ¶
func WithSlots(slots Slots) RenderOption
WithSlots provides slot content for the render. Slot values can be:
- string: rendered as HTML content
- Renderable: rendered using its Render method
- []Renderable: all items rendered in sequence
type RenderType ¶
type RenderType int
RenderType controls whether a Renderable outputs its outer HTML tags or only its content.
const ( // RenderInner renders only the inner content of the element, without the opening/closing tags. // Used by g-inner-use to embed a template's content without its root element. RenderInner RenderType = iota // RenderOuter renders the complete element including its opening and closing tags. // This is the default render type used for most rendering operations. RenderOuter )
type Renderable ¶
type Renderable interface {
// Render outputs the HTML representation to the provided writer.
//
// Parameters:
// - ctx: The render context providing resolution and component creation functions.
// This is created once per render call and should be passed unchanged to children.
// Escaping is controlled by ctx.Escaped.
// - writer: Destination for the rendered HTML output.
// - renderType: Controls whether to render outer tags (RenderOuter) or content only (RenderInner).
//
// Returns an error if rendering fails at any point.
Render(ctx *RenderContext, writer io.Writer, renderType RenderType) error
}
Renderable represents something that can be rendered to HTML output. Built-in implementations handle HTML nodes, text content, Go templates, and literal strings.
The RenderContext parameter is created once at the start of rendering and passed through the entire rendering tree. Implementations should pass this same context to any child renderables they invoke, without creating new context instances.
Escaping behavior is controlled by ctx.Escaped rather than a method parameter. Specific attributes enforce their own escaping rules: g-inner-text always escapes, g-inner-html never escapes, and attribute values always escape.
type Slots ¶
Slots is a map of slot names to content. Content can be:
- string: rendered as HTML content
- Renderable: rendered using its Render method
- []Renderable: all items rendered in sequence
Slots supports a fluent API for building slot content:
slots := gotmx.Slots{}.
Set("header", headerComponent).
Set("content", bodyComponent).
Add("sidebar", widget1).
Add("sidebar", widget2)
type SlottedRenderables ¶
type SlottedRenderables map[string][]Renderable
SlottedRenderables maps slot names to their renderable content.
type Template ¶
type Template interface {
// Name returns the unique name of the template within its namespace.
// This must always return a constant value. Unpredictable behavior will occur if it returns
// a different value after the template has been registered.
Name() TemplateName
// Namespace returns the namespace of the template. Within a namespace, the name must be unique.
// Typically, the namespace is the file path the template came from, e.g., an HTML file.
// The fully qualified name of a template is the namespace and the name separated by a hash,
// for example "frontend/index.html#myTemplate".
// This must always return a constant value. Unpredictable behavior will occur if it returns
// a different value after the template has been registered.
Namespace() Namespace
// NewRenderable creates a new Renderable instance from this template with the given data.
// The data parameter provides the model context for rendering.
NewRenderable(data any) (Renderable, error)
}
Template is a blueprint for creating Renderable instances. Each Template has a unique name within its namespace and can create Renderable instances with the provided data.
func NewStringLiteralTemplate ¶
func NewStringLiteralTemplate(name TemplateName, literal string, namespace Namespace) Template
NewStringLiteralTemplate creates a template that is defined by a string literal. When rendered, it does not substitute any placeholders. The string literal is rendered as is.
type TemplateLoadError ¶
type TemplateLoadError struct {
TemplateName TemplateName
Namespace Namespace
Cause error
}
TemplateLoadError is returned when a template fails to load. It wraps the underlying error and provides context about which template failed.
func (*TemplateLoadError) Error ¶
func (e *TemplateLoadError) Error() string
func (*TemplateLoadError) Unwrap ¶
func (e *TemplateLoadError) Unwrap() error
type TemplateName ¶
type TemplateName string
TemplateName is the simple name of a template within its namespace. Examples: "my-button", "dashboard-page"
func (TemplateName) String ¶
func (n TemplateName) String() string
type TemplateNotFoundError ¶
type TemplateNotFoundError struct {
Name TemplateName
Namespace Namespace
Available []TemplateName
DidYouMean TemplateName
}
TemplateNotFoundError is returned when a template cannot be found in the registry. It includes helpful context for debugging: the list of available templates and a "did you mean" suggestion based on Levenshtein distance.
func (*TemplateNotFoundError) Error ¶
func (e *TemplateNotFoundError) Error() string
type TemplateRef ¶
type TemplateRef string
TemplateRef is a reference to a template, either simple or fully qualified with namespace. Simple: "my-button" Qualified: "components/button.html#my-button"
func (TemplateRef) String ¶
func (n TemplateRef) String() string
type TemplateRegistry ¶
type TemplateRegistry interface {
// GetTemplate returns the template with the given name or an error if no
// matching template exists or if the template name is ambiguous.
GetTemplate(ref TemplateRef) (Template, error)
// RegisterTemplate adds a new template to the registry.
// Returns an error if the template cannot be registered (e.g., duplicate template name in the same namespace).
RegisterTemplate(template Template) error
// ClearTemplates removes all templates from the registry.
ClearTemplates()
SetLazyTemplateLoader(ltl LazyTemplateLoader)
}
TemplateRegistry is an interface for managing templates within the gotmx engine. It provides methods for registering, retrieving, and managing templates.
type TemplateRegistryDefault ¶
type TemplateRegistryDefault struct {
// contains filtered or unexported fields
}
TemplateRegistryDefault is a default implementation of TemplateRegistry and also implements GoTemplateRegistry It supports
- registering any type of template that implements the Template interface
- parsing HTML files from the filesystem or other sources using the golang builtin HTML parser and automatically registering any found template definitions as NodeTemplate
- registering golang templates
Thread Safety: This registry is safe for concurrent use. All template access and registration operations are protected by an internal mutex.
func NewTemplateRegistryDefault ¶
func NewTemplateRegistryDefault() *TemplateRegistryDefault
NewTemplateRegistryDefault creates a new ModelPathResolverDefault
func (*TemplateRegistryDefault) ClearTemplates ¶
func (tr *TemplateRegistryDefault) ClearTemplates()
func (*TemplateRegistryDefault) GetTemplate ¶
func (tr *TemplateRegistryDefault) GetTemplate(templateRef TemplateRef) (Template, error)
GetTemplate returns the template with the given name or an error if no matching template exists or if the template name is ambiguous. The name can either be an unqualified template name without namespace like "myTemplate" or a fully qualified name with namespace like "frontend/templates/index.html#myTemplate". If an unqualified name is given, then that name must be globally unique across all namespaces. Otherwise, an error will be returned.
func (*TemplateRegistryDefault) GetTemplateExt ¶
func (tr *TemplateRegistryDefault) GetTemplateExt(namespace Namespace, templateName TemplateName) (Template, error)
GetTemplateExt returns a template by namespace and name, with extended lookup logic. Returns an error if the template is not found, is ambiguous, or if loading fails.
func (*TemplateRegistryDefault) LogTemplates ¶
func (tr *TemplateRegistryDefault) LogTemplates()
func (*TemplateRegistryDefault) RegisterFunc ¶
func (tr *TemplateRegistryDefault) RegisterFunc(name string, f interface{})
func (*TemplateRegistryDefault) RegisterGoTemplate ¶
func (tr *TemplateRegistryDefault) RegisterGoTemplate(name TemplateName, template string, sourceFile string) error
func (*TemplateRegistryDefault) RegisterTemplate ¶
func (tr *TemplateRegistryDefault) RegisterTemplate(template Template) error
func (*TemplateRegistryDefault) ReplaceFrom ¶
func (tr *TemplateRegistryDefault) ReplaceFrom(source *TemplateRegistryDefault)
ReplaceFrom atomically replaces all templates in this registry with the templates from source. This is used by dev mode to perform zero-downtime reloads: a new registry is built in isolation, then swapped in with a single write-lock acquisition. Concurrent readers never see an empty registry.
func (*TemplateRegistryDefault) SetLazyTemplateLoader ¶
func (tr *TemplateRegistryDefault) SetLazyTemplateLoader(ltl LazyTemplateLoader)
func (*TemplateRegistryDefault) SetLogger ¶
func (tr *TemplateRegistryDefault) SetLogger(logger Logger)
type TemplateRetrievalError ¶
type TemplateRetrievalError struct {
TemplateName TemplateRef
Cause error
}
TemplateRetrievalError is returned when there's a failure retrieving a template from the registry. It wraps the underlying error (which could be TemplateNotFoundError, AmbiguousTemplateError, etc.).
func (*TemplateRetrievalError) Error ¶
func (e *TemplateRetrievalError) Error() string
func (*TemplateRetrievalError) Unwrap ¶
func (e *TemplateRetrievalError) Unwrap() error
type TemplateSourceNotFoundError ¶
type TemplateSourceNotFoundError struct {
Name TemplateName
Namespace Namespace
Sources []string // Description of sources that were searched
}
TemplateSourceNotFoundError is returned when a template cannot be found in any of the configured template sources (file systems or directories).
func (*TemplateSourceNotFoundError) Error ¶
func (e *TemplateSourceNotFoundError) Error() string
type VoidElementChildError ¶
type VoidElementChildError struct {
Element string // The void element tag name (e.g., "br", "img")
}
VoidElementChildError is returned when a void HTML element (like <br>, <img>, etc.) unexpectedly has child nodes. Void elements cannot have children according to the HTML spec.
func (*VoidElementChildError) Error ¶
func (e *VoidElementChildError) Error() string
Source Files
¶
- attr_composition.go
- attr_constants.go
- attr_content.go
- attr_control_flow.go
- attr_helpers.go
- attr_iteration.go
- attr_transform.go
- engine.go
- engine_loading.go
- engine_options.go
- engine_render.go
- engine_slots.go
- errors.go
- golang_template.go
- gotmx.go
- html_boolean_attrs.go
- htmlsupport.go
- logger.go
- model_resolver.go
- model_resolver_default.go
- node_component.go
- node_template.go
- renderable.go
- stringliteral_template.go
- template_loader.go
- template_loader_html.go
- template_loader_htmldev.go
- template_loader_string.go
- template_registry.go
- template_registry_default.go
- testsupport.go
- text_component.go