Perry

Write TypeScript. Ship native. Everywhere.

Perry compiles the TypeScript you already write into real machine-code executables — for macOS, Windows, Linux, iOS, Android, watchOS, and TV. No Node.js to install. No Electron to bundle. No runtime at all. Just a binary. (The same codebase can also target the web, emitted as JavaScript or WebAssembly.)

Website · Documentation · Showcase · Examples

perry compile src/main.ts -o myapp
./myapp    # a standalone native binary — ~34 KB for hello world

The language you know. The deployment you wish it had.

Millions of developers write TypeScript every day — but shipping it has always meant shipping a JavaScript engine: a Node install on every server, ~100 MB of embedded runtime per CLI, or a whole browser engine per desktop app. Perry removes the engine. SWC parses your code, LLVM compiles it to machine code, and you get what systems languages get: instant cold starts, tiny self-contained binaries, real threads — without leaving TypeScript.

PerryNode.jsBunElectron
What you shipOne native binary, from ~34 KBYour code + a Node installOne binary embedding the JS engineApp bundle with a browser engine
ExecutionAhead-of-time machine codeJITJITJIT
Cold startInstant — no engine to boot, no warmupEngine boot + warmupEngine boot + warmupBrowser boot
Native UI (no WebView)✅ AppKit, UIKit, Android Views, Win32, GTK4——Chromium
iOS · Android · watchOS · TV✅ from the same codebase———
MulticoreReal OS threads, data-race-safe at compile timeworker_threadsworkersprocesses

Table describes Perry's native targets. --target web / --target wasm emit JavaScript / WebAssembly that runs in the browser rather than as a native binary.

Performance

Ahead-of-time machine code means no engine boot and no JIT warmup — and on measured workloads, real multiples over the JS runtimes. Highlights from our open benchmark harnesses (Apple M1 Max, medians of repeated runs):

WorkloadPerryNode.jsBunRust
Image convolution (4K, 5×5 Gaussian)354 ms1,207 ms — 3.4× slower915 ms — 2.6× slower392 ms
Fibonacci (recursive calls)309 ms987 ms — 3.2× slower518 ms — 1.7× slower316 ms
JSON pipeline (100 records)39 ms144 ms — 3.7× slower51 ms — 1.3× slower34 ms
JSON pipeline (500k records, 108 MB)1,649 ms1,010 ms — 1.6× faster647 ms — 2.5× faster604 ms
Object allocation (1M objects)2 ms8 ms — 4× slower6 ms — 3× slower<1 ms
Array write (10M elements)3 ms9 ms — 3× slower6 ms — 2× slower7 ms
Peak memory (JSON pipeline)3.5 MB36 MB — 10× more11 MB — 3× more1.2 MB

Look at the Rust column again: on convolution, fibonacci, and the array-write loop, TypeScript compiled with Perry runs even with — or ahead of — Rust. And Electron isn't in the table because it doesn't compete here: it ships a whole browser engine per app, where a comparable Perry app is a single-digit-MB native binary.

Sources: convolution & JSON from the systems-language report — pinned to 7beb3a50ca, the last revision whose artifact contains these values; the copy at HEAD was regenerated in #7641 on a host where the harness clock was invalid and now reads 0.0 ms in every timing cell (see this PR). Re-pin to main once it is regenerated. Fibonacci, object allocation & array write from the polyglot sweep (Perry default mode, no fast-math).

We publish everything, including the workloads where V8's JIT still beats us — no cherry-picked table can survive an open harness. Run it yourself: ./benchmarks/run_public_baseline.sh (methodology).

The full published suite, generated from the artifact — including the rows we lose

Generated from benchmarks/results/public-node-bun-v1.json at Perry commit b77aba63433b. Lower wall-clock median is better; every row includes complete raw samples and passed correctness checks. Medians within 5% of a peer count as a tie with it.

BenchmarkPerryNode.jsBunResultWhat it tests
modulo_loop93 ms95 ms95 mstieInteger-modulo accumulation loop (microbenchmark)
method_calls35 ms11 ms8 msloss vs bothClass method dispatch
closure47 ms49 ms49 mstieClosure creation and invocation
binary_trees13 ms6 ms6 msloss vs bothTree allocation and traversal
string_concat6 ms31 ms5 msmixedString append loop
prime_sieve6 ms5 ms5 msloss vs bothSieve of Eratosthenes
mandelbrot25 ms24 ms29 msmixedComplex-number iteration
matrix_multiply19 ms33 ms33 mswin vs bothMatrix multiplication
json_roundtrip144 ms379 ms219 mswin vs bothParse and stringify ~1 MB JSON

Why developers pick Perry

  • ⚡ Native speed, zero warmup. LLVM-optimized machine code with escape analysis, scalar replacement, and a generational GC — see the numbers above.
  • 📦 Binaries you can actually email. Hello world is ~34 KB: a program that only prints constants links no runtime at all. Once a program uses the runtime (loops over values, JSON, async), expect a few MB stripped — about 5.5 MB on macOS arm64 today. A real MongoDB GUI built with Perry ships as a ~7 MB app.
  • 🔌 Your Node code mostly just works. ~97% pass rate on Node's own test suite across 53 node:* modules — real implementations of fs, http/http2, net/tls, crypto, stream, child_process, worker_threads, fetch and the web globals, plus ~50 popular npm packages (Fastify, Express, mysql2, pg, ioredis, ws, bcrypt, jsonwebtoken…). Plain JavaScript compiles too.
  • 🖥️ One UI codebase, 11 targets. A SwiftUI-like API that compiles to real platform widgets — macOS, iOS, iPadOS, visionOS, tvOS, watchOS, Android, Wear OS, Windows, Linux, Web/WASM. Even home-screen widgets.
  • 🧵 All your cores, safely. parallelMap, parallelFilter, and spawn on real OS threads — the compiler rejects shared mutable state, so data races don't compile.
  • 🔋 Batteries included. Databases, WebSockets, containers, i18n, keychain, notifications, auto-update, a TUI framework — natively implemented and statically linked.

Get started in 60 seconds

# Install (macOS · Linux · Windows)
npm install -g @perryts/perry     # or: brew install perryts/perry/perry
                                  # or: winget install PerryTS.Perry

# Create and run a project
perry init my-app && cd my-app
perry run .

npm packages and ES modules work as you'd expect:

import fastify from 'fastify';

const app = fastify();
app.get('/api/users', async () => [{ id: 1, name: 'Alice' }]);
app.listen({ port: 3000 }, () => console.log('Listening on :3000'));
perry compile src/main.ts -o api && ./api    # one binary — no node_modules on the server

Same code, other platforms: --target ios, --target android, --target web… full list in the platforms guide. APT, Scoop, install script, and source builds are in the installation guide; run perry doctor to verify your setup.

Built with Perry

Real products, shipping today:

ProjectWhat it isPlatforms
Bloom EngineNative TypeScript game engine — Metal, DirectX 12, Vulkan, OpenGLmacOS, Windows, Linux, iOS, tvOS, Android
MangoNative MongoDB GUI — ~7 MB binary, sub-second cold startmacOS, Windows, Linux, iOS, Android
HoneAI-powered native code editor with terminal, Git, and LSPmacOS, Windows, Linux, iOS, Android, Web
dB MeterReal-time sound level measurement at 60 fpsiOS, macOS, Android

More in the showcase — built something with Perry? Open a PR and add it.

Documentation

Everything else lives in the docs:

Community

Perry is built in the open — come say hi:

  • 💬 Join the Discord — get help, share what you're building, and talk directly to the people building Perry
  • 🐛 Issues — bug reports and feature requests
  • 🚀 Showcase — apps the community is shipping

Privacy

Telemetry is opt-in: nothing leaves your machine unless you explicitly enable it in ~/.perry/config.toml, and PERRY_NO_TELEMETRY=1 (or CI=true) always wins. What can be sent is anonymous and redacted — never your source, paths, or project names. Inspect it any time with perry doctor, and see exactly what's in the payload in the privacy & telemetry docs.

Sponsors

Perry's development is backed by our sponsors. 🙏

💎 Skelpo — Premium Sponsor

Want to support Perry and see your logo here? Get in touch via perryts.com.

License

MIT