Open-source project
gopherjs/gopherjs avatar
gopherjs/gopherjs

GopherJS: compiling Go to JavaScript without WebAssembly

A compiler from Go to JavaScript for running Go code in a browser

13,188 stars574 forksGoBSD-2-Clause

At a glance

What is it?
GopherJS compiles Go packages into plain JavaScript so the same language runs in the browser. It requires Go 1.21, pins itself to a matching Go toolchain, and does not support cgo.
Who is it for?
Adopt GopherJS if you already have Go packages whose logic you want running in a browser without a WebAssembly toolchain, and you accept that the compiler tracks Go releases with a lag: the newest release, v1.21.0, targets Go 1.21.13, so a project on Go 1.24 or later must keep a separate 1.21 toolchain around and point GOPHERJS_GOROOT at it.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 40 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What GopherJS is for, and who it is not for

The README states the purpose plainly: GopherJS compiles Go code to pure JavaScript, and its main purpose is to let you write front-end code in Go that still runs in all browsers. That is a narrower claim than "run Go anywhere." The output is JavaScript, not a binary and not a WebAssembly module, so it loads the way any script tag loads and runs on browsers that have no WebAssembly support at all.

The audience follows from that. If you have Go packages holding validation rules, parsers, state machines, or game logic, and you want that exact code executing in the browser instead of a reimplementation in TypeScript, GopherJS is aimed at you. The README also points at bindings for other libraries as a contribution path, which tells you the project expects users to bridge into existing JavaScript code rather than live in a sealed Go world.

It is the wrong tool in at least two cases. Cgo is not supported, full stop, so anything reaching into C libraries is out. And if your front end is a mature JavaScript or TypeScript codebase with its own component model, adding a Go compilation step buys you a second build system and a language boundary, not a simplification.

How the compiler turns Go packages into JavaScript

The repository layout separates the work. The compiler/ directory holds the translation itself, js/ holds the JavaScript-facing package that Go code calls into, nosync/ is a build variant, and build/ plus tool.go sit at the top level alongside embed.go. The go.mod file lists github.com/evanw/esbuild as a direct dependency, which is the bundler and minifier stage after translation, and github.com/neelance/sourcemap for the .js.map output. github.com/neelance/astrewrite appears as a dependency, consistent with the compiler rewriting Go syntax trees before emission.

Goroutines are supported, and the README links a compatibility document for the details. Since there are no OS threads in a browser, the implementation has to schedule goroutines cooperatively on the single JavaScript event loop, which is why the compatibility page exists rather than a one-line promise. The README also notes that on supported GOOS platforms system calls such as file system access can be made available, with instructions in doc/syscalls.md. That matters for gopherjs run and gopherjs test, which execute the generated code under Node.js rather than in a browser.

One build detail is worth knowing before you hit it: GopherJS tries to write compiled object files of the core packages into $GOROOT/pkg, and if that fails it falls back to $GOPATH/pkg. On a read-only or system-managed Go installation the first attempt fails quietly and the second path carries the cost.

Installing GopherJS and running a first build

The README gives go install as the installation route, with the version pinned in the module path. Run this and the gopherjs binary lands in your Go bin directory:

bash
go install github.com/gopherjs/[email protected]

The README states GopherJS requires Go 1.21 or newer, and the v1.21.0 release is described as GopherJS 1.21.0 for Go 1.21.13. If the Go distribution reported by go version is newer than Go 1.21, the README says you must set GOPHERJS_GOROOT to a directory containing a Go 1.21 distribution. The documented way to get one is the golang.org/dl downloader:

bash
go install golang.org/dl/go1.21.13@latest
go1.21.13 download
export GOPHERJS_GOROOT="$(go1.21.13 env GOROOT)"

After that, build a main package the way you would with the go tool. The README states that for main packages these commands create a .js file and a .js.map source map in the current directory or in $GOPATH/bin:

bash
gopherjs build [package]

During development, gopherjs serve is the faster loop. It starts an HTTP server on ":8080" by default and compiles packages on request. The README gives this example: navigating to http://localhost:8080/example.com/user/project/ compiles and runs the Go package example.com/user/project, and the generated JavaScript is served at http://localhost:8080/example.com/user/project/project.js, where the file name equals the base directory name. If the directory has an index.html it is served; otherwise GopherJS supplies a minimal one containing a script tag for project.js. Refreshing the browser rebuilds if needed, and compilation errors appear both in the terminal and in the browser console.

If you pass an argument, it becomes the root everything is served from. The README's example: gopherjs serve github.com/user/project serves the generated JavaScript for github.com/user/project/mypkg at http://localhost:8080/mypkg/mypkg.js.

Platform constraints and the Mac LC_UUID error

GopherJS uses your platform's default GOOS when generating code, and the README lists the supported values as linux and darwin. On Windows or FreeBSD you have to set GOOS to a supported value yourself, for example GOOS=linux gopherjs build [package]. That is a build-time setting, not a runtime restriction on where the JavaScript runs, but it does mean the compiler is not equally at home on every development machine.

The Mac failure mode is documented and specific. If you compile the gopherjs app with anything less than go1.24, you may see a dyld error reading "missing LC_UUID load command in ...". The README attributes this to a Go and Mac issue fixed in go1.24, and notes it only appears because GopherJS has not reached go1.24 support yet. The documented workaround is to build the gopherjs app with go1.24 or newer. The catch is in the next sentence: because of how GopherJS targets specific STL versions, you must still use the correct STL for the version of GopherJS you have, which means setting GOPHERJS_GOROOT as described above. So the fix is two toolchains, one to build the compiler and one for it to compile against.

Two environment variables are documented. GOPHERJS_GOROOT overrides the default GOROOT. GOPHERJS_SKIP_VERSION_CHECK, when set to true, disables the check of the Go version in the GOROOT. Setting that second one is how you get past the version gate, and it is also how you end up compiling against a Go version the project has not tested.

GopherJS against WebAssembly and V8go

The obvious comparison is WebAssembly. The difference is in the output artifact and what it demands of the browser. GopherJS emits JavaScript, so the result is a script the browser already knows how to load, and the README's claim that output runs in all browsers follows from that. A WebAssembly build produces a .wasm module plus the JavaScript glue to instantiate it, and the runtime support that implies. If your target includes older browsers or embedded webviews, that difference decides the question before performance does. The README points at an HTML5 game engine benchmark for performance and says performance is quite good in most cases; treat that as the project's own pointer, not a number to plan around.

V8go is a different shape of tool entirely. It embeds V8 so a Go program can execute JavaScript, which is the inverse direction: Go hosts JavaScript rather than compiling to it. If your problem is running untrusted or third-party JavaScript inside a Go service, V8go is the relevant comparison. If your problem is running your Go logic inside a page, GopherJS is.

Vecty comes up in the same searches because it is a Go front-end library, and the pairing is natural: GopherJS is the compiler, Vecty is something you might compile with it. That is a layering relationship, not a competition. The same goes for React bindings, which the README's bindings wiki page covers as a category.

Version tracking, licence, and what upgrading costs

The release history sets expectations. Go 1.21 support shipped as v1.21.0 on 2026-06-26, Go 1.20 support as v1.20.0 on 2026-01-02, and Go 1.19 support arrived in beta form in 2024 before stabilizing. The gap between a Go release and GopherJS support for it is measured in months to years, not weeks. The last push to the repository was on 2026-08-20, so work is ongoing, but the cadence of Go support is the number that governs your upgrade planning.

That lag has a direct cost. Each GopherJS release is tied to a specific Go patch level, and the README's own instructions require a separate Go 1.21.13 installation for the current release. Upgrading GopherJS therefore means managing a second Go toolchain, not just bumping a version string, and the GOPHERJS_GOROOT variable is the seam where that shows up. A team that tracks Go closely will feel this every time the main toolchain moves.

The licence is BSD-2-Clause, a permissive licence, and the go.mod file lists dependencies including github.com/evanw/esbuild, github.com/sirupsen/logrus, github.com/spf13/cobra, and several golang.org/x modules. Redistributing compiled JavaScript output is a different question from redistributing the compiler, and the answer depends on which of those dependencies end up in your bundle. That is a question for your own legal review; the repository's LICENSE file is the source to read.

Editorial conclusion

Adopt GopherJS if you already have Go packages whose logic you want running in a browser without a WebAssembly toolchain, and you accept that the compiler tracks Go releases with a lag: the newest release, v1.21.0, targets Go 1.21.13, so a project on Go 1.24 or later must keep a separate 1.21 toolchain around and point GOPHERJS_GOROOT at it. Do not adopt it if you depend on cgo, if you need the current Go release the day it ships, or if your front end is already a JavaScript framework you are happy with. Verify two things before committing: that every package you need compiles under Go 1.21.13, and that your deployment can serve the .js and .js.map pair the build produces.

Frequently asked questions

How do I install GopherJS?

The README gives go install github.com/gopherjs/[email protected], which places the gopherjs binary in your Go bin directory. If your local Go is newer than Go 1.21, you also need to set GOPHERJS_GOROOT to a directory containing a Go 1.21 distribution.

Does GopherJS support cgo?

No. The README states that cgo is not supported, so any package that reaches into C libraries cannot be compiled with GopherJS.

What is GopherJS used for?

The README describes its main purpose as letting you write front-end code in Go that still runs in all browsers, by compiling Go to pure JavaScript. It also supports goroutines, with the details in a separate compatibility document.

Is GopherJS the same as WebAssembly?

No. GopherJS emits pure JavaScript, while a WebAssembly build produces a .wasm module plus JavaScript glue to instantiate it. The README's claim that output runs in all browsers follows from the JavaScript output, which does not require WebAssembly runtime support.

Official sources

  1. gopherjs/gopherjs on GitHub
  2. Issues
  3. License: BSD-2-Clause
  4. README
  5. Releases
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/gopherjs-gopherjs.svg)](https://hysenlabs.com/projects/gopherjs-gopherjs)
Community notes

Community notes