Repository navigation
ZIPP v0.0.31
ZIPP 0.0.31
A release for TypeScript, Node programs and WebAssembly. zipp app.ts runs
TypeScript and TSX directly, with no transpile step; zipp app.js runs a
program the way node does, with Node's globals, npm packages and the core
modules most packages reach for; and WebAssembly is there, compiled to
machine code by Wasmtime in the 64-bit command lines. The JIT is faster on
class-heavy code and on doubles.
- TypeScript and TSX run as they are. The JavaScript parser reads a
.ts,.mts,.ctsor.tsxfile with its types and drops them;
there is no transpile step and no JavaScript text, and the compiler, the
interpreter and the JIT run it as they run JavaScript. Constructs with a
run-time meaning are lowered as tsc emits them:enum(constant-folded
and reverse-mapped; aconst enuminlined where read),namespace,
constructor parameter properties,import x = …andexport = ….
Imports used only as types are elided,tsconfig.jsondecides class
fields (useDefineForClassFields) and legacy decorators
(experimentalDecorators, withemitDecoratorMetadata), and modules
resolve as TypeScript names them. JSX (.tsx, and.jsxwithout types)
is lowered to the calls tsc emits for the project'sjsxsetting, React's
automatic runtime by default. A.tsfile runs as a script unless it
does not parse as one. - Node programs.
zipp app.js,app.mjs,app.tsandapp.tsxrun
withprocess,Buffer,URLandURLSearchParams, timers,
EventTarget,AbortController,structuredClone, CommonJS (require,
module,exports,__filename,__dirname), npm packages resolved
fromnode_modulesbyexports,mainand Node's probing, and the core
modulespath,events,util,fs(synchronous, callback and promise
forms, and streams),os,tty,url,module,assert,buffer,
crypto(hashes and HMAC for md5, sha1 and sha256; not a cryptographic
random source),string_decoder,timers,streamandreadline.
Output is written as the program prints. docs/NODE.md lists what is and
is not there;ZIPP_NO_NODE=1gives the plain ECMAScript global object. - WebAssembly.
WebAssembly.validate,compile,instantiateand
their streaming forms,Module(withimports,exportsand
customSections),Instance,Memory,TableandGlobal, with
i64as BigInt andexternrefas the JavaScript value itself. Memory is
shared, not copied: a memory'sbufferis over its own bytes. In the
Windows x86-64, Linux x86-64 and Linux arm64 command lines Wasmtime
compiles modules to machine code while the JIT is on; the wasmi
interpreter runs them with the JIT off (ZIPP_NOJIT=1,
ZIPP_WASM_INTERP=1for WebAssembly alone) and in the armv7 build.
A loop of 200 million WebAssembly iterations takes 36 ms on Wasmtime and
289 ms on wasmi (Node 24: 73 ms). - WebAssembly is held to the limits a script runs under. Under a step
budget or a heap limit (zipp sandbox's, or an embedder's
set_limits/set_heap_limit), every WebAssembly instruction is charged
to the budget, on either runtime, and every memory and table counts
against the heap limit before it is allocated. A host's abort request
stops WebAssembly within milliseconds: Wasmtime checks at each 5 ms
epoch, wasmi between slices of its fuel (except inside a module's start
function, which wasmi cannot pause; there the budget bounds it).
zipp sandbox's child runs WebAssembly on wasmi, as it runs JavaScript
with the JIT off. - A faster JIT. Among the changes:
- a class method called on an instance (
a.dot(b)) is inlined at the
call site behind the class's guard,thisbound to the receiver, and
its property reads take the shape its own call site saw; - a class method's or getter's call from compiled code is a native call
through a per-site lookup, and an own function property's call too; - arithmetic that runs on doubles compiles to double operations kept in
registers,Math.sqrt,abs,floor,ceilandtruncare one
instruction each, and an Int product stays an Int; - a property read or written at the shape its site last saw is a direct
load or store, and a verified receiver's values stay at hand through a
straight run of code; - a loop that no compiled region serves enters the compiled function at
its head, andtry/finallyjumps compile; JSON.parsebuilds objects from shared key plans,JSON.stringify
decides a shape's keys once, and short decimals print without the
general formatter;/re/.test(x)makes no object, two different string literals compare
unequal without reading their bytes, and an object literal's spread of
a literal defines its properties in place.
- a class method called on an instance (
- Profile-guided command lines. The Windows x86-64, Linux x86-64 and
Linux arm64 command lines are built with profile-guided optimization for
the first time (7ee15ed3): on each runner, an instrumented build of the
release feature set runs the canonical training corpus, and the release
is built with that profile. The armv7 command line and the WebAssembly
modules stay plain builds. - A sixth WebAssembly module,
javascript-typescript, the JavaScript
engine with the TypeScript front end, beside the JavaScript-only one. The
sandbox loader (zipp.mjs1.5.0) runs a TypeScript program in it as it
runs a JavaScript one, with the same bridges, host calls andfetch;
every other engine refuses TypeScript, naming this one.
Downloads
| File | What it is |
|---|---|
zipp-0.0.31-windows-x86_64-portable.exe |
The Windows command line; run it from anywhere. |
zipp-0.0.31-windows-x86_64-installer.msi |
The same binary, installed for the current user into %LOCALAPPDATA%\Programs\ZIPP and added to PATH; no administrator rights. Uninstall from Settings > Apps. |
zipp-0.0.31-linux-x86_64 |
The Linux command line, x86-64 (glibc 2.35 or later). chmod +x it after downloading. |
zipp-0.0.31-linux-arm64 |
The same for 64-bit Arm. |
zipp-0.0.31-linux-armv7 |
The same for 32-bit Arm (hard float); interpreter only, and WebAssembly on wasmi. |
zipp-0.0.31-javascript.wasm |
The JavaScript engine for browsers, Node, Deno and Bun. |
zipp-0.0.31-javascript-typescript.wasm |
The same with TypeScript and TSX. |
zipp-0.0.31-javascript-lite.wasm |
The JavaScript core without Intl and Temporal, built for size. |
zipp-0.0.31-javascript-python.wasm |
JavaScript and Python, torch built in. |
zipp-0.0.31-javascript-python-base.wasm |
JavaScript and Python without torch. |
zipp-0.0.31-torch-package.wasm |
The torch package for the -python-base engine. |
zipp-0.0.31-<engine>.wasm.br |
Each WebAssembly module compressed with Brotli (quality 11), about a quarter of its size. Serve it with Content-Encoding: br and Content-Type: application/wasm, or let zipp.mjs load it; the plain .wasm is the default. |
zipp-0.0.31-wasm-embed-kit.zip |
zipp.mjs (the loader and sandbox, version 1.5.0) and its types, the host SDK (1.4.0), the engine's TypeScript declarations, the browser GPU adapter, docs and three examples. |
SHA256SUMS |
Checksums of every file. |
Changes in behaviour
zipp file.jsruns in Node mode (and.mjs,.ts,.tsx): the
globals above are defined. A bundle that sniffsprocessorrequireto
pick its platform now takes its Node path, as it would undernode; run
it withZIPP_NO_NODE=1for the plain ECMAScript environment. A program
that uses none of the layer pays about half a millisecond at startup.WebAssemblyis a global in the command line and native embeddings
(the browser modules and the hardenedzipp-sandboxbuild have none).- A script under a step budget or heap limit that made WebAssembly
before the budget was set cannot run that WebAssembly under it: its
store counts no steps, so a call fails with aRangeError.
Fixes
- A
finallyin an array builtin's callback was skipped under the JIT:
[1, 2, 3].map((x) => { try { return x * 2; } finally { n++; } })left
nat 0 in 0.0.30. A builtin now runs a callback withtry,catchor
finallyon its own frame.
Dependencies
- wasmi 2.0.0 (with wasmi_core 2.0.0) runs WebAssembly in every native
build; Wasmtime 36.0.19, its long-term support line, with Cranelift, and
wasmparser 0.236 for custom sections, compile it in the 64-bit command
lines. Wasmtime had been at 44.0.3 since the WebAssembly work began on
the development branch, a version with eight RustSec advisories (two
critical: a component-model stack buffer overflow, and a wrongbr_table
jump target on arm64); 36.0.19 has the fixes for all eight.
Performance
Measured on 29 October 2026 on an otherwise idle machine, native Windows
x86-64 (Ryzen 9 9950X3D):
- the published 0.0.30 and 0.0.31 Windows binaries;
- a profile-guided build of the 0.0.31 source by the canonical recipe
(tools/pgo.sh, JavaScript only), which the release does not ship; - Bellard's QuickJS 2026-06-04 and Node 24.19.0.
Every ZIPP binary runs with address-space randomization turned off, so
image placement does not move the numbers. Each run is pinned to one core
of the processor's die with the 96 MB L3 cache unless stated otherwise.
The figures are medians of interleaved runs.
Bundled applications. Each library is bundled into one script that runs
a fixed workload. Each figure is the median of 7 runs of zipp js app.js,
and the time includes compiling the bundle. 0.0.31 runs them with
ZIPP_NO_NODE=1, as plain ECMAScript, which is what they were bundled
for: in Node mode the TypeScript bundle takes its Node path, which its
bundled os module cannot serve, and fails as it does under Node. The
0.0.30 and 0.0.31 columns have the code cache off, as on a first run. The
warm column has the cache filled.
| Program | 0.0.30 | 0.0.31 | 0.0.31, cache warm | Bellard's QuickJS | Node 24 |
|---|---|---|---|---|---|
Babel: @babel/standalone transforms about 20 KB of modern JSX twice |
3.50 s | 3.05 s | 2.93 s | 3.68 s | 0.77 s |
TypeScript: transpileModule on about 40 KB of classes, generics and enums |
0.93 s | 0.88 s | 0.68 s | 1.05 s | fails: its bundled os is a stub |
| Prettier: formats JavaScript, TypeScript and Markdown | 0.46 s | 0.41 s | 341 ms | 0.44 s | 358 ms |
| marked, lodash, acorn, terser, three.js, immer, RxJS and date-fns | 0.71 s | 0.66 s | 0.61 s | 1.91 s | 347 ms |
| Vue server rendering | 85 ms | 79 ms | 67 ms | 85 ms | 73 ms |
| Preact server rendering | 75 ms | 62 ms | 59 ms | 73 ms | 63 ms |
| React server rendering | 69 ms | 61 ms | 56 ms | fails | 63 ms |
On the die with the 32 MB L3 cache, 0.0.31 takes 0.83x to 0.97x
0.0.30's time, and 0.87x to 0.98x QuickJS's on Babel, TypeScript,
Prettier, Vue and Preact (0.83x to 0.93x on the larger-cache die).
The profile-guided build takes 0.83x to 0.91x QuickJS's time on those
five on the larger-cache die and 0.87x to 0.97x on the other.
Node 24, whose optimizing JIT compiles these long-running bundles' hot code,
is faster than 0.0.31 on Babel (3.94x), the eight libraries (1.91x), Prettier (1.14x) and Vue server rendering (1.08x), given as 0.0.31's time over
Node's; 0.0.31 is ahead on React server rendering (0.96x) and about even on Preact server rendering (0.99x).
0.0.31 is the first release whose command lines are built with profile-guided
optimization (see the list above), and on the bundles that is most of its lead
over 0.0.30. A plain release build of the same engine source, without the
profile, takes 1.07x to 1.16x the published binary's time on the bundles, and
over the 78 programs 1.055x interpreted and 1.066x with the JIT (same machine and
die, runs of their own). Taken with the comparison above, the engine changes
alone come to about 0.96x to 1.01x 0.0.30's time on the bundles, which is to say
about even, and about 0.96x interpreted and 0.95x with the JIT over the 78
programs. TypeScript, Node programs and WebAssembly are new, not faster.
Peak working set and page faults, cache off (mean of 3 runs):
| Program | 0.0.30 | 0.0.31 | Bellard's QuickJS |
|---|---|---|---|
| TypeScript | 225 MiB, 57,853 faults | 234 MiB, 60,180 | 114 MiB, 29,986 |
| Babel | 245 MiB, 64,590 | 251 MiB, 67,715 | 96 MiB, 25,433 |
| The eight libraries | 209 MiB, 53,669 | 212 MiB, 54,350 | 32 MiB, 9,433 |
| Prettier | 126 MiB, 32,512 | 131 MiB, 33,755 | 38 MiB, 11,164 |
| Vue server rendering | 37 MiB, 9,672 | 43 MiB, 10,998 | 20 MiB, 5,248 |
| Preact server rendering | 32 MiB, 8,484 | 34 MiB, 8,990 | 14 MiB, 3,786 |
0.0.31 holds 2 to 9 MiB more than 0.0.30 on these programs. A one-line
program's peak is unchanged (7.5 MiB, 7.6 with ZIPP_NO_NODE=1; 9.4 MiB in
Node mode, which sets up Node's globals), so the difference is in the work,
not the larger binary.
The 78 benchmark programs (median of 3 runs each; every output
identical):
- Interpreted (
ZIPP_NOJIT=1, which leaves the regex JIT on, as in
0.0.30's notes): 0.912x 0.0.30's time, faster on 59 of 78. The largest
gains arenew_class(0.58x),new_obj(0.61x),accessors-proxy(0.74x),warm-router(0.77x),async-promise-chain(0.79x); the largest losses (medians of 3 runs, not rerun) are
dv_get(1.15x),f64_write(1.12x),i32_rw(1.09x),dv_get_glob(1.08x). - With the JIT: 0.892x 0.0.30's time, faster on 68. The largest gains are
new_class(0.51x),new_obj(0.55x),call_method(0.56x),finally-labels(0.67x),polymorphic-objects(0.71x). - Against Bellard's QuickJS in the same runs, in that lane: the
interpreter takes 0.742x QuickJS's time and is faster on 62 of the 78
programs. 0.0.30 took 0.815x and was faster on 54. The largest gaps left
arearguments_len(1.63x),f64_write(1.43x),upval_rw(1.32x),proto_read(1.30x),i32_rw(1.26x). - Against Node 24: with the JIT, 0.0.31 takes 0.857x Node's time and is
faster on 48 of the 78 (0.0.30: 0.961x, 43); interpreted, against
node --jitless, 1.071x (0.0.30: 1.175x).
The TypeScript benchmarks (bench/typescript, five programs). Run as
TypeScript, zipp file.ts against Node 24 (best of 5): collections
1.89x Node's time, the expression compiler 2.74x, n-body 0.84x,
the raytracer 1.79x and records 1.11x. As the same programs in plain
JavaScript, since 0.0.30 cannot read TypeScript: interpreted, 0.0.31 takes
0.669x 0.0.30's time and 0.675x QuickJS's; with the JIT, 0.313x 0.0.30's
time and 1.425x Node's (0.0.30: 4.556x).
WebAssembly. A loop of 200 million iterations in one exported function
takes 36 ms on Wasmtime (the JIT on), 289 ms on wasmi (ZIPP_WASM_INTERP=1)
and 73 ms in Node 24, each the median of 5 runs, timed inside the
program around the call alone (the module is compiled before the clock
starts).
Measured while 0.0.31 was prepared (Linux, the same release build with each
change switched on and off), a class method called on an instance takes
6.8 ns a call instead of 14.5 (the same body written out in the loop: 6.0
ns), and the raytracer benchmark 3.7% fewer cycles from that alone.
The figures in the README and
docs/PERFORMANCE.md
describe the profile-guided 0.0.31 capture of 29 October 2026;
docs/PERFORMANCE.md also lists these release measurements.
Publishing
As for earlier releases, the engines the site serves are refreshed AFTER this
release exists, in one commit that:
- sets
zippEngineReleaseinlanding/package.jsonto0.0.31; - refreshes
landing/public/playground-runtimeandlanding/public/wasmfrom
zipp-0.0.31-javascript-python.wasmandzipp-0.0.31-javascript.wasm
(node crates/zipp-wasm/embed-kit/package.mjs extract, then
sync-playground.mjs --refresh-engine), SHA256SUMS-verified; - syncs the sandbox playground (
node landing/scripts/sync-sandbox.mjswith
the release's files) and rewrites the site's Brotli copies
(npm run compress:engines); - updates the version, size and SHA-256 that
landing/src/App.tsxand
landing/README.mdstate about the bundled engine.
Full Changelog: v0.0.30...v0.0.31