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:
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:
- The
fs.Glob()
function returns a slice of filepaths matching a glob pattern. It’s effectively the same as thefilepath.Glob()
function that we used earlier in the book, except that it works on embedded filesystems. - The
Template.ParseFS()
method can be used to parse the HTML templates from an embedded filesystem into a template set. This is effectively a replacement for both theTemplate.ParseFiles()
andTemplate.ParseGlob()
methods that we used earlier.Template.ParseFiles()
is also a variadic function, which allows you to parse multiple templates in a single call toParseFiles()
.
Let’s put these to use in our cmd/web/templates.go
file:
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.
