Your Go code runs anywhere TypeScript runs: Node, Bun, and the browser.
GoScript compiles Go packages into readable TypeScript modules. Write your
program in Go, with its standard library, its package ecosystem, and its
concurrency, and run it in any web browser, in Node, or in Bun.
Goroutines, channels, select, defer, pointers, and struct copies behave as
they do in Go, and the output is ordinary TypeScript you can import, bundle,
and step through in a debugger.
go install github.com/s4wave/goscript/cmd/goscript@latest
goscript compile --package . --output ./outputGo gives you a strong standard library, a large ecosystem of packages, simple modules, and concurrency built into the language. GoScript brings all of it to the browser without a WebAssembly runtime. The compiled program is plain TypeScript, a fraction of the size of the same program built for WebAssembly:
| Program | GoScript | Go WebAssembly | Smaller by |
|---|---|---|---|
Hello world with fmt |
47 KB | 543 KB | 12x |
encoding/json, sort, strings |
71 KB | 904 KB | 13x |
| Spacewave browser core, with go-git | 3.7 MB | 14.4 MB | 4x |
Sizes are Brotli-compressed. The GoScript output is bundled with Bun and
minified with oxc. The WebAssembly builds use Go 1.27.1 with
-trimpath -ldflags='-s -w' and exclude the 17 KB wasm_exec.js loader.
Blocking Go code meets JavaScript through standard async and await. A Go
function that can block compiles to an async function, so TypeScript calls it
with await, and a js.FuncOf callback that blocks returns a Promise to its
JavaScript caller. Goroutines run as async tasks on the event loop, beside the
rest of the page.
Generated code also runs on the engine's own garbage collector and JIT. Go values are ordinary JavaScript objects, so the engine optimizes the hot paths and collects what the program drops. A Go WebAssembly program runs its own collector inside linear memory, which can grow but never shrinks, so the page keeps its peak heap until it reloads.
Use GoScript when Go is the source of truth and part of your product runs in a TypeScript runtime: shared validation and business rules, TypeScript packages published from Go code, or a Go application running in the browser without a rewrite.
GoScript is built against Spacewave, a
large Go and TypeScript application framework. Spacewave compiles its browser
core through GoScript, including go-git and the go-mysql-server SQL engine, and
runs its core package tests through goscript test in CI.
GopherJS shares the goal of running Go in JavaScript and ships its own goroutine scheduler. GoScript emits readable TypeScript modules and maps goroutines onto JavaScript async functions.
GoScript needs the Go toolchain. Install the CLI with Go:
go install github.com/s4wave/goscript/cmd/goscript@latestOr add it to a JavaScript project. The npm package runs the same compiler through your local Go toolchain and adds the TypeScript API:
bun add -d goscriptBun runs generated code directly. For Node and browsers,
bundle it with a bundler that resolves tsconfig.json paths, such as Bun,
Vite, or esbuild.
Write a Go program:
// main.go
package main
import "fmt"
type Greeter struct{ Name string }
func (g Greeter) Greet() string { return "Hello, " + g.Name + "!" }
func main() {
ch := make(chan string)
go func() { ch <- Greeter{Name: "GoScript"}.Greet() }()
fmt.Println(<-ch)
}Compile it from the module directory. GoScript also emits the packages it
imports, here fmt, so the output runs on its own:
goscript compile --package . --output ./outputPoint @goscript/* imports at the output in tsconfig.json:
{
"compilerOptions": {
"paths": { "@goscript/*": ["./output/@goscript/*"] }
}
}Run it:
$ bun run output/@goscript/example.com/hello/main.gs.ts
Hello, GoScript!Each Go package becomes a directory under output/@goscript/, named by its
import path, with one .gs.ts file per Go file and an index.ts that exports
the package. A package main runs directly; any other package is a module you
import:
import { NewUser } from '@goscript/example.com/my/module/index.js'docs/typescript.md has the full tsconfig.json for
typechecking and bundling generated code.
goscript compile --package ./pkg/... --output ./output--package takes any Go package pattern and repeats. GoScript compiles the
requested packages and every package they import. --skip-dependencies emits
only the requested packages and the runtime, for builds that compile
dependencies separately.
docs/cli.md lists every option.
goscript test compiles a package's Go tests to TypeScript and runs them with
Bun, or in Chromium with --browser. The output follows go test:
goscript test --tags goscript ./...import { compile } from 'goscript'
await compile({
pkg: '.',
output: './output',
dir: process.cwd(),
})comp, err := compiler.NewCompiler(&compiler.Config{
Dir: ".",
OutputPath: "./output",
}, nil, nil)
if err != nil {
return err
}
_, err = comp.CompilePackages(ctx, ".")github.com/s4wave/goscript/compiler/wasm builds to WebAssembly and compiles a
single Go source file to TypeScript in the page. It accepts files without
imports; the website playground uses it.
ts, err := wasm.CompileSource(src, "main")- Go language: structs, interfaces, generics, closures, slices, maps, and value copies.
- Pointers:
&x, pointers to pointers, and pointer receivers behave as in Go. - Concurrency: goroutines, channels,
select,sync,defer,panic, andrecover. - Exact integers:
int64anduint64compile tobigintand wrap like Go. - Control flow:
goto, labels, type switches, andrangeover iterator functions. - Standard library:
fmt,strings,sync,time,encoding/json,crypto, and more. - Third-party packages: go-git, klauspost/compress, blake3, protobuf-go-lite, and more.
- Go tests:
goscript testruns your Go tests on the output in Bun or Chromium. - Large programs: GoScript compiles Spacewave's browser core, including go-git.
- Async interop: blocking Go functions compile to
asyncfunctions that TypeScript awaits. - In the browser: the compiler itself runs in the page through WebAssembly.
Go packages -> type check -> semantic model -> lowered IR -> TypeScript
+ runtime + overrides
GoScript loads packages with the Go toolchain and type-checks them. It then
decides which variables need a pointer box, which functions must become
async, and how each type maps to TypeScript, before it writes any text. The
emitter renders the result, and the compiler copies the
@goscript/builtin runtime and any handwritten
overrides for packages such as sync, os, and reflect.
A function that can block on a channel, select, or a lock becomes async,
and so does every function that calls it. Goroutines run as async tasks on the
JavaScript event loop, and no WebAssembly runtime ships with your code.
docs/explainer.md walks through each stage with
generated output for structs, pointers, channels, and defer.
docs/performance.md explains where Go semantics cost
extra work in JavaScript, and how GopherJS and WebAssembly handle the same
cases.
GoScript compiles most Go code as written. These are the known gaps:
- Inputs: the CLI and APIs take package patterns, not individual
.gofiles. - Browser compiler: compiles a single file without imports. Use the CLI for real packages.
unsafeand cgo: cgo is unsupported, andunsafe.Sizeofand pointer conversions throw.int: a JavaScript number. It loses precision past 2^53 and does not wrap at 64 bits.- Standard library: a package works when GoScript overrides it or it compiles from Go source.
- Host APIs: sockets, processes, and plugins work only as far as the JavaScript host allows.
reflect: covers types, values, struct fields, maps,MakeFunc, andDeepEqual, not all.goscript test: supports a subset oftestingand of thego testflags.- Speed: async calls and
bigintmath cost more than synchronous code on plain numbers.
Use int64 or uint64 where a value needs exact 64-bit arithmetic: both
compile to bigint and wrap as in Go.
bun install
bun run test
bun run lint
bun run buildbun run example compiles and runs example/simple.
bun run website:build builds the website and playground.
example/app is a full-stack application built on generated
TypeScript.
The compliance tests under tests/tests are Go programs compiled, typechecked, and run against expected output.
To fix a missing Go behavior, add a focused compiler or compliance test that reproduces it, then implement the behavior in the compiler or runtime stage responsible for it.
Open an issue for Go code GoScript cannot compile, runtime gaps, and missing standard-library overrides.
MIT