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:
- Initiate a Golang application
- Create a frontend application using Vite
- 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.