GoScript is a Go to TypeScript compiler that translates Go code to TypeScript at the AST level. Perfect for sharing algorithms and business logic between Go backends and TypeScript frontends.
Right now goscript looks pretty cool if you problem is "I want this self-sufficient algorithm be available in Go and JS runtimes". gopherjs's ambition, however, has always been "any valid Go program can run in a browser". There is a lot that goes on in gopherjs that is necessary for supporting the standard library, which goes beyond cross-language translation.
β nevkontakte, developer of GopherJS
Write once, run everywhere. Share your Go algorithms, business logic, and data structures seamlessly between your backend and frontend without maintaining two codebases.
β Perfect for:
- Sharing business logic between Go services and web apps
- Porting Go algorithms to run in browsers
- Building TypeScript libraries from existing Go code
- Full-stack teams that love Go's simplicity
- Structs, interfaces, methods, and functions
- Channels and goroutines (translated to async/await)
- Slices, maps, and most built-in types
- Basic reflection support
- Standard control flow (if, for, switch, etc.)
Current limitations:
- Uses JavaScript
number
type (64-bit float, not Go's int types) - No pointer arithmetic (
uintptr
) orunsafe
package - No complex numbers
- Limited standard library (growing rapidly)
If you're building algorithms, business logic, or data processing code, GoScript has you covered! π
π Learn more: Design document | Compliance tests
Option 1: Go Install
go install github.com/aperturerobotics/goscript/cmd/goscript@latest
Option 2: NPM
npm install -g goscript
# Compile your Go package to TypeScript
goscript compile --package . --output ./dist
Go Code (user.go
):
package main
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func (u *User) IsValid() bool {
return u.Name != "" && u.Email != ""
}
func NewUser(id int, name, email string) *User {
return &User{ID: id, Name: name, Email: email}
}
func FindUserByEmail(users []*User, email string) *User {
for _, user := range users {
if user.Email == email {
return user
}
}
return nil
}
Compile it:
goscript compile --package . --output ./dist
Generated TypeScript (user.ts
):
export class User {
public ID: number = 0
public Name: string = ""
public Email: string = ""
public IsValid(): boolean {
const u = this
return u.Name !== "" && u.Email !== ""
}
constructor(init?: Partial<User>) {
if (init) Object.assign(this, init)
}
}
export function NewUser(id: number, name: string, email: string): User {
return new User({ ID: id, Name: name, Email: email })
}
export function FindUserByEmail(users: User[], email: string): User | null {
for (let user of users) {
if (user.Email === email) {
return user
}
}
return null
}
Use in your frontend:
import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
// Same logic, now in TypeScript!
const users = [
NewUser(1, "Alice", "[email protected]"),
NewUser(2, "Bob", "[email protected]")
]
const alice = FindUserByEmail(users, "[email protected]")
console.log(alice?.IsValid()) // true
Go Code:
func ProcessMessages(messages []string) chan string {
results := make(chan string, len(messages))
for _, msg := range messages {
go func(m string) {
// Simulate processing
processed := "β " + m
results <- processed
}(msg)
}
return results
}
Generated TypeScript:
export function ProcessMessages(messages: string[]): $.Channel<string> {
let results = $.makeChannel<string>(messages.length, "")
for (let msg of messages) {
queueMicrotask(async (m: string) => {
let processed = "β " + m
await results.send(processed)
})(msg)
}
return results
}
Use with async/await:
import { ProcessMessages } from '@goscript/myapp/processor'
async function handleMessages() {
const channel = ProcessMessages(["hello", "world", "goscript"])
// Receive processed messages
for (let i = 0; i < 3; i++) {
const result = await channel.receive()
console.log(result) // "β hello", "β world", "β goscript"
}
}
goscript compile --package ./my-go-code --output ./dist
Options:
--package <path>
- Go package to compile (default: ".")--output <dir>
- Output directory for TypeScript files
Go:
import "github.com/aperturerobotics/goscript/compiler"
conf := &compiler.Config{OutputPath: "./dist"}
comp, err := compiler.NewCompiler(conf, logger, nil)
_, err = comp.CompilePackages(ctx, "your/package/path")
Node.js:
import { compile } from 'goscript'
await compile({
pkg: './my-go-package',
output: './dist'
})
React + GoScript:
import { NewCalculator } from '@goscript/myapp/calculator'
function CalculatorApp() {
const [calc] = useState(() => NewCalculator())
const handleAdd = () => {
const result = calc.Add(5, 3)
setResult(result)
}
return <button onClick={handleAdd}>Add 5 + 3</button>
}
Vue + GoScript:
<script setup lang="ts">
import { NewUser, FindUserByEmail } from '@goscript/myapp/user'
const users = ref([
NewUser(1, "Alice", "[email protected]")
])
const searchUser = (email: string) => {
return FindUserByEmail(users.value, email)
}
</script>
Current Status:
- β Core language features (structs, methods, interfaces)
- β Async/await for goroutines and channels
- β Basic reflection support
- β Most control flow and data types
Coming Soon:
- π¦ Expanded standard library
- π§ͺ Go test β TypeScript test conversion
- β‘ Performance optimizations
- π§ Better tooling integration
Check our compliance tests for detailed progress.
Fintech: Share complex financial calculations between Go services and trading dashboards
Gaming: Run the same game logic on servers and in browser clients
Data Processing: Use identical algorithms for backend ETL and frontend analytics
Validation: Keep business rules consistent across your entire stack
Ready to eliminate code duplication? Get started now π
MIT