We talked about how I built https://status.barelyhuman.dev and what I was doing to handle adding the html data into the final binary so that the hosting provider could render the html from a single binary.
You can read that post here
The previous solution works and was quite easy to do but then it obviously took away my speed since there's no syntax highlighting for css or html as they are all just go strings now. Plus, to format the html or css I have to move them to a separate file format it there and paste it back here which is really not something I wish to do if I have to maintain the repo for a long time.
So i started looking up ways and while I knew about go embed I was waiting for embed filesystem(FS) to get a little more stable and then I forgot about it and never checked the pull requests that were to be merged for embed FS. I happened to randomly check it two days back and so I'm now going to talk about how embed works and how status is using it to handle the HTML and style templates.
This assumes that you've worked with html/templates
in go before and we build up on that. The basic html/template code flow is something like this
embed provides two ways to go about embedding files or other static data
If we were to use the first one the code would look a little something like
package extras
import (
_"embed"
"html/template"
)
//go:embed templates/home.html
var homeHTML []byte
the modifier you need to look at is go:embed
this allows you to put a path based on the current file, you cannot use files from folder outside your
current .go
file , which is the current limitation of pattern matching but I really hope changes in the future.
The compiler then reads and embeds the data into the variable homeHTML
and then you can continue using the file like it's already read. but in out
case we have more than one template and you can add that to each variable , just know that the go:embed
has to be on the top level and cannot be
inside a function (at least, when I'm typing this.)
The other way involves using the embed FS which is basically going to do the same thing but instead creates an in-binary filesystem that you can read
through. This is built on the existing go FS interface so you can use the embedFS wherever the FS interface is supported which the html/template
library does and we are going to use that since I don't want to manually have to go and add the embed since I am parsing all templates anyway.
The code for the same would look something like this
package extras
import (
"embed"
"html/template"
)
//go:embed templates/*.html
var embedFS embed.FS
// GetTemplates - parse and get all templates
func GetTemplates() (*template.Template, error) {
allTemplates, err := template.ParseFS(embedFS, "templates/*.html","styles/styles.html")
if err != nil {
return nil, err
}
return allTemplates, nil
}
The line of focus is again the go:embed
comment but also focus on the next line this time, we now have a variable that is of the type embed.FS
which as I mentioned before is an implementation of the filesystem interface that go already has for other io interfaces and since the html/template
allows you to parse based on filesystem and from the 2nd param you can add in patterns which is of the type (...strings)
aka infinite patterns on
the FS and everything gets parsed into allTemplates
.
At this point, all I do is call GetTemplates
from wherever I need them, and follow the normal template code flow
and we are basically done, as a user, this won't create a difference on what you see when you open status but as a dev, this made it a lot more easier for me to handle changes in the html and style files.