Learn about Parka's flexible template rendering system for HTML and Markdown templates with features like template lookup, reloading, and directory-based serving
Parka provides a flexible and powerful template rendering system that supports both HTML and Markdown templates, with features like template lookup, reloading, and directory-based serving. This document explains how the template system works and how to use it effectively.
The template rendering system in Parka is built around several key concepts:
The template lookup system is the foundation of Parka's template rendering. It's designed to provide a flexible way to load templates from different sources while supporting development and production needs.
type TemplateLookup interface {
// Lookup returns a template by name. If there are multiple names given,
// implementations may choose how to handle them.
Lookup(name ...string) (*template.Template, error)
// Reload reloads all or partial templates. This is useful for development
// where templates might change without server restart.
Reload(name ...string) error
}
The Renderer's lookup process follows these steps:
Template Name Resolution:
// From renderer.go
t, err := r.LookupTemplate(templateName+".tmpl.md", templateName+".md", templateName)
if err != nil {
return errors.Wrap(err, "error looking up template")
}
<name>.tmpl.md for markdown templates<name>.md for static markdown<name> directly<name>.tmpl.html and <name>.htmlBase Template Resolution (for markdown):
baseTemplate, err := r.LookupTemplate(r.MarkdownBaseTemplateName)
if err != nil {
return errors.Wrap(err, "error looking up base template")
}
Template Chain Search:
LookupTemplateFromFile)Loads a single template file and optionally restricts it to a specific template name.
// Basic usage
lookup := render.NewLookupTemplateFromFile("templates/index.tmpl.html", "")
tmpl, err := lookup.Lookup("index.tmpl.html")
// With specific template name
lookup := render.NewLookupTemplateFromFile("templates/base.tmpl.html", "base")
tmpl, err := lookup.Lookup("base") // Only responds to "base"
Example from tests:
// Create a file-based lookup that always returns the same file
lookup := NewLookupTemplateFromFile("templates/tests/test.txt", "")
tmpl, err := lookup.Lookup("any-name.txt") // Will return test.txt content
if err != nil {
log.Fatal(err)
}
LookupTemplateFromDirectory)Loads templates from a directory, reloading on every request.
// Basic usage
lookup := render.NewLookupTemplateFromDirectory("./templates")
tmpl, err := lookup.Lookup("pages/index.html")
// With trailing slash handling
lookup := render.NewLookupTemplateFromDirectory("templates/")
tmpl, err := lookup.Lookup("index.html")
Example from tests:
// Create a directory-based lookup
lookup := NewLookupTemplateFromDirectory("templates/tests")
tmpl, err := lookup.Lookup("test.txt")
if err != nil {
log.Fatal(err)
}
// Execute the template
buf := new(bytes.Buffer)
err = tmpl.Execute(buf, nil)
fmt.Println(buf.String()) // Output: template content
LookupTemplateFromFS)Most flexible implementation, supporting embedded files and pattern matching.
// Basic usage with default patterns (*.html)
lookup := render.NewLookupTemplateFromFS(
render.WithFS(os.DirFS("./templates")),
)
// With custom patterns and base directory
lookup := render.NewLookupTemplateFromFS(
render.WithFS(os.DirFS("./content")),
render.WithBaseDir("pages"),
render.WithPatterns("*.md", "*.html"),
render.WithAlwaysReload(true),
)
Example from tests:
// Create a filesystem-based lookup with specific patterns
lookup := NewLookupTemplateFromFS(
WithFS(os.DirFS("templates/tests")),
WithPatterns("*.html"),
)
tmpl, err := lookup.Lookup("test.html")
if err != nil {
log.Fatal(err)
}
// With base directory
lookup := NewLookupTemplateFromFS(
WithFS(os.DirFS("templates")),
WithBaseDir("tests"),
WithPatterns("*.html"),
)
Each implementation handles reloading differently:
File-based: Always reloads on lookup
func (l *LookupTemplateFromFile) Reload(name ...string) error {
return nil // No need to reload, happens on every lookup
}
Directory-based: Always reloads on lookup
func (l *LookupTemplateFromDirectory) Reload(_ ...string) error {
return nil // No need to reload, happens on every lookup
}
Filesystem-based: Configurable reloading
// Configure reloading
lookup := NewLookupTemplateFromFS(
WithAlwaysReload(true), // Reload on every lookup
)
// Manual reload
err := lookup.Reload()
if err != nil {
log.Fatal(err)
}
Example of dynamic template reloading from tests:
func TestLookupTemplateFromFS_Reload(t *testing.T) {
l := NewLookupTemplateFromFS()
// Copy a template file
err := copyFile("templates/tests/test.html", "templates/tests/test_tmpl.html")
require.NoError(t, err)
// Reload to pick up the new file
err = l.Reload()
require.NoError(t, err)
// Lookup should now find the new template
tmpl, err := l.Lookup("templates/tests/test_tmpl.html")
require.NoError(t, err)
// Clean up
os.Remove("templates/tests/test_tmpl.html")
}
The rendering process in Parka follows these steps:
Template Resolution:
<name>.tmpl.md<name>.md<name>.tmpl.html<name>.htmlTemplate Processing:
Data Injection:
Parka provides two main types of template handlers for serving templates over HTTP:
TemplateHandler): For serving individual template filesTemplateDirHandler): For serving multiple templates from a directoryFor detailed information about these handlers, including their structure, configuration options, and usage examples, see the Template Handlers Documentation.
When developing with Parka templates, you can enable several features to make development easier:
Template Organization:
.tmpl.html for HTML templates that need processing.html for static HTML files.tmpl.md for markdown templates that need processing.md for static markdown filesTemplate Lookup Configuration:
LookupTemplateFromFS for productionLookupTemplateFromDirectory for developmentLookupTemplateFromFile for single-file casesPerformance Optimization:
alwaysReload in productionDevelopment Workflow:
alwaysReload during developmentThis section shows how to configure template handlers using Parka's configuration file system. For detailed information about the handlers themselves, refer to the Template Handlers Documentation.
routes:
- path: "/about"
template:
templateFile: "about.tmpl.html"
alwaysReload: true
routes:
- path: "/docs"
templateDirectory:
localDirectory: "./docs"
indexTemplateName: "index.tmpl.html"
markdownBaseTemplateName: "base.tmpl.html"
alwaysReload: true
Here's a complete example showing various template configurations:
defaults:
renderer:
useDefaultParkaRenderer: true
templateDirectory: "./templates"
markdownBaseTemplateName: "base.tmpl.html"
routes:
# Serve a single template
- path: "/"
template:
templateFile: "index.tmpl.html"
alwaysReload: true
# Serve a documentation directory
- path: "/docs"
templateDirectory:
localDirectory: "./docs"
indexTemplateName: "index.tmpl.html"
markdownBaseTemplateName: "docs-base.tmpl.html"
alwaysReload: true
# Serve API documentation
- path: "/api"
template:
templateFile: "api.tmpl.html"
data:
title: "API Documentation"
version: "1.0"
And here's the equivalent programmatic setup:
package main
import (
"github.com/go-go-golems/parka/pkg/render"
"github.com/go-go-golems/parka/pkg/handlers/template"
"github.com/go-go-golems/parka/pkg/handlers/template-dir"
"github.com/go-go-golems/parka/pkg/server"
)
func main() {
// Create a new server
server, err := server.NewServer()
if err != nil {
panic(err)
}
// Set up default renderer
defaultRenderer, err := render.NewRenderer(
render.WithMarkdownBaseTemplateName("base.tmpl.html"),
)
if err != nil {
panic(err)
}
// Set up index page
indexHandler := template.NewTemplateHandler(
"index.tmpl.html",
template.WithAlwaysReload(true),
)
indexHandler.Serve(server, "/")
// Set up documentation pages
docsHandler := template_dir.NewTemplateDirHandler(
template_dir.WithLocalDirectory("./docs"),
template_dir.WithAlwaysReload(true),
template_dir.WithAppendRendererOptions(
render.WithMarkdownBaseTemplateName("docs-base.tmpl.html"),
render.WithIndexTemplateName("index.tmpl.html"),
),
)
docsHandler.Serve(server, "/docs")
// Set up API documentation
apiHandler := template.NewTemplateHandler(
"api.tmpl.html",
template.WithAppendRendererOptions(
render.WithMergeData(map[string]interface{}{
"title": "API Documentation",
"version": "1.0",
}),
),
)
apiHandler.Serve(server, "/api")
// Start the server
if err := server.Start(":8080"); err != nil {
panic(err)
}
}
Parka's template rendering system provides a flexible and powerful way to serve both HTML and Markdown content. By understanding the different components and their interactions, you can effectively use templates in your Parka applications while maintaining good development practices and performance considerations.