前にginを使ったWEBアプリをシングルバイナリ化をする場合、テンプレートを含めるなら別途実装が必要になると書いた。
現在はgolangでフロント部分を書かない方針だけど、そのうち必要になるかもしれないのでフォルダ内のテンプレートを一括で読むくらいの処理を書いてみました。
具体的にはginのLoadHTMLGlob
の置き換えです。
gin.Engine.LoadHTMLGlob(doc・src)
まず該当ソースを見てみるとhtml/templateのParseGlobを呼んでいる。
(LoadHTMLFileの場合はParseFiles)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// LoadHTMLGlob loads HTML files identified by glob pattern // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLGlob(pattern string) { left := engine.delims.Left right := engine.delims.Right templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern)) if IsDebugging() { debugPrintLoadTemplate(templ) engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims} return } engine.SetHTMLTemplate(templ) } // LoadHTMLFiles loads a slice of HTML files // and associates the result with HTML renderer. func (engine *Engine) LoadHTMLFiles(files ...string) { if IsDebugging() { engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims} return } templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...)) engine.SetHTMLTemplate(templ) } |
template.Template.ParseGlob(doc・src)
filepath.Globで取得ファイル一覧をとってきてparseFilesしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// ParseGlob returns an error if t or any associated template has already been executed. func (t *Template) ParseGlob(pattern string) (*Template, error) { return parseGlob(t, pattern) } // parseGlob is the implementation of the function and method ParseGlob. func parseGlob(t *Template, pattern string) (*Template, error) { if err := t.checkCanParse(); err != nil { return nil, err } filenames, err := filepath.Glob(pattern) if err != nil { return nil, err } if len(filenames) == 0 { return nil, fmt.Errorf("html/template: pattern matches no files: %#q", pattern) } return parseFiles(t, filenames...) } |
つまりテンプレート作成本体はこれ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
func parseFiles(t *Template, filenames ...string) (*Template, error) { if err := t.checkCanParse(); err != nil { return nil, err } if len(filenames) == 0 { // Not really a problem, but be consistent. return nil, fmt.Errorf("html/template: no files named in call to ParseFiles") } for _, filename := range filenames { b, err := ioutil.ReadFile(filename) if err != nil { return nil, err } s := string(b) name := filepath.Base(filename) // First template becomes return value if not already defined, // and we use that one for subsequent New calls to associate // all the templates together. Also, if this file has the same name // as t, this file becomes the contents of t, so // t, err := New(name).Funcs(xxx).ParseFiles(name) // works. Otherwise we create a new template associated with t. var tmpl *Template if t == nil { t = New(name) } if name == t.Name() { tmpl = t } else { tmpl = t.New(name) } _, err = tmpl.Parse(s) if err != nil { return nil, err } } return t, nil } |
filepath.Glob(doc・src)
ファイルのパターンマッチを返す関数だけど今回は必要ないので流す。
Statikからテンプレート読み込み
template.ParseFilesに相当する部分を作ればいいことが分かった。
asset/html/*/*.html
をテンプレートとして指定していたのでasset
をバイナリ化して以下のように書き換えた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
statikFS, _ := fs.New() r := gin.Default() ... tmpl := template.New("") tmplDirNames := []string{"/html/page", "/html/tmpl"} for _, tmplDirName := range tmplDirNames { fmt.Println("read dir : ", tmplDirName) dir, _ := statikFS.Open(tmplDirName) files, _ := dir.Readdir(-1) for _, fi := range files { fmt.Println(fi.Name(), " load") f, _ := statikFS.Open(tmplDirName + "/" + fi.Name()) content, _ := ioutil.ReadAll(f) tmpl.New(fi.Name()).Parse(string(content)) } } r.SetHTMLTemplate(tmpl) |
複数フォルダの中身を見に行ってテンプレートをパースしている。
個人的なハマりポイントとしてstatik
のReaddir(0)
は全取得でなく0個取得になるので-1を指定する必要がある(0取得なんてものがあるとは思ってなかった)。
拡張子指定くらいなら簡単そうだけどGlobのような正規表現化は面倒そうなのでしていない。
またデリミタとテンプレート関数の設定をしている場合はgin
のものでなくhtml/template
の方式で書き直す必要がある。
そこまでしてシングルバイナリにする必要があるのかは微妙です。