Vascula v0.1.5

Getting started

Vascula is a Go library. This page takes you from nothing to a rendered page.

#Requirements

  • Go 1.25 or later.
  • Add the module: go get vascula.dev/vascula@latest.

The package has no third-party dependencies.

#Render your first template

Go
package main

import (
	"fmt"

	"vascula.dev/vascula"
)

func main() {
	tmpl, err := vascula.Compile(`<h1>Hello, {{ settings.name }}!</h1>`)
	if err != nil {
		panic(err)
	}
	html, err := tmpl.Render(vascula.Options{
		Settings: map[string]interface{}{"name": "Ada & Grace"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(html)
}
Prints
<h1>Hello, Ada &amp; Grace!</h1>

Two steps, always in this order:

  1. Compile parses the source into a *Template. Do it once and keep the template: compiling is the expensive part, and a compiled template is safe to render from many goroutines at once.
  2. Render evaluates the template with Options and returns the HTML.

settings is the template's own parameters. It is always readable, without any permission.

#Give the template data

Application data goes in Options.Data, keyed by root name. A template can only read the roots listed in Options.Allow.

Go
package main

import (
	"fmt"

	"vascula.dev/vascula"
)

func main() {
	tmpl, err := vascula.Compile(`{% for p in catalog.products %}{{ p.title }} costs {{ p.price }}.
{% endfor %}`)
	if err != nil {
		panic(err)
	}
	html, err := tmpl.Render(vascula.Options{
		Data: map[string]interface{}{
			"catalog": map[string]interface{}{
				"products": []interface{}{
					map[string]interface{}{"title": "Cup", "price": 12},
					map[string]interface{}{"title": "Saucer", "price": 4.5},
				},
			},
			"secrets": "not for templates",
		},
		Allow: []string{"catalog"},
	})
	if err != nil {
		panic(err)
	}
	fmt.Print(html)
}
Prints
Cup costs 12.
Saucer costs 4.5.

secrets is in Data but not in Allow, so the template cannot read it. If it tried, rendering would fail with an UndeclaredNameError.

Use JSON-shaped values: map[string]interface{}, []interface{}, strings, numbers, booleans and nil. Data decoded with encoding/json already has this shape.

#Handle errors

Compilation fails on malformed source; rendering fails on things that can only be known with data, such as reading a name that is not allowed. Both errors say where the problem is.

Go
package main

import (
	"errors"
	"fmt"

	"vascula.dev/vascula"
)

func main() {
	_, err := vascula.Compile("<p>\n{% if settings.on %}open")
	fmt.Println(err)

	tmpl, _ := vascula.Compile("<p>{{ user.name }}</p>")
	_, err = tmpl.Render(vascula.Options{})
	fmt.Println(err)

	var undeclared *vascula.UndeclaredNameError
	fmt.Println(errors.As(err, &undeclared), undeclared.Name)
}
Prints
{% if %} is never closed with {% endif %} (line 2, col 1)
undeclared name "user": it is not a variable or an allowed data root (line 1, col 7)
true user

Show these messages to the person who wrote the template. Do not show them, or the template source, to the people visiting your site. See Errors for every message and what to do about it.

#Next steps