Go resource bundling

embed.FS

With the release of Go 1.16 in February 2021, a new feature, embed.FS, was introduced, providing a native way to embed static files into Go binaries. This feature eventually led to the deprecation of pkger (if you are using Go pre-1.16, see pkger).

embed.FS simplifies the process of embedding files directly into your Go applications without external dependencies. Unlike pkger, which uses reflection at compile time, embed.FS is more straightforward and integrates seamlessly with the Go toolchain.

Usage of embed.FS

Using embed.FS is simple. First, you need to import the embed package:

import "embed"

Then, you can use the //go:embed directive to specify the files or directories to embed:

//go:embed templates/page.tmpl
var tmplFS embed.FS

Example

To bundle the same Go template file as in the pkger example, you can use embed.FS like this:

import (
    "embed"
    "text/template"
    "io/fs"
    "os"
)

//go:embed templates/page.tmpl
var tmplFS embed.FS

func main() {
    tmplData, _ := fs.ReadFile(tmplFS, "templates/page.tmpl")
    tpl, err := template.New("page").Parse(string(tmplData))
    if err != nil {
        panic(err)
    }
    _ = tpl.Execute(os.Stdout, ...)
}

Building Your Application

With embed.FS, there’s no need for a separate bundling step. You simply build your application as usual:

go build

The introduction of embed.FS in Go 1.16 provides a more native and efficient way of embedding files in Go applications. While pkger served well for a time, embed.FS is now the recommended approach for most use cases.


This structure should integrate well with your existing blog post, providing a clear and concise comparison between pkger and embed.FS, and guiding readers on how to transition to the newer method.

pkger

Notes on the installation and usage of pkger.

Installation done with

go get github.com/markbates/pkger/cmd/pkger

pkger works by bundling the resources with a code-generated pkg.go. The configuration of assets to be bundled is done by reflection at compile time and not direct configuration. This is done by replacing standard Go file operations with pkger proxy ones, such as:

type Pkger interface {
  Parse(p string) (Path, error)
  Current() (here.Info, error)
  Info(p string) (here.Info, error)
  Create(name string) (File, error)
  MkdirAll(p string, perm os.FileMode) error
  Open(name string) (File, error)
  Stat(name string) (os.FileInfo, error)
  Walk(p string, wf filepath.WalkFunc) error
  Remove(name string) error
  RemoveAll(path string) error
}
type File interface {
  Close() error
  Info() here.Info
  Name() string
  Open(name string) (http.File, error)
  Path() Path
  Read(p []byte) (int, error)
  Readdir(count int) ([]os.FileInfo, error)
  Seek(offset int64, whence int) (int64, error)
  Stat() (os.FileInfo, error)
  Write(b []byte) (int, error)
}

Example

Bundling a Go template file.

tmplFile, _ := pkger.Open("/templates/page.tmpl")
tmplBytes, _ := ioutil.ReadAll(tmplFile)
tmplString := string(tmplBytes)

tpl, err := template.New("page").Parse(tmplString)
_ = tpl.Execute(f, ...)

The bundling is simply done by running

pkger

and building as usual

go build