Build your Golang package docs locally

Previewing the HTML of your go docs from localhost

Writing Golang docs

For my recent focus, I've been needing to write more than I ever have before. Given the nature of the project, this also means composing technical docs for a variety of ecosystems. While certain tools vary in friction for writers to do their jobs, this post is for a workflow I currently have set up for writing Golang docs.

The ideal

Similar to my Rust setup, I wanted to see a rendering of my module docs as they would on pkg.go.dev locally before publishing. This used to be possible with godoc but it's been deprecated since then with no official solution.

The recommendation now is to point users to pkgsite (the tool used to render pkg.go.dev) but it's left as an exercise to the user on exactly what the setup should be.

So let's do the exercise.

pkgsite cmd

golang/pkgsite hosts the cli pkg.go.dev uses for docs rendering. The comments of the pkgsite cmd are concise but can get us started:

// Pkgsite extracts and generates documentation for Go programs.
// It runs as a web server and presents the documentation as a
// web page.
//
// To install, run `go install ./cmd/pkgsite` from the pkgsite repo root.

We'll use this to set up a local server version of pkg.go.dev that contains the documentation rendering functionality for Golang packages. So let's install it. The recommendation I've seen elsewhere is to locally clone this repo, build it and install it somewhere in your $PATH but this go get install should just work:

$ go install golang.org/x/pkgsite/cmd/pkgsite@latest

Once we have it installed, it should be as easy as running it where our go package repo lives:

$ cd /path/to/go/pkg
$ pkgsite
2022/06/16 10:13:55 Info: Listening on addr http://localhost:8080

You can customize the bound port with -http localhost:3030

Opening up that localhost site will show you a page similar to pkg.go.dev with the search bar and everything. Of course, you won't see any packages if you search because we're rendering just the local package (although there are flags to change this).

Your local docs

Now, similar to pkg.go.dev, you can check your local module's documentation by appending the module path from your go.mod to the url:

http://localhost:8080/my/local/module/path

And that's it. You can now see the docs you wrote for your module in web form. With just that pkgsite install you can now preview what the docs for your Go project will look like before publishing.

Yet, can we push it further? I don't want to have to manually save the file, kill the server, start it again and refresh the page between thoughts. I want to just have a browser tab open rendering the page as I type from my text editor.

Hot reload

For this, we'll need a couple of more tools. One that watches when our Go files change and another to communicate to our browser when to reload. Ideally, there would be a single tool to do this (maybe even one day we can have pkgsite come with this functionality built in...). If you're from the frontend JavaScript world, this is table stakes and it usually comes with the framework's tooling. Now we'll work for it.

My tool of choice for file watching is nodemon. It does its job well and lets me configure it when I need to (and we'll need to). At first, I wanted to find a Go-based file watcher but I couldn't find any that could do what I wanted. I even tried to shoehorn cosmtrek/air into this, but didn't work out.

Now that we have a file watcher, we'll want to tell the browser when to reload the page. We'll use browser-sync for this.

Terminal commands

Here are the key commands we'll use. Open up a new terminal for each process or just background either in the same one:

$ browser-sync start --proxy "localhost:8080"
[Browsersync] Proxying: http://localhost:8080
[Browsersync] Access URLs:
--------------------------------------
Local: http://localhost:3000
External: http://192.168.1.227:3000
--------------------------------------
UI: http://localhost:3001
UI External: http://localhost:3001
--------------------------------------

The reason we need to set up this proxy is because we need an automatic way to communicate to the browser that something has changed. browser-sync injects a bit of javascript into our requests that listens to a signal as to when to reload.

$ nodemon --signal SIGTERM --watch my-mod.go --exec "browser-sync reload && pkgsite ."
[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): my-mod.go
[nodemon] starting `browser-sync reload && pkgsite .`

And this is how we create that signal. nodemon watches my-mod.go for file changes and auto-restarts the pkgsite process while sending a signal to browser-sync.

Conclusion

With both of these running in parallel, you should now have automated documentation reloading. I can now write my go doc comments in my text editor, save the file and see the tab reload.

There are less excuses to not write now but the developer experience could still be better. Obviously besides just the pain of setting this up, the reloading is slower than necessary. We shouldn't have to wait for the entire process to start and then wait again for it to render my local module before seeing it in my browser tab.

Perhaps by the time you read this article, that pkgsite issue will be resolved and more doc-focused tooling will exist. Or maybe I'll have a free weekend and send some PRs.

If you see any inaccuracies, typos or have comments, please reach out @mdaverde.