A minimal Go-to-JavaScript transpiler focused on clean, readable output for browser UI code.
This is a finished experiment demonstrating a minimal transpiler design. It's not intended to replace GopherJS or WebAssembly, but explores a different design point in the Go→JS compilation space.
goscript targets a specific niche:
- ✅ Small Go subset - Structs, functions, basic control flow
- ✅ Clean JavaScript output - Human-readable, minimal runtime
- ✅ Direct DOM bindings - WebGL, WebSocket via
.extern.gofiles - ✅ No runtime overhead - Unlike full Go compilers
- ✅ Goroutines, channels (via async/await)
- ✅ Defer, panic, recover
Not supported:
- ❌ Interfaces
- ❌ Full Go standard library (partial support for fmt, strings)
- ❌ Reflection, complex generics
- ❌ Production support or maintenance guarantees
- 📚 Learning compiler implementation - Clean ~2k LOC transpiler
- 🔬 Research/experiments - Comparing transpiler approaches
- 📦 Size-constrained environments - Minimal JS output matters
- 🎓 Teaching - Real working example for courses
- 🏭 Need production support → Use Go WebAssembly
- 🔧 Need full Go language → Use GopherJS
- 🚀 Building serious web apps → Use TypeScript or standard Go WASM
Input (Go):
package app
type Vec2 struct {
X float64
Y float64
}
func Main() {
pos := Vec2{X: 10, Y: 20}
canvas := Document.GetElementsByTagName("canvas")[0]
gl := canvas.GetContext("webgl")
gl.ClearColor(0, 0, 0, 1)
}Output (JavaScript):
class Vec2 {
constructor() {
this.X = 0;
this.Y = 0;
}
}
function Main() {
const pos = new Vec2();
pos.X = 10;
pos.Y = 20;
const canvas = document.getElementsByTagName("canvas")[0];
const gl = canvas.getContext("webgl");
gl.clearColor(0, 0, 0, 1);
}- Parse Go code using
go/parserandgo/ast - Transform to JS AST - Custom JavaScript AST representation
- Link external symbols - Map Go types to browser APIs
- Generate JavaScript - Pretty-print readable output
Define browser APIs with .extern.go files:
// dom.extern.go
type HTMLCanvasElement struct{}
func (c HTMLCanvasElement) GetContext(ctx string) WebGLRenderingContext
type WebGLRenderingContext struct{}
func (gl WebGLRenderingContext) ClearColor(r, g, b, a float64)The transpiler recognizes these as external symbols and generates appropriate JS calls.
dommer is a tool included in goscript that generates Go extern definitions from TypeScript declaration files (.d.ts).
- Input: TypeScript definition files (e.g.,
lib.dom.d.ts) - Output: Go extern files (e.g.,
dom.extern.go) - Purpose: Automates the creation of type-safe browser API bindings.
go run cmd/dommer/main.go -input lib/lib.dom.d.ts -output app/dom.extern.go┌─────────────┐
│ Go Source │
└──────┬──────┘
│ go/parser
▼
┌─────────────┐
│ Go AST │
└──────┬──────┘
│ transpiler/
▼
┌─────────────┐
│ JS AST │ (js/ package)
└──────┬──────┘
│ link/
▼
┌─────────────┐
│ JavaScript │
└─────────────┘
The transpiler uses a unified Effects & Frames theory to handle advanced control flow (defer, panic, goto, goroutines). By scanning functions for effects, it selects the appropriate "Frame" (wrapper) to manage state, errors, or async operations without ad-hoc hacks.
See ARCHITECTURE.md for the deep dive on this system.
See examples/webgl-app/ for a complete WebGL+WebSocket demo:
- Real-time rendering with WebGL
- Binary WebSocket communication
- Go structs mapped to JS objects
- ~150 LOC of Go → clean JavaScript
go get github.com/rryanrussell/goscriptpackage main
import (
"os"
"goscript"
)
func main() {
// Transpile directory to stdout
if err := goscript.Transpile("./myapp", os.Stdout); err != nil {
panic(err)
}
}Or use as CLI:
go run github.com/rryanrussell/goscript ./myapp > output.js| Tool | Approach | Output Size | Go Coverage | Status |
|---|---|---|---|---|
| goscript | Minimal subset | Small | ~15% | Experimental |
| GopherJS | Full compiler | Large | ~95% | Maintained |
| Go WASM | Native WASM | Medium | 100% | Official |
| Joy | Idiomatic subset | Small | ~30% | Abandoned (2017) |
goscript is closest to Joy in philosophy but more recent and focused on direct DOM access.
Language Features:
- No interfaces or reflection
- Limited standard library (fmt, strings supported; others missing)
- Basic type system only
Output:
- No source maps (yet)
- No optimization passes
- No minification
- Requires manual extern definitions (automated via
dommer)
Stability:
- No semantic versioning
- Breaking changes possible
- Minimal test coverage
- Educational project, not production-ready
See ROADMAP.md for future plans and known limitations to be addressed.
This is a finished experiment, but contributions welcome for:
✅ Documentation improvements ✅ Bug fixes ✅ Example additions ✅ Educational materials
For major features, consider forking! This project intentionally stays small.
# Clone
git clone https://github.com/rryanrussell/goscript
cd goscript
# Run example
cd examples/webgl-app
go run ../../main.go . > output.js
# Inspect JS AST
cd js
go docDeveloped in 2022 as part of a genetic algorithm framework for training neural networks. The transpiler enabled writing browser UI code in Go while keeping the JS output minimal and readable. Extracted and released standalone in 2025 for educational value.
Original need: Write browser UI in Go without 2MB WASM runtime overhead.
Broader value: Demonstrates transpiler design for minimal language subsets.
Lesson learned: WASM is the right choice for most cases, but exploring alternatives teaches you a lot about both compilers and the constraints they navigate.
- Go WebAssembly - Official, full Go support
- GopherJS - Mature, extensive compatibility
- TinyGo WASM - Smaller WASM builds
- TypeScript - If you don't need Go specifically
MIT License - see LICENSE
Ryan Russell (@rryanrussell)
Built with curiosity about minimal transpilers and a desire for clean JavaScript output.
Remember: This is an educational project showing one approach to Go→JS transpilation. It's not trying to be GopherJS. It's not trying to replace WASM. It's exploring a specific design point and hopefully teaching something along the way.
If you build something cool with it, let me know! If you use it to learn compilers, even better.