adrianhesketh.com

Introducing templ

About templ

Templ is a language, command line tool and set of IDE extensions that makes it easier to write HTML user interfaces and websites using Go. It strives for simplicity, developer experience and performance - in that order.

The command line tool generates Go code from the templates (templ generate) provides formatting (templ fmt) and supports the VS Code and vim extensions by providing a Language Server implementation for autocomplete (templ lsp).

Why did you write this?

I enjoy using the Go programming language, but I’ve never been a fan of the built-in Go templating language. It’s hard to call external functions (you have to remember to add them), templates can crash at runtime because they’re not strongly typed, I can never remember the syntax, and the IDE support isn’t great.

It does a job, but I couldn’t see myself using it to build a project, so I looked at the alternatives in the Go ecosystem. I liked a few, but the IDE support was lacking (and would have been hard to retrofit) or they weren’t strongly typed, or compiled, so I decided to start a project squarely focussed at building HTML components, that has great IDE support as a core component.

My plan is to use templ as the templating language in Hotwire [0] style projects, to compare it to established SPA, micro-site and micro-frontend tooling.

Show me!

You can watch a short video example on YouTube [1] or you can follow the steps yourself.

To run it on your machine, install templ by downloading the latest binary for your system from Github [2]. You’ll need to place the binary somewhere that’s in your command line path, e.g. (the /usr/local/bin directory on a Mac, on Windows, you can check the path with echo %PATH%).

Create a new Go project.

mkdir hello-world
cd hello-world
go mod init github.com/a-h/templ-hello-world

Create your first template file - hello.templ.

{% package main %}

{% templ Page(title string, content templ.Component) %}
  <html>
    <head>
      <title>{%= title %}</title>
    </head>
    <body>
      {%! content %}
    </body>
  </html>
{% endtempl %}

{% templ Greeter(name string) %}
  <div>
    <h1>{%= "Hello, " + name + "!" %}</h1>
  </div>
{% endtempl %}

Generate your Go code.

templ generate

Now build a web server by creating main.go:

package main

func main() {
        http.Handle("/hello", HelloHandler{})
        http.ListenAndServe(":8000", nil)
}

type HelloHandler struct{}

func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  // Get the data.
  name := r.URL.Query().Get("name")

  // Create the components.
  body := Greeter(name)
  page := Page("hello", body)

  // Render.
  err := page.Render(r.Context(), w)
  if err != nil {
          log.Println("error", err)
  }
}

Run it.

go run *.go

Visit in a web browser.

How does it work?

HTML user interfaces and websites are often built up from individual components, such as buttons, textboxes, and screen areas. These components need to populated with text that comes from various sources, such as APIs or database calls.

Templ makes it easy to construct HTML components using a tiny language that compiles to Go code. The output of a templ component is automatically HTML escaped and minified to reduce bandwidth requirements. Compiling to Go code means that the components are rendered quickly and that more errors are caught at compilation time instead of runtime.

What is the templ language?

The templ language is a cut-down HTML interspersed with Go branching expressions (e.g. if, else, switch, for`), string outputs and calls to external templates.

The project README documents the language, but a complete example is probably all you need to get started.

{% import "strings" %}

{% templ Layout(header, footer, body templ.Component) %}
	{%! header %}
	{%! body %}
	{%! footer %}
{% endtempl %}

{% templ AddressTemplate(addr Address) %}
	<div>{%= addr.Address1 %}</div>
	<div>{%= addr.Address2 %}</div>
	<div>{%= addr.Address3 %}</div>
	<div>{%= addr.Address4 %}</div>
{% endtempl %}

{% templ PersonTemplate(p Person) %}
	<div>
		<div>{%= p.Name() %}</div>
		<a id="id123" href={%= p.URL %}>{%= strings.ToUpper(p.Name()) %}</a>
		<div>
			{% if p.Type == "test" %}
				<span>{%= "Test user" %}</span>
			{% else %}
				<span>{%= "Not test user" %}</span>
			{% endif %}
			{% for _, v := range p.Addresses %}
				{%! AddressTemplate(v) %}
			{% endfor %}
			{% switch p.Type %}
				{% case "test" %}
					<span>{%= "Test user" %}</span>
				{% endcase %}
				{% case "admin" %}
					<span>{%= "Admin user" %}</span>
				{% endcase %}
				{% default %}
					<span>{%= "Unknown user" %}</span>
				{% enddefault %}
			{% endswitch %}
		</div>
	</div>
{% endtempl %}

What are templ components?

templ components implement a simple interface containing a method that writes the component to any io.Writer, for example, a http.ResponseWriter, an os.File, or a strings.Buffer. This makes templ components both flexible, and able to be used alongside other tools.

It’s possible to write templ components using the templ language, or by directly writing Go code that implments the templ.Component interface.

Using the Templ language

{% templ Hello(name string) %}
  <div>{%= name %}</div>
{% endtempl %}

Go struct component

type Hello struct {
  Name string
}

func (h Hello) Render(ctx context.Context, w io.Writer) (err error) {
    _, err = io.WriteString("<div>" + html.EscapeString("Hello, " + h.Name + "!") + "</div>")
    return err
}

Go function component

func Hello(name string) templ.Component {
  return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
    _, err = io.WriteString("<div>" + html.EscapeString("Hello, " + name + "!") + "</div>")
    return
  })
}

Building layouts

Templ components can be passed as arguments, or embedded within the template to build complex layouts:

{% templ Layout(url string, header, footer, body templ.Component) %}
	{%! header %}
        {%! SideBar(url) %}
	{%! body %}
	{%! footer %}
{% endtempl %}

How can I share components between projects?

Since templ components are just Go code, they can be packaged up as a Go module and shared like any other - by pushing the code to Github or another source code provider.

To version compnents, tag the git repository with a version number.

export VERSION=`git rev-list --count HEAD`; 
git tag v0.0.${VERSION};
git push origin v0.0.${VERSION};

To import components, run go get github.com/user/library-name, add import statements to your .templ or .go files, and you’re good to go.

What tools does templ provide?

Templ ships as a command line tool that generates Go code from templ code (templ generate), formats templ code (templ fmt) and provides features used by IDE plugins and extensions.

A VS Code extension [3] and vim plugin [4] provide syntax highlighting and autocomplete for developers.

How do I output a number?

Use Go’s standard library functions that return a string.

{% templ PersonAge(p Person) %}
  <div>
    {%= strconv.Itoa(p.Age) %}
  </div>
{% endtempl %}

Since templ components are just Go functions, if your needs are more complex, you can also write a component to do it.

func Number(d int64) templ.Component {
  return templ.ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
    _, err = io.WriteString(strconv.Itoa(d))
    return err
  })
}
{% templ PersonAge(p Person) %}
  <div>
    {%! Number(p.Age) %}
  </div>
{% endtempl %}