Let's Go File embedding › Embedding HTML templates
Previous · Contents · Next
Chapter 12.2.

Embedding HTML templates

Next let’s update our application so that the template cache uses embedded HTML template files, instead reading them from your hard disk at runtime.

Head back to the ui/efs.go file, and update it so that ui.Files embeds the contents of the ui/html directory (which contains our templates) too. Like so:

File: ui/efs.go
package ui

import (
    "embed"
)

//go:embed "html" "static" 
var Files embed.FS

Then we need to update the newTemplateCache() function in cmd/web/templates.go so that it reads the templates from ui.Files. To do this, we’ll need to leverage a couple of the special features that Go has for working with embedded filesystems:

Let’s put these to use in our cmd/web/templates.go file:

File: cmd/web/templates.go
package main

import (
    "html/template"
    "io/fs" // New import
    "path/filepath"
    "time"

    "snippetbox.alexedwards.net/internal/models"
    "snippetbox.alexedwards.net/ui" // New import
)

...

func newTemplateCache() (map[string]*template.Template, error) {
    cache := map[string]*template.Template{}

    // Use fs.Glob() to get a slice of all filepaths in the ui.Files embedded
    // filesystem which match the pattern 'html/pages/*.tmpl'. This essentially
    // gives us a slice of all the 'page' templates for the application, just
    // like before.
    pages, err := fs.Glob(ui.Files, "html/pages/*.tmpl")
    if err != nil {
        return nil, err
    }

    for _, page := range pages {
        name := filepath.Base(page)

        // Create a slice containing the filepath patterns for the templates we
        // want to parse.
        patterns := []string{
            "html/base.tmpl",
            "html/partials/*.tmpl",
            page,
        }

        // Use ParseFS() instead of ParseFiles() to parse the template files 
        // from the ui.Files embedded filesystem.
        ts, err := template.New(name).Funcs(functions).ParseFS(ui.Files, patterns...)
        if err != nil {
            return nil, err
        }

        cache[name] = ts
    }

    return cache, nil
}

Now that this is done, when our application is built into a binary it will contain all the UI files that it needs to run.

You can try this out quickly by building an executable binary in your /tmp directory, copying over the TLS certificates, and running the binary. Like so:

$ go build -o /tmp/web ./cmd/web/
$ cp -r ./tls /tmp/
$ cd /tmp/
$ ./web 
time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000

And again, you should be able to visit https://localhost:4000 in your browser and everything should work correctly — despite the binary being in a location where it does not have access to the original UI files on disk.

12.02-01.png