Skip to content

i2y/ramune

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ramune

Ramune

A JavaScript/TypeScript runtime for Go — powered by JavaScriptCore, no Cgo required. Pure Go except for JSC: type checker and formatter (typescript-go), linter (rslint), bundler (esbuild), and all Node.js polyfills are built in with zero external tool dependencies.

Named after Ramune, a Japanese carbonated soft drink served in a Codd-neck bottle.

ramune run server.ts          # Run TypeScript
ramune test                   # Run tests
ramune check app.ts           # Type-check
ramune fmt .                  # Format
ramune lint .                 # Lint
ramune compile app.ts -o app  # Compile to standalone binary

What is Ramune?

Ramune is two things:

  1. A JS/TS runtime like Bun or Deno, but built in Go
  2. An embeddable JS engine for Go applications

It loads Apple's JavaScriptCore dynamically via purego — no C compiler, no Cgo, just go build.

Install

macOS

JavaScriptCore is built into macOS — no extra dependencies.

go install github.com/i2y/ramune/cmd/ramune@latest
ramune setup-jit   # enable JIT (~10x faster, recommended)

Linux

sudo apt install libjavascriptcoregtk-4.1-dev   # JSC runtime (required)
go install github.com/i2y/ramune/cmd/ramune@latest

Multi-runtime (RuntimePool, worker_threads) works out of the box on x86_64. On arm64, gcc is required for cgo signal forwarding (apt install gcc).

Smaller binary

go install -tags nosqlite -ldflags="-s -w" github.com/i2y/ramune/cmd/ramune@latest

-tags nosqlite excludes bun:sqlite. -ldflags="-s -w" strips debug info.

Quick Start

Run JavaScript/TypeScript

ramune run app.ts
ramune run -p lodash -p dayjs app.ts   # with npm packages
ramune run                              # reads package.json
ramune run -w server.ts                 # watch mode
ramune run --workers 4 server.ts        # multi-worker HTTP server
ramune run --env-file .env.prod app.ts  # load env file
ramune run dev                          # run package.json script

Evaluate Expressions

ramune eval "1 + 2"
ramune eval "require('crypto').randomUUID()"
ramune eval "const x: number = 42; x"   # TypeScript works

REPL

ramune repl

Packages from package.json are automatically available:

ramune add lodash
ramune repl
> lodash.chunk([1,2,3,4,5,6], 2)
[[1,2],[3,4],[5,6]]

Features: history, tab completion, TypeScript, colors, multiline.

Test Runner

ramune test

Finds *.test.ts, *.spec.js, etc. Jest/Bun-compatible API:

describe("math", () => {
  test("addition", () => {
    expect(1 + 2).toBe(3);
  });
});

Mocking is supported via jest.fn() and jest.spyOn():

test("mock", () => {
  const fn = jest.fn().mockReturnValue(42);
  expect(fn()).toBe(42);
  expect(fn).toHaveBeenCalledTimes(1);
});

Compile to Standalone Binary

ramune compile server.ts -o myserver --http --minify
./myserver    # self-contained binary with embedded JS

The compiled binary embeds the bundled JS via go:embed. On macOS, it is automatically codesigned with the JIT entitlement.

Options: --http (Ramune.serve event loop), --minify (esbuild minification). Output binary is ~21MB (linter/formatter/checker are not included — only the runtime).

Note: The compiled binary loads JavaScriptCore dynamically at runtime. The target machine must have JSC available (macOS: built-in, Linux: libjavascriptcoregtk).

Type Checking

ramune check app.ts              # check files
ramune check src/                # check directory
ramune run --check app.ts        # check then run

Uses typescript-go (TypeScript 7.0-dev, backward-compatible with TS 5.x) built into Ramune — no external tools required.

Format & Lint

ramune fmt .                     # format all JS/TS files
ramune fmt --check .             # check formatting (CI)
ramune lint .                    # lint all JS/TS files
ramune lint --fix .              # lint with auto-fix

The formatter uses typescript-go's built-in formatter. The linter uses rslint (Go-based, 20-40x faster than ESLint). Both are built into Ramune — no external tools required.

If rslint.json or rslint.jsonc exists, ramune lint uses that configuration. Otherwise, all recommended rules are enabled by default.

Note: TypeScript transpilation (ramune run app.ts) uses esbuild which is also built into Ramune.

Package Manager

ramune init                      # create package.json
ramune add lodash dayjs          # add dependencies
ramune remove lodash             # remove
ramune install                   # install all

Build (esbuild)

ramune build app.ts --outdir=dist --bundle --minify

Permissions (Sandbox)

ramune run app.ts                              # default: all allowed
ramune run --sandbox app.ts                    # deny all
ramune run --sandbox --allow-read=/tmp app.ts  # selective access

Flags: --allow-read, --allow-write, --allow-net, --allow-env, --allow-run.

Environment Variables

.env and .env.local files are automatically loaded (like Bun/Deno). Use --env-file to specify a custom file:

ramune run --env-file .env.production app.ts

Package.json Scripts

Run scripts defined in package.json:

ramune run dev     # runs "scripts.dev" from package.json
ramune run build   # runs "scripts.build"

Embed in Go

Ramune is also a Go library. Embed JavaScript in your Go application and expose any Go library to JS — database drivers, image processing, gRPC clients, ML inference, etc.

package main

import (
    "fmt"
    "log"

    "github.com/i2y/ramune"
)

func main() {
    rt, err := ramune.New()
    if err != nil {
        log.Fatal(err)
    }
    defer rt.Close()

    val, _ := rt.Eval("1 + 2")
    defer val.Close()
    fmt.Println(val.Float64()) // 3
}

Call Go from JavaScript

rt.RegisterFunc("greet", func(args []any) (any, error) {
    return fmt.Sprintf("Hello, %s!", args[0]), nil
})

val, _ := rt.Eval(`greet("World")`) // "Hello, World!"

Go functions registered via RegisterFunc can safely access Value methods (Attr, Call, SetAttr, etc.) — no deadlock. For typed callbacks, use Register with generics:

ramune.Register(rt, "add", func(a, b float64) float64 {
    return a + b
})

Struct Binding

Expose Go structs to JavaScript:

type User struct {
    Name string `js:"name"`
    Age  int    `js:"age"`
}
func (u *User) Greet() string { return "Hello, " + u.Name }

rt.Bind("user", &User{Name: "Alice", Age: 30})
// JS: user.name → "Alice", user.greet() → "Hello, Alice"

Plugin System

Register custom modules available via require():

rt, _ := ramune.New(ramune.NodeCompat(), ramune.WithModule(ramune.Module{
    Name: "mydb",
    Exports: map[string]ramune.GoFunc{
        "query": func(args []any) (any, error) {
            return db.Query(args[0].(string))
        },
    },
}))
// JS: const db = require('mydb'); db.query("SELECT 1")

Use npm Packages

rt, _ := ramune.New(
    ramune.NodeCompat(),
    ramune.Dependencies("lodash@4"),
)
val, _ := rt.Eval(`lodash.chunk([1,2,3,4,5,6], 2)`)

Async / Promises

val, _ := rt.EvalAsync(`
    new Promise(resolve => setTimeout(() => resolve(42), 100))
`)

HTTP Server

rt, _ := ramune.New(ramune.NodeCompat())

rt.Exec(`
    Ramune.serve({
        port: 3000,
        fetch(req) {
            return new Response("Hello!");
        }
    })
`)
rt.RunEventLoop()

Works with Hono and other frameworks. Async handlers with setTimeout/await are supported:

app.get('/slow', async (c) => {
    await new Promise(r => setTimeout(r, 100));
    return c.json({ ok: true });
});

Multi-core Parallelism

Unlike Bun/Node (single-threaded), Ramune runs multiple JSC VMs in parallel on separate OS threads:

pool, _ := ramune.NewPool(4, ramune.NodeCompat())
defer pool.Close()

pool.Eval("computeHeavy()")                     // round-robin dispatch
pool.Broadcast("globalThis.config = {debug: true}")  // run on all

// Multi-worker HTTP server
pool.ListenAndServe(":3000", `
    globalThis.__poolHandle = function(req) {
        return { status: 200, body: "Hello from worker!" };
    };
`)

With CPU-heavy handlers, 3 workers achieve 2.7x throughput vs single-threaded Bun.

Worker threads are also supported:

const { Worker } = require('worker_threads');
const w = new Worker('./worker.js', { workerData: { n: 42 } });
w.on('message', msg => console.log(msg));

Ramune APIs & Bun Compatibility

Ramune provides its own API namespace. Bun.* is available as an alias for backward compatibility with existing Bun code, though compatibility is partial and will be improved over time.

API Status
Ramune.serve({port, fetch, websocket}) Supported (Go net/http backend, 101K req/s)
Ramune.file(path) Supported (text, json, exists, size)
Ramune.write(path, data) Supported
Ramune.password.hash/verify Supported (bcrypt)
Ramune.sleep(ms) Supported
Ramune.plugin({setup}) Supported (onLoad filters, virtual modules)
Request / Response Polyfilled with ReadableStream body
bun:sqlite Supported (pure Go, modernc.org/sqlite)
Bun.* Alias for Ramune.* (partial Bun compatibility)

GC Configuration

Go's garbage collector can interfere with JavaScriptCore under high load. Ramune provides tunable GC settings:

rt, _ := ramune.New(ramune.NodeCompat(), ramune.WithGC(ramune.GCConfig{
    DisableAutoGC: true,   // disable Go's auto GC during HTTP serving
    GCInterval:    2000,   // manual GC every N requests
    GCPercent:     100,    // Go GC target %
}))

For most use cases (CLI, scripting, SDK), defaults work fine. Tuning is only needed for high-throughput HTTP servers.

Permissions (Library API)

rt, _ := ramune.New(
    ramune.NodeCompat(),
    ramune.WithPermissions(&ramune.Permissions{
        Read:      ramune.PermGranted,
        ReadPaths: []string{"/tmp", "/var/data"},
        Write:     ramune.PermDenied,
        Net:       ramune.PermDenied,
    }),
)

Performance

Benchmarks on Apple M4 Max (macOS, JIT enabled):

Test Ramune Bun Node.js
Hello World startup 14.2ms 7.1ms 18.0ms
Fibonacci(35) 46.2ms 40.0ms 64.7ms
JSON 10K objects 17.6ms 9.7ms 22.9ms
Crypto SHA256 x1000 19.8ms 11.0ms 20.4ms
File I/O x100 20.7ms 13.3ms 24.2ms
HTTP req/s (single) 101K 156K 112K

Multi-Runtime Pool

Ramune runs multiple JSC VMs in parallel on separate OS threads (Bun/Node are single-threaded):

Workers req/s Scaling
1 44K 1.0x
2 65K 1.48x
3 68K 1.56x

Measured with a JSON generate/filter/map handler (200 objects per request).

vs Go JS Runtimes

Test Ramune (JSC+JIT) goja otto
Fibonacci(35) 31ms 1,964ms (64x slower) 26,203ms (852x slower)
JSON 10K objects 0.9ms 11ms (13x slower) 27ms (31x slower)

Ramune uses Apple's JavaScriptCore with JIT compilation. goja and otto are pure Go interpreters.

Run make bench to reproduce.

JIT Setup

On macOS, JIT requires a code signing entitlement:

# After go install:
ramune setup-jit

# Or when building from source:
make build-cli

Linux does not need JIT setup.

Node.js Compatibility

Module Coverage Module Coverage
path 100% zlib 75% (gzip, deflate, brotli)
fs 90% (async + sync + watch) os 85%
child_process 80% events 85%
crypto 85% (+ crypto.subtle) url 80%
stream 70% Buffer 60%
http/https 70% assert 80%
net/tls 60% dns basic
worker_threads 70% readline 70%
vm 70% querystring 80%
timers/promises 70% perf_hooks basic
util 80% (types, promisify, format) process 85% (signals, exit, env)

Web Platform APIs

API Status
fetch Supported (Go net/http backend)
ReadableStream / WritableStream / TransformStream Supported (pipeTo, pipeThrough, tee, async iterator)
crypto.subtle Supported (digest, sign/verify, encrypt/decrypt, importKey/exportKey, deriveBits/deriveKey)
crypto.getRandomValues / randomUUID Supported
Blob / File Supported
FormData Supported
Headers / Request / Response Supported (ReadableStream body)
TextEncoder / TextDecoder Supported (UTF-8)
AbortController / AbortSignal Supported
URL / URLSearchParams Supported
WebSocket Supported (server-side via Ramune.serve)
performance.now / mark / measure Supported
structuredClone Supported (circular refs, Map, Set, Date, RegExp, TypedArray)
setTimeout / setInterval Supported
navigator Supported (userAgent, platform, hardwareConcurrency)
console.time / table / trace Supported

Ramune also supports package.json "exports" field resolution (conditional exports with require/import/default and subpath exports).

Known Limitations

  • N-API / Native addons: Not supported. Packages that require .node native binaries (e.g., bcrypt, sharp, better-sqlite3) will not work. Use pure JS alternatives instead.
  • HTTP self-fetch: Ramune.serve() handlers cannot fetch their own server (same JSC context deadlock).
  • Windows: No JavaScriptCore available.
  • Linux multi-runtime: Architecture-dependent signal handling. On arm64, CGO_ENABLED=1 and gcc are required for multi-runtime (cgo's signal forwarding is needed for JSC's GC). On x86_64, multi-runtime works without cgo (CGO_ENABLED=0).
  • Multi-worker limit: 2-3 workers recommended for sustained high-throughput; 4+ may trigger JSC JIT contention.

Requirements

Dependency Required Purpose
Go 1.26+ Yes Build and install
macOS or Linux Yes macOS: JSC built-in. Linux: apt install libjavascriptcoregtk-4.1-dev

All tools are built in — no external dependencies needed for check, fmt, lint, or TypeScript transpilation. npm packages are fetched directly from the npm registry — no npm or bun CLI required.

Development

make ci          # fmt + build + vet + test
make build-cli   # build with JIT entitlement (macOS)
make bench       # benchmark vs Bun/Node
make sync        # sync typescript-go & rslint from submodules

License

MIT

Third-Party Licenses

Ramune includes code from the following projects:

Project License Usage Inclusion
microsoft/typescript-go Apache-2.0 Type checker, formatter (TS 7.0-dev) Source copy (internal/tsgo/)
web-infra-dev/rslint MIT Linter Source copy (internal/rslint/)
evanw/esbuild MIT TypeScript transpilation, bundling Go module dependency

License texts for source-copied projects are in internal/tsgo/LICENSE and internal/rslint/LICENSE.

The Ramune logo includes the Go Gopher, originally designed by Renée French, licensed under Creative Commons Attribution 4.0.

About

A JavaScript/TypeScript runtime for Go — powered by JavaScriptCore, no Cgo required

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages