GoScript

Go to TypeScript compiler

Write it in Go.Run it anywhere.

GoScript compiles Go packages into readable TypeScript. Bring the standard library, your favorite packages, and goroutines to any web browser, Node, or Bun, without shipping a WebAssembly runtime.

main.go
package main

import "fmt"

func main() {
	ch := make(chan string)
	go func() { ch <- "Hello from a goroutine" }()
	fmt.Println(<-ch)
}
main.gs.ts
export async function main(): globalThis.Promise<void> {
	let ch = $.makeChannel<string>(0, "", "both")
	queueMicrotask(async () => {
		await $.chanSend(ch, "Hello from a goroutine")
	})
	await fmt.Println(await $.chanRecv(ch))
}

12x smaller than Go WebAssembly for hello world

async and await at every blocking call

No runtime beyond a small TypeScript library

Why Go

Everything Go gives you, in the browser

Write the code once in Go and keep the tools that make Go pleasant. GoScript compiles the program you already have, so its packages, tests, and concurrency come with it.

Standard library

fmt, strings, sync, time, encoding/json, and crypto compile with your code.

Go packages

Import modules as usual. go-git, klauspost/compress, and blake3 run on the output.

Concurrency

Goroutines, channels, select, and sync keep their Go semantics on the event loop.

Go tests

goscript test runs your Go tests on the output, in Bun or in Chromium.

Bundle size

A fraction of the bytes

GoScript output is ordinary TypeScript modules, so your bundler minifies and tree-shakes it like any other code. The same Go program built for WebAssembly ships the whole Go runtime.

Hello world with fmt12x smaller

GoScript47 KB
Go WebAssembly543 KB

encoding/json, sort, and strings13x smaller

GoScript71 KB
Go WebAssembly904 KB

Spacewave browser core, with go-git4x smaller

GoScript3.7 MB
Go WebAssembly14.4 MB

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.

Runs on the engine

Plain async and await

A Go function that can block compiles to an async function, so TypeScript calls it with await. A js.FuncOf callback that blocks returns a Promise to its JavaScript caller, and goroutines run as async tasks beside the rest of the page.

Go values are ordinary JavaScript objects, so the engine's JIT optimizes the hot paths and its garbage collector frees what the program drops. A WebAssembly program runs its own collector inside linear memory, which grows but never shrinks.

app.ts
import { NewUser } from '@goscript/example.com/my/module/index.js'

// A Go function that blocks is an async function here.
const user = await NewUser('gopher')
console.log(user.Name)

Install

Compile your first package

GoScript needs the Go toolchain. Bun runs the output directly. For Node and browsers, bundle it with a bundler that reads tsconfig.json paths, such as Bun, Vite, or esbuild.

The npm package runs the same compiler and adds a TypeScript API: bun add -d goscript.

  1. Install the compiler

    shell
    $ go install github.com/s4wave/goscript/cmd/goscript@latest
  2. Compile the package in the current directory

    shell
    $ goscript compile --package . --output ./output
  3. Run it, with @goscript/* mapped to the output

    shell
    $ bun run output/@goscript/example.com/hello/main.gs.ts

Limitations

What to know first

GoScript compiles most Go code as written. These are the known gaps.

Integers

int is a JavaScript number. It loses precision past 253 and does not wrap at 64 bits. Use int64 or uint64 where a value needs exact 64-bit arithmetic: both compile to bigint and wrap as in Go.

unsafe and cgo

cgo and pointer arithmetic are unsupported. Code using unsafe type-checks, but Sizeof, Alignof, Offsetof, and pointer conversions throw at runtime.

Standard library and host APIs

A standard library package works when GoScript ships an override for it or it compiles cleanly from Go source. Sockets, processes, and plugins work only as far as the JavaScript host allows. reflect covers types, values, struct fields, maps, MakeFunc, and DeepEqual, but not the whole package.

Tooling

The CLI and APIs take package patterns, not individual .go files. The in-browser compiler behind the playground compiles a single file without imports. goscript test supports a subset of testing and of the go test flags.

Speed

Async calls and bigint arithmetic cost more than synchronous JavaScript on plain numbers. GoScript makes a function async only when it can block.