Integrating JavaScript Frameworks into your Golang Projects
A Case Study of using Vite.js based projects to improve your Golang web project.
Go is a great language to write web applications, with its powerful http libraries, and its flexible templating language. The html.Template package makes it easy enough to embed JavaScript into your web pages. But not too much JavaScript — do it too much, and your JavaScript becomes difficult to maintain, and hard for other developers to follow if they need to fix or maintain code that you wrote.
This will sound pretty familiar to front-end web developers. The solution to this, for a very long time, has been frameworks. Modern JS frameworks like Angular, React, Vue and Svelte help you make large single page applications. They can also be used, with a bit of work (OK, sometimes a lot of work) to take an application that mostly uses your favored web application server to add JavaScript elements to your pages, w/o handing the whole UI over to a framework.
This can be a bit of a mess. JavaScript application frameworks typically preprocess the code and linked CSS and compile, preprocess it, fold, spindle, and possibly mutilate it into the files your webpages can actually load. If you’re doing a progressive enhancement of your existing pages, it can be painful to determine what these “chunked” files are called, and what order they should be linked into your pages. If you’ve read a lot of tutorials telling you how to do this, you’ll appreciate what I mean here.
I’m going to try not to write another one of *those* tutorials. I’m going to try and make it easy for you. Vite.js is a relatively new JavaScript app-builder, bundler designed by Evan You, the creator of Vue.js, as a faster and easier to understand replacement for Vue CLI. It’s platform agnostic, and has support for not just Vue, but various flavors of ReactJS and Svelte. There’s an active community building plugins to use with Vite, and relevant to us here, developers for Ruby on Rails, Django (Python), Laravel and Wordpress (PHP) and other frameworks and languages have written integrations to make it simple to do development in JavaScript using your favorite tools, and have it “just work”. I’ve recently written a Go module that does this for your Go web apps: vite-go. I’m going to use it as an example of how you use an integration both for rapid development of your JavaScript code, and for bundling your ready-for-production code with your app.
What Integrations Make Easier
Integration packages like vite-go typically automate or streamline at least the following tasks:
- Connecting the Vite development server to web pages served from your application.
- Embedding production dist/ directories built by Vite into your production pages.
- Serving assets from dist/ from your production server as static files.
Vite-go does all three of these tasks. There’s a complete sample app that shows you how to use all of these features in the repository.
Installing vite-go into Your Go App
To use vite-go, first you’ll need to `go get` the module:
go get github.com/torenware/vite-go
You’ll also want to set up your JS project in a directory in your Go work directory (we’ll call it “frontend” here). You can create a new Vite-based project with NPM from the command line on MacOS or Linux with the command
npm create vite@latest -y frontend -- --template vue
The command on Windows will be similar, but is an exercise for the reader :-)
To integrate support for loading your app, you’ll need to create a glue object that’s configured for the kind of support you want (i.e., development support for hot-loading your files, or production support for building the finished JavaScript bundles into your Go app). Your code will look something like this:
import (
...
vueglue "github.com/torenware/vite-go"
)var vueGlue *vueglue.ViteConfigfunc main() { // This for production...
// Production configuration.
config := &vueglue.ViteConfig{
Environment: "production",
AssetsPath: "dist",
EntryPoint: "src/main.js",
FS: os.DirFS("dist"),
}
// ...OR this for development:
// Development configuration
config := &vueglue.ViteConfig{
Environment: "development",
AssetsPath: "frontend",
EntryPoint: "src/main.js",
FS: os.DirFS("frontend"),
} glue, err := vueglue.NewVueGlue(config)
if err != nil {
//bail!
}
vueGlue = glue
}
To inject the glue into your templates, do something similar to this in your handler code:
ts, err := template.ParseFiles("path/to/your-template.tmpl")
if err != nil {
// better handle this...
}
ts.Execute(respWriter, vueGlue)
and something like this in your templates:
<!doctype html>
<html lang='en'>
{{ $vue := . }}
<head>
<meta charset='utf-8'>
<title>Home - Vue Loader Test</title>
{{ if $vue }}
{{ $vue.RenderTags }}
{{ end }}
</head>
<body>
<div id="app"></div>
</body>
</html>
Supporting the Dev Server
Under the hood, supporting the Vite dev server is pretty simple: you just need to add a script link to your rendered web page pointing to the dev server. If we’re linking a typical Vue 3 application that does not use Typescript, the default link will be
<script type="module" src="http://localhost:3000/src/main.js"></script>
vite-go will do this for you as well, by injecting this tag into your templates, using code like in the previous section.
Serve Assets From Your Go App (Both Development and Production)
Configuring a static file server to get processed JavaScript and CSS into your webpages is not that hard to do with http.FileServer(), but vite-go makes it even easier by keeping your configuration in sync with the file server. The code is very simple once you’ve created your glue object. It also makes sure that dot files and hidden directories are not accessible via the static file server:
// using the standard library's multiplexer:
mux := http.NewServeMux()
// Set up a file server for our assets.
fsHandler, err := glue.FileServer()
if err != nil {
log.Println("could not set up static file server", err)
return
}
mux.Handle("/src/", fsHandler)
See the docs for a few gotchas, since some routers will require you to handle “/src/*” instead of “/src/”.
Deploying Your Finished Code
Serving your production code is much easier using an integration solution like vite-go. The key here is to configure your vite.config.js file so it creates a manifest file, and also have the config file put the dist/ directory somewhere convenient for Go to find it. If your main.go file is under cmd/web/, you’ll want your vite.config.js file (this should be in your “frontend” directory in our example) to look something like this:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
outDir: '../cmd/web/dist',
manifest: "manifest.json",
},
})
If you use the production configuration from above, this is seamless. The glue parses the manifest.json file placed in the dist/ directory by the Vite builder code, and generates the (multiple) needed tags into your web pages. This is much less error prone than attempting to do it manually.
Whatever your platform for your web applications, using an integration library like vite-go will make your development life easier.
Rob Thorne is a full-stack developer who’s doing more and more devops these days. He’s available for hire. You can find Rob on Twitter as @torenware.