Documentation
¶
Overview ¶
Package blocks renders HTML pages built from a layout and a content template, on top of the standard html/template.
A page is one file. A layout is a file that says where the page goes, with the yield action or with the content template action:
<!-- views/layouts/main.html -->
<html>
<head><title>{{ .Title }}</title></head>
<body>{{ yield . }}</body>
</html>
<!-- views/index.html -->
<h1>{{ .Title }}</h1>
Every page is parsed into every layout, so any page can render under any layout:
views := blocks.New("./views")
if err := views.Load(); err != nil {
return err
}
err := views.ExecuteTemplate(w, "index", "main", map[string]any{"Title": "Home"})
Where templates come from ¶
New accepts a directory name, any fs.FS (an embed.FS included), or a MemoryFileSystem for templates built at runtime. Embedding is what most programs want, because the binary then carries its own templates:
//go:embed views
var embedded embed.FS
views := blocks.New(embedded).RootDir("views")
Names ¶
A template is named by its path under the root directory, without the extension, so "views/admin/users.html" is "admin/users". Layouts live in "layouts" by default, and that directory is trimmed from the name: "views/layouts/main.html" is "main". Both names may be given with the extension, and a layout with its directory, so "layouts/main.html" works too.
Blocks, partials and functions ¶
A layout can leave a hole with the block action, and a page can fill it:
<title>{{ block "title" . }}Default{{ end }}</title> in the layout
{{ define "title" }}Specific{{ end }} in the page
The partial function renders another template in place, which is how a footer or a card is reused:
<footer>{{ partial "partials/footer" . }}</footer>
Funcs adds functions for one engine, LayoutFuncs adds functions only layouts can call, and Register adds functions every engine gets. A function map is read when Load runs, so add the functions first.
What happens to a file before it is parsed ¶
Leading and trailing whitespace is trimmed. HTML comments are removed, which has to happen because the template parser reads a define inside a comment as a real one. A file with the .md extension is rendered from markdown to HTML first, and other extensions can be handled by registering a parser with Extensions.
Loading and reloading ¶
Load parses everything once. Reload(true) reparses on every render, for development. A reload builds a new set of templates and swaps it in, so a render already in flight keeps the set it started with, and a reparse that fails leaves the working templates in place and returns the error.
An engine is safe for concurrent use once loaded. Configure it, load it, then serve.
Example ¶
Loading templates from an embed.FS. RootDir selects the directory inside it, so the names come out as "index" and "about" rather than "testdata/views/index".
package main
import (
"embed"
"log"
"os"
"github.com/kataras/blocks"
)
// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS
func main() {
views := blocks.New(embedded).RootDir("testdata/views")
if err := views.Load(); err != nil {
log.Fatal(err)
}
data := map[string]any{"Title": "Home", "Name": "Gopher", "Year": 2026}
if err := views.ExecuteTemplate(os.Stdout, "index", "main", data); err != nil {
log.Fatal(err)
}
}
Output: <!DOCTYPE html> <html lang="en"> <head><title>Home</title></head> <body> <h1>Home</h1> <p>Welcome, Gopher.</p> <footer><small>2026 Blocks</small></footer> </body> </html>
Index ¶
- Variables
- func Register(funcMap template.FuncMap)
- func Set(v *Blocks) func(http.Handler) http.Handler
- type Blocks
- func (v *Blocks) AddFunc(funcName string, fn any)
- func (v *Blocks) DefaultLayout(layoutName string) *Blocks
- func (v *Blocks) Delims(left, right string) *Blocks
- func (v *Blocks) ExecuteTemplate(w io.Writer, tmplName, layoutName string, data any) error
- func (v *Blocks) Ext() string
- func (v *Blocks) Extension(ext string) *Blocks
- func (v *Blocks) Extensions(ext string, parser ExtensionParser) *Blocks
- func (v *Blocks) Funcs(funcMap template.FuncMap) *Blocks
- func (v *Blocks) LayoutDir(relToDirLayoutDir string) *Blocks
- func (v *Blocks) LayoutFuncs(funcMap template.FuncMap) *Blocks
- func (v *Blocks) LayoutNames() iter.Seq[string]
- func (v *Blocks) Load() error
- func (v *Blocks) LoadWithContext(ctx context.Context) error
- func (v *Blocks) Lookup(tmplName, layoutName string) *template.Template
- func (v *Blocks) Option(opt ...string) *Blocks
- func (v *Blocks) PartialFunc(partialName string, data any) (template.HTML, error)
- func (v *Blocks) Reload(b bool) *Blocks
- func (v *Blocks) RootDir(root string) *Blocks
- func (v *Blocks) TemplateNames() iter.Seq[string]
- func (v *Blocks) TemplateString(tmplName, layoutName string, data any) (string, error)
- type ContextKeyType
- type ErrNotExist
- type ExtensionParser
- type FuncMapper
- type LoadError
- type MemoryFileSystem
- func (mfs *MemoryFileSystem) Open(name string) (fs.File, error)
- func (mfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) error
- func (mfs *MemoryFileSystem) ReadDir(name string) ([]fs.DirEntry, error)
- func (mfs *MemoryFileSystem) ReadFile(name string) ([]byte, error)
- func (mfs *MemoryFileSystem) Stat(name string) (fs.FileInfo, error)
- func (mfs *MemoryFileSystem) TemplateFuncs() template.FuncMap
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoTemplates = errors.New("blocks: no template files found")
ErrNoTemplates is returned by Load when the file system holds no file the engine can use, which usually means the directory or the extension is wrong.
Functions ¶
func Register ¶
Register adds functions that every engine can use, the way database/sql.Register adds a driver. Call it from an init function, before the engines are created.
A value is usually a plain function, as html/template wants. Two other forms are accepted, for a function that needs the engine it belongs to:
func(*Blocks) any // returns the function to register func(*Blocks) template.FuncMap // returns a whole map, resolved the same way
Overwriting a name that is already registered is allowed, including the built-in partial. The functions are picked up by the next Load of each engine.
Usage:
package myfuncs
func init() {
blocks.Register(template.FuncMap{"year": func() int { return time.Now().Year() }})
}
The program then imports that package for its side effect, and every engine has the function:
import _ "myfuncs"
func Set ¶
Set returns middleware that puts this engine in the request's context, for a program that serves different groups of routes from different engines.
See Get.
Example ¶
Different engines for different groups of routes, reached through the request context.
package main
import (
"embed"
"fmt"
"log"
"net/http"
"net/http/httptest"
"github.com/kataras/blocks"
)
// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS
func main() {
admin := blocks.New(embedded).RootDir("testdata/views")
if err := admin.Load(); err != nil {
log.Fatal(err)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
views := blocks.Get(r)
data := map[string]any{"Title": "Admin", "Year": 2026}
if err := views.ExecuteTemplate(w, "about", "main", data); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
})
w := httptest.NewRecorder()
blocks.Set(admin)(handler).ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/admin", nil))
fmt.Println(w.Code)
}
Output: 200
Types ¶
type Blocks ¶
type Blocks struct {
// contains filtered or unexported fields
}
Blocks parses a set of template files and renders them, on their own or inside a layout. Create one with New, configure it, then call Load once before rendering.
A Blocks is safe for concurrent use once loaded, and Reload(true) stays safe: a reload builds a new set of templates and swaps it in, so a render already in flight keeps the set it started with.
func New ¶
New returns an engine that loads templates from fsOrDir, which may be:
- a directory name, such as "./views"
- an fs.FS, including an embed.FS and anything fs.Sub returns
- a *MemoryFileSystem, for templates built at runtime
Embedding is the usual choice for a program that ships as a single binary:
//go:embed views
var embedded embed.FS
views := blocks.New(embedded).RootDir("views")
The engine is configured through its methods, which return the engine so calls can be chained, and which must be called before Load. Load parses the files, and nothing renders until it has run.
Functions registered with the package level Register are inherited by every engine. Use Funcs for functions this engine alone should have.
It panics if fsOrDir is none of the types above.
Example (Directory) ¶
A directory on disk, the shape most programs start with. DefaultLayout means ExecuteTemplate can be called with an empty layout name.
package main
import (
"log"
"os"
"github.com/kataras/blocks"
)
func main() {
views := blocks.New("./testdata/views").DefaultLayout("main")
if err := views.Load(); err != nil {
log.Fatal(err)
}
data := map[string]any{"Title": "About", "Year": 2026}
if err := views.ExecuteTemplate(os.Stdout, "about", "", data); err != nil {
log.Fatal(err)
}
}
Output: <!DOCTYPE html> <html lang="en"> <head><title>About</title></head> <body> <h1>About</h1> <footer><small>2026 Blocks</small></footer> </body> </html>
func (*Blocks) AddFunc ¶ added in v0.0.10
AddFunc adds one function, the way Funcs adds a map. It takes effect at the next Load.
func (*Blocks) DefaultLayout ¶
DefaultLayout sets the layout ExecuteTemplate uses when it is given an empty one.
func (*Blocks) Delims ¶
Delims sets the action delimiters, which default to "{{" and "}}". An empty delimiter keeps its default. Layout detection follows the delimiters, so a layout written with custom ones is still recognized.
It panics if a delimiter is not valid UTF-8. The delimiters become part of the expressions that read each file, and an expression cannot hold an invalid rune.
func (*Blocks) ExecuteTemplate ¶
ExecuteTemplate renders the template named tmplName into w, inside the layout named layoutName, with data as the template's dot.
An empty layoutName falls back to the one given to DefaultLayout, and when that is empty too the template renders on its own. Either name may be given with or without the file extension, and a layout may be named with or without its directory.
The error is an ErrNotExist when a name matches nothing loaded. Output may already have been written to w when an execution error comes back.
It is safe to call from many goroutines. If they share a w, their output interleaves.
Example ¶
Rendering from an HTTP handler. Set the content type yourself: the engine writes the body and nothing else.
package main
import (
"embed"
"fmt"
"log"
"net/http"
"net/http/httptest"
"github.com/kataras/blocks"
)
// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS
func main() {
views := blocks.New(embedded).RootDir("testdata/views")
if err := views.Load(); err != nil {
log.Fatal(err)
}
handler := func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
data := map[string]any{"Title": "Home", "Name": "Gopher", "Year": 2026}
if err := views.ExecuteTemplate(w, "index", "main", data); err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
}
w := httptest.NewRecorder()
handler(w, httptest.NewRequest(http.MethodGet, "/", nil))
fmt.Println(w.Code, w.Header().Get("Content-Type"))
}
Output: 200 text/html; charset=utf-8
func (*Blocks) Extension ¶
Extension sets the template file extension, with its dot. It defaults to ".html".
func (*Blocks) Extensions ¶
func (v *Blocks) Extensions(ext string, parser ExtensionParser) *Blocks
Extensions registers a parser that runs on a file's contents before they are parsed as a template. The extension starts with a dot, such as ".md".
One entry is registered by default, ".md", which renders markdown to HTML. Pass a nil parser to drop an extension, after which files carrying it are ignored unless it is the engine's own extension.
func (*Blocks) Funcs ¶
Funcs adds functions that every template can call, layouts included. The map is copied, so the caller may keep using it. Overwriting a name that is already there is allowed.
The functions take effect at the next Load: templates that are already parsed keep the functions they were parsed with.
It panics if a value is not a function with a return type html/template accepts.
func (*Blocks) LayoutDir ¶
LayoutDir sets the directory that holds the layouts, relative to the root one. It defaults to "layouts".
The directory is trimmed from a layout's name, so the file "layouts/main.html" is the layout "main". Naming it in full works too.
func (*Blocks) LayoutFuncs ¶
LayoutFuncs adds functions that only layouts can call. The map is copied.
A name added here can be overridden by Funcs, which is applied after it.
It panics if a value is not a function with a return type html/template accepts.
func (*Blocks) LayoutNames ¶ added in v0.1.0
LayoutNames iterates the loaded layout names in order.
func (*Blocks) Load ¶
Load parses the templates, including the layouts, into the engine. Call it once after configuring the engine and before rendering anything.
If it fails, the templates from the previous successful Load keep working.
func (*Blocks) LoadWithContext ¶
LoadWithContext is Load with a context, for a file system slow enough to be worth cancelling: a network mount, or a very large directory.
func (*Blocks) Lookup ¶ added in v0.1.0
Lookup returns the parsed template for a name, or nil when there is none. Give an empty layoutName for the template on its own. Names are normalized the way ExecuteTemplate normalizes them.
The returned template is the live one. Read it and execute it, but do not reparse or reconfigure it: it is shared with every other render, and the next Load replaces it.
Example ¶
Lookup reaches the parsed template, for rendering one block on its own. Treat what it returns as read-only.
package main
import (
"embed"
"log"
"os"
"github.com/kataras/blocks"
)
// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS
func main() {
views := blocks.New(embedded).RootDir("testdata/views")
if err := views.Load(); err != nil {
log.Fatal(err)
}
tmpl := views.Lookup("partials/footer", "")
if err := tmpl.ExecuteTemplate(os.Stdout, "content", map[string]any{"Year": 2026}); err != nil {
log.Fatal(err)
}
}
Output: <small>2026 Blocks</small>
func (*Blocks) Option ¶
Option sets options for the templates, each a plain string or "key=value". The options reach every template, layouts included.
The one option html/template defines is missingkey, which says what happens when a map is indexed with a key it does not hold:
"missingkey=default" or "missingkey=invalid" Do nothing and continue. A printed value becomes "<no value>". "missingkey=zero" Use the zero value for the map's element type. "missingkey=error" Stop execution with an error.
It panics if an option string is not recognized.
func (*Blocks) PartialFunc ¶
PartialFunc renders a template's content block and returns it as HTML, which is what the partial template function calls.
func (*Blocks) Reload ¶
Reload makes ExecuteTemplate reparse the templates on every call, which is what you want while editing them and not in production. A failed reparse keeps the templates that already work and returns the error.
func (*Blocks) RootDir ¶ added in v0.0.3
RootDir sets the directory inside the file system that holds the templates. Everything outside it is ignored, and template names are relative to it.
The directory is a slash separated path. A leading slash, a leading "./" and a trailing slash are all accepted, so "views", "/views", "./views" and "views/" name the same directory. An empty value, "/" or "." means the whole file system.
It panics if the path cannot name a directory, such as one that climbs out of the file system with "..".
func (*Blocks) TemplateNames ¶ added in v0.1.0
TemplateNames iterates the loaded template names in order.
Example ¶
What was loaded, by name.
package main
import (
"embed"
"fmt"
"log"
"github.com/kataras/blocks"
)
// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS
func main() {
views := blocks.New(embedded).RootDir("testdata/views")
if err := views.Load(); err != nil {
log.Fatal(err)
}
for name := range views.TemplateNames() {
fmt.Println(name)
}
for name := range views.LayoutNames() {
fmt.Println("layout:", name)
}
}
Output: about index partials/footer layout: main
func (*Blocks) TemplateString ¶ added in v0.0.11
TemplateString renders a template and returns the result, the way ExecuteTemplate renders it into a writer.
It does not reload in Reload mode. Call Load first when the files may have changed.
type ContextKeyType ¶
type ContextKeyType struct{}
ContextKeyType is the type of the request context value that carries an engine.
See Set and Get.
var ContextKey ContextKeyType
ContextKey is the request context key an engine is stored under.
See Set and Get.
type ErrNotExist ¶
type ErrNotExist struct {
// Name is the template that was asked for.
Name string
// Layout is the layout that was asked for, empty when there was none.
Layout string
// IsLayout reports whether the layout is the part that is missing.
IsLayout bool
}
ErrNotExist reports that a template or a layout is not among the parsed ones. It matches fs.ErrNotExist, so errors.Is(err, fs.ErrNotExist) is true.
func (ErrNotExist) Error ¶
func (e ErrNotExist) Error() string
Error implements the error interface.
func (ErrNotExist) Is ¶ added in v0.1.0
func (e ErrNotExist) Is(target error) bool
Is reports that this error matches fs.ErrNotExist.
type ExtensionParser ¶
ExtensionParser converts a file's contents before they are parsed as a template. It is how a file that is not HTML, such as markdown, becomes one.
type FuncMapper ¶ added in v0.1.0
FuncMapper is implemented by a file system that carries template functions of its own. An engine asks for them at every Load and adds them to the function map, under anything given to Funcs. MemoryFileSystem implements it, which is how the function map passed to ParseTemplate reaches the templates.
type LoadError ¶ added in v0.1.0
type LoadError struct {
// File is the name of the file that failed, as it appears in the file system.
File string
// Err is the underlying parse error.
Err error
}
LoadError reports that one file could not be parsed. It names the file and wraps the error from html/template.
It never carries the file's contents. A caller that hands err.Error() to an HTTP response would otherwise publish the template source.
type MemoryFileSystem ¶ added in v0.0.11
type MemoryFileSystem struct {
// contains filtered or unexported fields
}
MemoryFileSystem holds template files in memory. It implements fs.FS, so it can be passed to New like any other file system, and it is safe for concurrent use.
Use it when the templates are built at runtime, or when a test would rather not write files to disk.
mfs := blocks.NewMemoryFileSystem()
err := mfs.ParseTemplate("index.html", []byte("<h1>Hello, {{ .Name }}!</h1>"), nil)
views := blocks.New(mfs)
err = views.Load()
func NewMemoryFileSystem ¶ added in v0.0.11
func NewMemoryFileSystem() *MemoryFileSystem
NewMemoryFileSystem returns an empty file system. Add files to it with ParseTemplate.
Example ¶
Templates built in memory, with no files on disk. The function map given to ParseTemplate is added to the engine, so every template can call greet.
package main
import (
"fmt"
"html/template"
"log"
"github.com/kataras/blocks"
)
func main() {
mfs := blocks.NewMemoryFileSystem()
err := mfs.ParseTemplate("layouts/main.html",
[]byte(`<main>{{ yield . }}</main>`), nil)
if err != nil {
log.Fatal(err)
}
err = mfs.ParseTemplate("index.html", []byte(`<p>{{ greet .Name }}</p>`),
template.FuncMap{"greet": func(name string) string { return "Hello, " + name }})
if err != nil {
log.Fatal(err)
}
views := blocks.New(mfs)
if err = views.Load(); err != nil {
log.Fatal(err)
}
out, err := views.TemplateString("index", "main", map[string]any{"Name": "Gopher"})
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}
Output: <main><p>Hello, Gopher</p></main>
func (*MemoryFileSystem) Open ¶ added in v0.0.11
func (mfs *MemoryFileSystem) Open(name string) (fs.File, error)
Open implements fs.FS. Each call returns an independent reader, so one file can be read by many goroutines at once.
func (*MemoryFileSystem) ParseTemplate ¶ added in v0.0.11
func (mfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) error
ParseTemplate stores a template file under the given name, which is a slash separated path such as "layouts/main.html". A name may be given with a leading slash or with backslashes, and both are normalized. It returns an error if the name escapes the file system, or if a value in funcMap is not a function usable by html/template.
The contents are copied, so the caller may reuse the slice.
The functions in funcMap are added to the engine's function map the next time the engine loads, which makes them available to every template rather than to this one alone. A nil funcMap adds nothing. Calling ParseTemplate again with the same name replaces the file.
func (*MemoryFileSystem) ReadDir ¶ added in v0.0.11
func (mfs *MemoryFileSystem) ReadDir(name string) ([]fs.DirEntry, error)
ReadDir implements fs.ReadDirFS. The entries are sorted by file name.
func (*MemoryFileSystem) ReadFile ¶ added in v0.1.0
func (mfs *MemoryFileSystem) ReadFile(name string) ([]byte, error)
ReadFile implements fs.ReadFileFS. The returned slice is a copy.
func (*MemoryFileSystem) Stat ¶ added in v0.1.0
func (mfs *MemoryFileSystem) Stat(name string) (fs.FileInfo, error)
Stat implements fs.StatFS.
func (*MemoryFileSystem) TemplateFuncs ¶ added in v0.1.0
func (mfs *MemoryFileSystem) TemplateFuncs() template.FuncMap
TemplateFuncs returns the functions collected from every ParseTemplate call, or nil if there are none. An engine calls it at Load.
Directories
¶
| Path | Synopsis |
|---|---|
|
_examples
|
|
|
basic
command
|
|
|
embedded
command
|
|
|
funcs
command
|
|
|
funcs/mycollection
Package mycollection contains a template.FuncMap that should be registered across all Blocks view engines.
|
Package mycollection contains a template.FuncMap that should be registered across all Blocks view engines. |
|
multiple
command
|