# zig-js: a pre-stabilization JS engine with JavaScriptCore shaped exports

> A JavaScript engine written in Zig whose static library presents JavaScriptCore-shaped C headers and symbols, with test262 and WebAssembly conformance tracked in the README down to exact sample counts. The benchmark tables are the honest part, including where JavaScriptCore still wins.

**zig-utils/zig-js** — A JavaScript engine in pure Zig.

- Repository: https://github.com/zig-utils/zig-js
- Stars: 31 · Forks: 0
- Language: Zig
- License: MIT
- Published: 2026-08-21 · Updated: 2026-08-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/zig-utils-zig-js

## JSC shaped exports, with Objective-C glue on macOS

The engine and its importable `js` module are written in Zig, and the shape of the deliverable is chosen for drop-in familiarity: the static library exports JavaScriptCore-shaped C headers and symbols. On macOS that means bridge glue in `src/objc_bridge.m`. Notably, the package does not bundle JavaScriptCore or V8. It depends on `zig-regex` and `zig-gc` instead, and a system JavaScriptCore is used only by explicit differential and benchmark targets. The APIs are described as pre-stabilization, and the smallest complete program looks like this:

```zig
const js = @import("js");

const ctx = try js.Context.create(allocator);
defer ctx.destroy();

const value = try ctx.evaluate("let x = 40; x + 2");
```

A context is created, evaluated and destroyed, with `defer` doing the cleanup. Because the library mimics JavaScriptCore at the boundary, existing embedding knowledge transfers, which is the argument the shape makes even though the README does not spell out a migration path.

## Conformance rows carry the evidence file names

The status table is unusual in that each row links to the artifact behind it rather than asking to be trusted. The configured test262 suite reports 53,175 of 53,175 passing. The WebAssembly matrix reports 151,802 of 151,802 applicable tests across ten profiles, with Core 3 accounted for at 63,964 of 63,964. A third row covers a pinned private ABI against two embedders, Home at 447 of 447 and Bun at 437 of 437. Underneath every figure sits a path into the documentation data directory, a run log for test262, a conformance matrix, a shadow inventory taken against upstream main, and an ABI folder described as holding inventories, a provider audit, a lifecycle description and exact reproduction steps. Nothing here is a summary without a way back to the raw record.

## Ten wins out of ten, with the harness arguing the details

The comparison table against JavaScriptCore is guarded by stated method: equal-work checks, alternating order, dispersion limits and a 50 ms timing floor are enforced by the harness, and the accepted report is backed by 1,540 raw samples. On one warmed lane the engine wins all ten comparisons at 2.32x throughput. At eight independent steady contexts it wins ten of ten at 2.53x, with scaling of 5.35x against 4.96x for JSC. Cold lifecycles come out at 2.52x with 5.67x against 4.97x. The shared realm row with no GIL reaches 4.84x scaling but has no public JSC equivalent to compare against, since JSC offers no public shared-realm embedding path. Ratios above 1.00x favor zig-js.

## WebAssembly scales hard and starts slow

The WebAssembly numbers split cleanly in two, and both halves matter. Scaling is the strong side, with SIMD reaching 3.67 to 4.58 times across eight lanes. Threads report 17.23 million contended adds per second and 287,444 wait and notify handoffs per second at eight workers. Against JavaScriptCore the throughput figures are the opposite, landing at 0.10 to 0.14 times JSC for the SIMD runs. In other words the engine parallelizes well and compiles slowly, and a reader who only looks at the scaling column will draw the wrong conclusion about which workload suits it. Both WebAssembly rows are dated and link to a report plus a sample file, so the two halves of that verdict can be checked separately rather than taken as one average.

## Collector rows are ratios, and one of them counts timeouts

The garbage collection section is phrased as distance from a baseline rather than as an absolute number, which is the honest way to present a collector. The moving age-three generation runs at 0.63 to 1.01 times its exact non-moving parents and 0.84 to 1.02 times moving age-one across accepted single-mutator rows, with 480.42 MiB copied in the recorded moving age-three rows. The shared no-GIL minor pause maximum is 110.56 ms, and that row carries twelve timeouts. Explicit compaction is the cleaner result: retained fragmented backing drops 90.8 percent, from 8.81 to 0.81 MiB, at a 0.99 ms median pause with unchanged post-action throughput.

## The JSON rows say where JavaScriptCore still wins

One diagnostic is unusually candid. The debug pass on Apple M2 Pro with Zig 0.17.0-dev.1770, in default arena mode with the collector off, shows 51.7 percent fewer retired instructions for long strings with a sparse escape, against a control of minus 0.08 percent, and 27.5 percent fewer for arrays of short integers with a fractional control of plus 0.41 percent. A later production pass switches to ReleaseFast with the real collector and reports 6.74 and 6.72 percent fewer instructions on string-length rows, with a guarded bit-and removing another 0.96 and 0.53 percent while controls stay within plus or minus 0.11 percent. Then the same document reports the system engine as 4.05 and 4.04 times faster on the frozen base and variant, with an explicit statement that no parity claim is made.

## The docs site builds from paths outside the repository

The engine repository also contains a documentation site, and its build scripts assume a machine layout that a fresh clone does not have. The package is named `zig-js-docs`, private, and its scripts all invoke a bunpress command through a relative path that climbs out of the repository to `../../Tools/bunpress`, with the dev dependency declared the same way through a `file:` reference. The data regeneration script goes further and calls a binary at an absolute path under a personal home directory. So the documentation tooling works for the author and its collaborators and will not resolve for anyone who clones the project, while the engine itself needs only `zig build`, which installs `libzig-js.a` and matching headers under `zig-out/`.

## Conclusion

Read zig-js if you want an engine whose conformance and performance claims come with the raw sample files attached, and treat the version as pre-stabilization while the published API is called pre-stabilization. Do not plan around the JSC throughput rows for JavaScript parsing, since the project's own diagnostics put the system engine well ahead there.

## FAQ

### What is Zig used for?

In this project Zig is the implementation language and the build tool. The engine and its importable js module are written in Zig, and zig build installs libzig-js.a with compatible headers under zig-out/.

### What companies use Zig?

The repository does not name any company. It attributes its API shape to JavaScriptCore as the thing being emulated, and it reports ABI compatibility results for two embedders, Home at 447 of 447 and Bun at 437 of 437.

### Why use Zig instead of C?

That argument is not made here. What the project states instead is that the static library exports JavaScriptCore-shaped C headers and symbols, so C-shaped consumers keep the interface they expect while the implementation is Zig, with Objective-C bridge glue in src/objc_bridge.m.

## Sources

- [Official README](https://github.com/zig-utils/zig-js#readme)
- [Project repository](https://github.com/zig-utils/zig-js)
- [Release notes](https://github.com/zig-utils/zig-js/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/zig-utils-zig-js
