跪拜 Guibai
← Back to the summary

moejs: Pure Go JavaScript Runtime Beats C QuickJS by 35%

moejs is a JavaScript runtime written in pure Go, released 0.1.0 on November 9, 2026, by Calcium-Ion, the maintainer of new-api.

The problem it solves is small and specific: let Go programs run many JavaScript plugins, each request having an independent sandbox.

The host is new-api, an AI gateway, with plugins written in JavaScript; each request passes through several hooks.

The repo was created on September 24, and now has 532 stars.

Its selling points are threefold: pure Go, 35% faster than the C version of QuickJS, and specialized for plugin sandboxing.

Image

Go programs have multiple categories of JS engines.

goja and Sobek are pure Go, easy to compile and cross-compile, but average performance.

modernc.org/quickjs is a Go translation of QuickJS, also pure Go, but memory-hungry.

quickjs-go and v8go use cgo, good performance, but require a C toolchain, complicating static builds and cross-compilation.

moejs aims to get the convenience of the former and the speed of the latter.

Official numbers on the V8 benchmark suite v7: eight classic small programs score 1429, while C-based QuickJS scores 1058 on the same machine, making it 1.35 times faster.

In all eight programs, moejs scores highest.

Compared to its own previous version, the same tests dropped from 41.55 seconds to 14.29 seconds.

Image

The plugin scenario is more illustrative; the cost is primarily in spinning up a runtime per request.

According to new-api's ten task plugins test, an Agent request with 100,000 tokens under 8 concurrency processes 2.9 times as many requests per second as C-based QuickJS.

Requests with an 8 MiB image are 8.6 times faster.

On small requests, C-based QuickJS still wins, by nearly 7%.

A single plugin call takes 6.9 microseconds, Sobek 14.3, quickjs-go 104.5.

Creating a runtime takes 1.4 microseconds, Sobek 2.2, quickjs-go 382.

With the largest plugin loaded, a runtime occupies 81 KiB, Sobek 264 KiB, quickjs-go 348 KiB.

Image

Embedding in a Go program looks roughly like this.

mod, err := moejs.Compile("task", source)

rt := moejs.NewRuntime(moejs.Options{})
rt.SetGlobal("utils", map[string]any{"env": moejs.NativeFunc(env)})
rt.Load(mod)

arg, err := rt.ParseJSONString(body)
res, err := rt.Call("buildRequest", arg)

var out Request
err = rt.Unmarshal(res, &out)

Compile once, take runtime from a pool, put back after use.

ParseJSONString does not copy input; strings in arguments point directly to the body block of memory, which is one reason it's fast on large requests.

What plugins can see is precomputed.

Only the JS standard library plus globals installed by the host.

Every import and import() must go through a Go function provided by the host; if the host didn't install it, it's not available.

eval and new Function can be length-limited or disabled entirely.

Built-in objects are frozen; all runtimes share the same Array.prototype, so one plugin can't modify another's arrays.

Running node:fs is also possible; the host gives a directory, all paths resolve inside os.Root, and .. and symlinks can't escape.

Image

For consistency, test262 ran 80,971 cases, zero failures.

Another 14,268 cases were skipped because moejs doesn't implement those features, such as Temporal, decorators, iterator helpers, and part of Intl.

TypeScript runs directly; CompileTS only strips types, doesn't change semantics, errors and stack traces point back to TS source.

It's still alpha; APIs will change between versions.

The bare runtime has no setTimeout; you need to install the eventloop package.

fetch requires the host to supply an http.Client; without it there's no network.

It has only one dependency, moego, for SIMD-accelerated base64; the engine itself remains pure Go and builds with CGO_ENABLED=0.

If you've written plugin systems in Go and been bothered by cgo or engine performance, this project is worth a look.

Repo: https://github.com/MoeForge/moejs

Do you care more about cross-compilation convenience or per-request memory overhead? Feel free to comment.


About me: