Using go-bindata to embed views into the binary
This is a quick tutorial showing how to use go-bindata to embed views into your binary so your appplication deployment is just that binary.
Normal Approach:
In a normal application we read the views using the html/template
package, like so:
t, _ := template.ParseFiles("views/layout.html.tmpl", "views/body.html.tmpl")
Then we simply do
t.Execute(w, data)
where w is our http.ResponseWriter
Using go-bindata:
The first thing we need to do is package the views into a go file, this is done by running the next command:
go-bindata views/...
You can even add multiple folders, strip prefixes and a bunch of goodies, but thats better explained here
This will create a file called bindata.go
, i recomend changing the package name from main
into something like assets
or bindata
that way you can share your bin data trough all the packages your application has.
If you browse trough the file you would see it looks something like this:
// Code generated by go-bindata.
// sources:
// assets/css/bulma.min.css
// assets/css/font-awesome.min.css
// assets/fonts/fontawesome-webfont.eot
// assets/fonts/fontawesome-webfont.ttf
// assets/fonts/fontawesome-webfont.woff
// assets/fonts/fontawesome-webfont.woff2
// views/bodyt.html.tmpl
// views/layout.html.tmpl
// DO NOT EDIT!
package bindata
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
var _assetsCssBulmaMinCss = []byte("HUGE HEX FILE HERE")
func assetsCssBulmaMinCssBytes() ([]byte, error) {
return bindataRead(
_assetsCssBulmaMinCss,
"assets/css/bulma.min.css",
)
}
func assetsCssBulmaMinCss() (*asset, error) {
bytes, err := assetsCssBulmaMinCssBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "assets/css/bulma.min.css", size: 137589, mode: os.FileMode(438), modTime: time.Unix(1517967570, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
//Lots more code...
Towards the end of a file theres a simple map containing the value of the files in hex and using the path to the file as key:
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"assets/css/bulma.min.css": assetsCssBulmaMinCss,
"assets/css/font-awesome.min.css": assetsCssFontAwesomeMinCss,
"assets/fonts/fontawesome-webfont.eot": assetsFontsFontawesomeWebfontEot,
"assets/fonts/fontawesome-webfont.ttf": assetsFontsFontawesomeWebfontTtf,
"assets/fonts/fontawesome-webfont.woff": assetsFontsFontawesomeWebfontWoff,
"assets/fonts/fontawesome-webfont.woff2": assetsFontsFontawesomeWebfontWoff2,
"views/layout.html.tmpl": viewsLayoutHtmlTmpl,
"views/body.html.tmpl": viewsBodyHtmlTmpl,
}
and the Asset
function, which is a wrapper to accesing the values of the map.
To access a file []byte
you only need to call:
bytes, err := bindata.Asset("views/layout.html.tmp")
(in my case because i changed the package from main
to bindata
) and it will, unsurprisingly give us the value of the key for the _bindata
variable, it will return and error
if anything goes wrong.
Now to execute the template we must first convert it into string like this:
stringFile := string(bytes)
and then we can feed it into the html/template
package like a string.
Then replace the function template.ParseFile
for
t, := template.New("cacheNameHere").Parse(stringFile)
after that its regular business serving with t.Execute(w)
.
Theres only one problem to this, the template.ParseFiles
function call is variadic, and you can supply various templates at once like when we used in the previous example:
t, _ := template.ParseFiles("views/layout.html.tmpl", "views/body.html.tmpl")
where we supply a layout.
To fix these we must understand what template.ParseFiles
does first:
It basically iterates trough the filenames, and instanciate a template with t := template.New
and subsequently runs t.Parse
on each file after reading them with iotutil.ReadFile
source here
So you can either:
Run subsequent Parse for each file:
view, err := bindata.Asset("views/body.html.tmpl")
layout, err := bindata.Asset("views/layout.html.tmpl")
t := template.New("test")
t, err := t.Parse(string(layout))
t, err := t.Parse(string(view))
t.Execute(w, data)
or simply concat the strings when parsing:
view, err := bindata.Asset("views/body.html.tmpl")
layout, err := bindata.Asset("views/layout.html.tmpl")
t, err := template.New("test").Parse(string(layout) + string(view))
t.Execute(w, data)
Conclusion:
go-bindata is a tool that can allow us to embed resources into our application binary to keep deployment as simple as posible.
Bonus:
Embed static assets, and serve them with an asset handler:
First the asset handler:
package handlers
import (
"strings"
"net/http"
"<yourproject>/bindata"
)
type AssetsHandler struct{}
func NewAssetsHandler() *AssetsHandler {
return &AssetsHandler{}
}
func (a *AssetsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
request := strings.TrimPrefix(r.URL.Path, "/")
file, err := bindata.Asset(request)
if err != nil {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(file)
}
and then simply add the route to your router:
r.PathPrefix("/assets").Handler(handlers.NewAssetsHandler()
this code is using gorilla/mux
also, for this to work you would need to include an assets
directory along with the views
directory for go-bindata like so:
go-bindata views/... assets/...