kilabit.info
| AmA | Build | Email | GitHub | Mastodon | SourceHut

This article compare the feature of go:embed with the memfs package from my pakakeh.go module.

The memfs package created on November 2018, based on my experiences maintains the fork of go-bindata project. The "go:embed" directive introduced into Go tools since Go version 1.16, released February 2021, three years after the first release of memfs package.

Given the following directory structure,

module-root/
+-- cmd/prog/main.go
+-- _content/
     +-- index.adoc
     +-- index.html
     +-- static/
         +-- index.png
         +-- index.png

We want to embed the directory "_content" but only html files and all files inside the "static/" directory.

Cons #1: The "go:embed" only works if files or directory to be embedded is in the same parent directory.

The "go:embed" directive define in "cmd/prog/main.go" will not able to embed files in their parent. The following code will not compile,

//go:embed ../../_content/*.html
//go:embed ../../_content/static
var contentFS embed.FS

// go build output,
// pattern ../../_content/*.html: invalid pattern syntax

If we remove the ".." and execute "go build" from module-root, it will still not compile,

//go:embed _content/*.html
//go:embed _content/static
var contentFS embed.FS

// go build or run output,
// pattern _content/*.html: no matching files found

The only solution is to create and export the variable "ContentFS" in the same parent directory as "_content".

The memfs package does not have this limitation. As long as the Go commands are executed from the module-root directory, you can define the variable in any packages.

Cons #2: Accessing the embedded file require the original path.

Let say we have embeded the "_content" directory using the following syntax,

//go:embed _content/*.html
//go:embed _content/static
var ContentFS embed.FS

To access the file "index.html" you need to pass their full path, in this case "_content/index.html". The path "_content" leaked to the parent FS and not portable.

In the memfs package, the content of [Options.Root] directory can be accessed with "/", so it would become "/index.html". This design allow flexibility and consistency between modules and packages. If an external, third-party package accept the MemFS instance and the first thing they do is to read all contents of "/" directory, the caller can embed any path without have specific prefix or name.

Case example, when we embed SQL files for migration under directory "db/migration" using the "go:embed" directive,

//go:embed db/migration/*.sql
var DBMigrationFS embed.FS

and then call the Migrate function, it cannot found any ".sql" files inside the "/" directory because the files is stored under "db/migration/" prefix.

Cons #3: No development mode.

Let say we run our server that served the content from FS instance. If we changes the html files, and refresh the browser, the new content will not reflected because it serve the content on the first embed.

The memfs package have [Options.TryDirect] that bypass file in memory and read directly to the file system. This allow quick development when changes only template or non-code files.