HTMLテンプレートをpackrでバイナリに埋め込む

最近、GoでWebアプリケーションを書く練習をしている。フレームワークとしてEchoを使っている。

アセットをバイナリに埋め込むときいくつか選択肢があったけど、HTMLテンプレートのパースを実装しやすそうなpackrを使ってみている。

以下のようなテンプレートを用意する。

{{/* assets/views/layout.html */}}
{{define "layout" -}}
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Task</title>
  </head>
  <body>
    {{template "content" . -}}
  </body>
</html>
{{end}}
{{/* assets/views/index.html */}}
{{define "index" -}}
<p>Here is content!</p>
{{end}}

これらのテンプレートは以下のコマンドでバイナリに埋め込むことができる。

$ packr build

次に、EchoでHTMLをレンダリングするためのRendererを実装する。

// renderer/renderer.go

// Renderer is html/template renderer for Echo.
type Renderer struct {
	templates *template.Template
}

// New parses templates in box and return a new Renderer.
func New(box packr.Box) *Renderer {
	templates := template.New("templates")

	for _, name := range box.List() {
		text := box.String(name)
		templates = template.Must(templates.Parse(text))
	}

	return &Renderer{templates: templates}
}

// Render renders HTML generated by template.
func (r *Renderer) Render(wr io.Writer, name string, data interface{}, c echo.Context) error {
	layout := template.Must(r.templates.Lookup("layout").Clone())
	content := template.Must(r.templates.Lookup(name).Clone())
	html := template.Must(layout.AddParseTree("content", content.Tree))

	return html.ExecuteTemplate(wr, "layout", data)
}

packrコマンドでバイナリに埋め込んだアセットはbox.String(filename)のように取得できる。これを使ってテンプレートをパースしていく。

Rendererを初期化するときにテンプレートをすべてパースしておいて、レンダリング時にネストしたテンプレートを組み立ててExecuteするようにしている。

最後に、このRendererを初期化してEchoに設定する。

// main.go

e := echo.New()

viewsBox := packr.NewBox("./assets/views")
e.Renderer = renderer.New(viewsBox)