Embed Vite app in a Go Binary

In recent years, Vite has become a popular choice among frontend engineers for creating single-page applications (SPAs). It leverages the power of esbuild to pre-bundle the NPM dependencies and uses Rollup to bundle the highly optimized static files for production.

In this post, we'll explore how to embed these static files into a Golang binary.

How to Do it?

We will achieve this through the following three steps:

  1. Initiate a Golang application
  2. Create a frontend application using Vite
  3. Use embed directive to create File-system for static files and serve

Initiate a Golang Application

Let's create a starter Golang application with the following commands.

mkdir golang-embed-example && cd golang-embed-example
go mod init golang-embed-example

Create a main.go file and add the code given below, this will be the entry point into our application.

// main.go
package main

import "fmt"

func main()  {
	fmt.Println("hello world!")
}

Now, in your terminal run the following command to check if our code is compiled correctly.

go run .

You should see the following output:

hello world!

Create a frontend application using Vite

In this example, we'll use the React app template. We'll store our frontend code inside a frontend directory.

npm create vite@latest frontend -- --template react

To run your frontend dev server, you'll first need to install the packages and then start the server.

cd frontend && npm install
# Start the dev server
npm run dev

Now that we have our frontend server up and running without any errors, We'll build a static output for the application. When you run the following command inside frontend directory you'll notice the build is stored inside frontend/dist directory.

npm run build

The files inside the frontend/dist directory need to be embedded in our Golang application that was created in the first section.

Use embed directive to create File-system for static files and serve

In this section, we are going to use the embed package to embed the static files. Note that the embed package was introduced in the Go v1.16.

For this, create a frontend.go file inside the frontend directory. Here, we'll use the //go:embed directive to create the file system for the dist folder, which contains our static files.

// frontend/frontend.go
package frontend

import (
	"embed"
)

//go:embed dist
var DistFS embed.FS

// Get the subtree of the embedded files with `dist` directory as a root.
func BuildHTTPFS() http.FileSystem {
	build, err := fs.Sub(DistFS, "dist")
	if err != nil {
		log.Fatal(err)
	}
	return http.FS(build)
}

Here, embed.FS is essentially a read-only collection of files, it implements fs.FS which means it can be used with any implementation that understands fs.FS. For example, http.FS; we are going to use it to serve our files through the http server. In the above snippet, we are using fs.Sub to select the dist folder as our root directory for serving our static files and then returning the http file system.

Coming back to our main.go file, we will now create a basic HTTP server to serve our files. Note that you can use frameworks like mux, echo, etc. to do the same.

// main.go
package main

import (
	"golang-embed-example/frontend"
	"net/http"
)

func main() {
 	http.Handle("/", http.FileServer(frontend.BuildHTTPFS()))
	http.ListenAndServe(":8081", nil)
}

Once again try running go run . and then navigate to localhost:8081. You'll see your frontend app up and running!

Benefits of Embedding Vite in Go

Embedding a Vite application can be highly beneficial in scenarios where the entire application must operate in isolated environments. By embedding your SPA within the Go binary, deployment becomes streamlined into a single executable. This approach removes the necessity of managing separate backend and frontend deployments. Each build of your Golang app will include the precise version of the SPA with which it was constructed, ensuring a consistent pairing between the backend and frontend.