Skip to content
/ tpl Public

Library that helps make Go html/template more tolerable.

License

Notifications You must be signed in to change notification settings

dstpierre/tpl

Repository files navigation

tpl

build badge GoReportCard Go Reference

I was tired of having the same issues phase when starting a new web project with Go's html/template.

tpl is an opinionated lightweight helper library that makes structuring, parsing, and rendering templates in Go more tolerable. It also adds i18n helpers to the funcmap of templates.

Table of content:

Installation

$ go get github.com/dstpierre/tpl

Usage

Template file structure

To use this library, you'll need to adopt the following files and directory structure for your templates:

Create a templates directory with the following structure:

templates/
├── _partials
│   └── a-reusable-piece-1.html
│   └── a-reusable-piece-2.html
├── app.html
├── layout.html
├── translations
│   ├── en.json
│   └── fr.json
└── views
    ├── app
    │   ├── dashboard.html
    │   └── page-signed-in-user.html
    └── layout
        └── user-login.html

Now app.html and layout.html are example names.

Layouts are HTML files at the root of your templates/ directory. They contain blocks that your views will fill.

views directory contains one directory per layout file name without the .html extension. If you have three layout templates, home.html, blog.html, and another.html, you'll have three sub-directories in the Views directory, each containing the views for this layout.

_partials is a directory where you put all re-usable pieces of template you need to embed into your HTML pages. For instance, you embed a blog-view.html in 'views/blog/list.html', views/blog/category.html, and views/blog/tag.html pages.

translations directory is where you put message translations via one file named after the language. It's optional.

Parsing and rendering

You'll need to parse your templates at the start of your program. The library returns a tpl.Template structure that you use to render your pages.

For example:

package main

import (
  "embed"
  "net/http"
  "github.com/dstpierre/tpl"
)

//go:embed templates/*
var fs embed.FS

func main() {
  // assuming your templates are in templates/ and have proper structure
  templ, err := tpl.Parse(fs, nil)
  // ...
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request)) {
    data := "this is your app's data you normally pass to Execute"
    pdata := tpl.PageData{Data: data}
    if err := templ.Render(w, "app/dashboard.html", pdata); err != nil {}
  }
}
)

As you can see, you need to wrap your data inside a tpl.PageData structure. This enables the library to perform lingual translations and internationalize dates and currencies.

PageData structure

Here's the fields of the tpl.PageData:

type PageData struct {
  Lang     string
  Locale   string
  Timezone string
  XSRFToken string
  Title       string
  CurrentUser any
  Data        any
  Extra       any
  Env string
}

Lang and Locale are useful if you want to use the i18n feature.

CurrentUser is handy if you want to let your templates know about the current user.

Env is useful if your system has multiple environment, like dev, staging, prod and you'd want to do different things based on the env. I personally use if to have a non-minified JavaScript bundle in dev and staging, while a minified one in prod.

Extra can be useful for anything that your views need that's not present in the main Data field.

Title is also helpful to set the page title, you can have this in your layout templates:

<title>{{.Title}}</title>
or
{{if .Title}}
  {{.Title}}
{{else}}
  Default title when empty
{{end}}

Example templates

The tests use somewhat real-ish directories and file structures from which you may get started.

Look at the testdata directory. In your program, you might want to name the root directory templates but it's configurable.

Quick template example

templates/layout.html:

{{template "nav.html"}}

<main>{{block "content" .}}{{end}}</main>

templates/views/layout/home.html:

{{define "content"}}
<h1>From the home.html view</h1>
{{end}}

templates/_partials/nav.html:

<nav>
  Navigation would goes here
</nav>

i18n

If your web application needs multilingual support, you can create language message files and save them in the Translations directory.

templates/translations/en.json:

[{
  "key": "unique key",
  "value": "The value",
  "plural": "Optional value for plural",
}]

For the translation to work you need to set the Lang field of the tpl.PageData when rendering your template:

func home(w http.ResponseWriter, r *http.Request) {
  pdata := tpl.PageData{Lang: "fr", Data: 1234}
  if err := templ.Render(w, "layout/home.html", pdata); err != nil {}
}

Inside your templates:

<p>{{ t .Lang "unique key" }}</p>

Or for plural

<p>{{ tp .Lang "unique key" .Data }}</p>

.Data is 1234 in example above, so the plural value would be displayed.

There's helper function to display dates and currencies in the proper format based on Locale.

func home(w http.ResponseWriter, r *http.Request) {
  pdata := tpl.PageData{Lang: "fr", Locale: "fr-CA", Data: 59.99}
  if err := templ.Render(w, "layout/home.html", pdata); err != nil {}
}

And inside the templates/views/layout/home.html file:

<p>The price is {{ currency .Locale .Data }}</p>

Display: The price is 59.99 $

If Locale is en-US: The price is $55.99.

There's also a {{ shortdate .Locale .Data.CreatedAt }} helper function which formats a time.Time properly based on Locale.

NOTE: At this time there's only a limited amount of locale supported. If your locale isn't supported, please consider contributing the changes.

Passing a funcmap

You may have helper functions you'd like to pass to the templates. Here's how:

package main

import (
  "embed"
  "github.com/dstpierre/tpl"
)

//go:embed templates/*
var fs embed.FS

var templ *tpl.Template

func main() {
  fmap := make(map[string]any)
  fmap["myfunc"] = func() string { return "hello" }
  r, err := tpl.Parse(fs, fmap)
  //...
  templ = t
}

About

Library that helps make Go html/template more tolerable.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages