Let's Go File embedding › Embedding static files
Previous · Contents · Next
Chapter 12.1.

Embedding static files

If you’re following along, the first thing to do is create a new ui/efs.go file:

$ touch ui/efs.go

Then add the following code:

File: ui/efs.go
package ui

import (
    "embed"
)

//go:embed "static"
var Files embed.FS

The important line here is //go:embed "static".

This looks like a comment, but it is actually a special comment directive. When our application is compiled (as part of either go build or go run), this comment directive instructs Go to store the files from our ui/static folder in an embedded filesystem referenced by the global variable Files.

There are a few important details about this which we need to explain.

Using the embedded static files

Now let’s switch up our application so that it serves our static CSS, JavaScript and image files from the embedded file system — instead of reading them from the disk at runtime.

Open your cmd/web/routes.go file and update it as follows:

File: cmd/web/routes.go
package main

import (
    "net/http"

    "snippetbox.alexedwards.net/ui" // New import

    "github.com/justinas/alice"
)

func (app *application) routes() http.Handler {
     mux := http.NewServeMux()

    // Use the http.FileServerFS() function to create a HTTP handler which 
    // serves the embedded files in ui.Files. It's important to note that our 
    // static files are contained in the "static" folder of the ui.Files
    // embedded filesystem. So, for example, our CSS stylesheet is located at
    // "static/css/main.css". This means that we no longer need to strip the
    // prefix from the request URL -- any requests that start with /static/ can
    // just be passed directly to the file server and the corresponding static
    // file will be served (so long as it exists).
    mux.Handle("GET /static/", http.FileServerFS(ui.Files))

    dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate)

    mux.Handle("GET /{$}", dynamic.ThenFunc(app.home))
    mux.Handle("GET /snippet/view/{id}", dynamic.ThenFunc(app.snippetView))
    mux.Handle("GET /user/signup", dynamic.ThenFunc(app.userSignup))
    mux.Handle("POST /user/signup", dynamic.ThenFunc(app.userSignupPost))
    mux.Handle("GET /user/login", dynamic.ThenFunc(app.userLogin))
    mux.Handle("POST /user/login", dynamic.ThenFunc(app.userLoginPost))

    protected := dynamic.Append(app.requireAuthentication)

    mux.Handle("GET /snippet/create", protected.ThenFunc(app.snippetCreate))
    mux.Handle("POST /snippet/create", protected.ThenFunc(app.snippetCreatePost))
    mux.Handle("POST /user/logout", protected.ThenFunc(app.userLogoutPost))

    standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders)
    return standard.Then(mux)
}

If you save these changes and then restart the application, you should find that everything compiles and runs correctly. When you visit https://localhost:4000 in your browser, the static files should be served from the embedded filesystem and everything should look normal.

12.01-01.png

If you want, you can also navigate directly to the static files to check that they are still available. For example, visiting https://localhost:4000/static/css/main.css should display the CSS stylesheet for the webpage from the embedded filesystem.

12.01-02.png

Additional information

Multiple paths

It’s totally OK to specify multiple paths in one embed directive. For example, we could individually embed the ui/static/css, ui/static/img and ui/static/js directories like so:

//go:embed "static/css" "static/img" "static/js" 
var Files embed.FS

Embedding specific files

I alluded to this at the start of the chapter, but it’s possible for an embed path to point to a specific file. Embedding isn’t just limited to directories.

For example, lets’s pretend that our ui/static/css directory contains some additional assets that we don’t want to embed, such as Sass or Less files. In that case, we could embed just the ui/static/css/main.css file like so:

//go:embed "static/css/main.css" "static/img" "static/js" 
var Files embed.FS

Wildcard paths

The character * can be used as a ‘wildcard’ in an embed path. Continuing with the example above, we could rewrite the embed directive so that only .css files under ui/static/css are embedded:

//go:embed "static/css/*.css" "static/img" "static/js" 
var Files embed.FS

Related to that, if you use the wildcard path "*" without any qualifiers, like this:

//go:embed "*"
var Files embed.FS

… then it will embed everything in the current directory, including the .go file that contains the embed directive itself! Most of the time you don’t want that, so it’s more common to explicitly embed specific subdirectories or files instead.

The all prefix

Finally, if a path is to a directory, then all files in that directory are recursively embedded — except for files with names that begin with . or _ characters. If you want to include those files too, then you should use the all: prefix at the start of the path.

//go:embed "all:static"
var Files embed.FS