Introduction
Perry is a native TypeScript compiler that compiles TypeScript source code directly to native executables. No JavaScript runtime, no JIT warmup, no V8 — your TypeScript compiles to a real binary.
// demonstrates: the one-liner hello shown on the docs landing page
// docs: docs/src/introduction.md
// platforms: macos, linux, windows
// targets: wasm, web, android
console.log("Hello from Perry!")
$ perry hello.ts -o hello
$ ./hello
Hello from Perry!
Why Perry?
- Native performance — Compiles to machine code via LLVM. Integer-heavy code like Fibonacci runs 2x faster than Node.js.
- Real multi-threading —
parallelMapandspawngive you actual OS threads with compile-time safety. No isolates, no message passing overhead. Something no JS runtime can do. - Small binaries — A hello world is ~300KB. Perry detects what runtime features you use and only links what’s needed.
- Native UI — Build desktop and mobile apps with declarative TypeScript that compiles to real AppKit, UIKit, GTK4, Win32, or DOM widgets.
- Terminal UI — Build interactive CLIs with ink-shape React hooks (
useState,useEffect,useApp) on a native cell-grid renderer. No Node, no reconciler — just a single static binary. - 7 targets — macOS, iOS, Android, Windows, Linux, Web, and WebAssembly from the same source code.
- Familiar ecosystem — Use npm packages like
fastify,mysql2,redis,bcrypt,lodash, and more — compiled natively. - Node.js compatibility — ~95% behavioral parity with Node, including real (non-stub) implementations of
fs,http/https/http2,net/tls,crypto,stream,events,child_process,worker_threads,process, and the WHATWG web globals. Against Node’s own test suite (node v26), Perry passes ~97% of cases. - Zero config — Point Perry at a
.ts(or.js) file and get a binary. Notsconfig.jsonrequired.
What Perry Compiles
Perry supports a practical subset of TypeScript:
- Variables, functions, classes, enums, interfaces
- Async/await, closures, generators
- Destructuring, spread, template literals
- Arrays, Maps, Sets, typed arrays
- Regular expressions, JSON, Promises
- Module imports/exports
- Generic type erasure
Perry also compiles plain JavaScript — .js, .cjs, .mjs, and .jsx
source files are parsed as JavaScript and lowered through the same native
pipeline, so no TypeScript annotations are required. Dynamic JS patterns aren’t
all guaranteed, but most JavaScript projects compile and run.
See Supported Features for the complete list.
Quick Example: Native App
// demonstrates: minimal stateful UI — label + increment button
// docs: docs/src/ui/state.md
// platforms: macos, linux, windows
// targets: ios-simulator, visionos-simulator, tvos-simulator, watchos-simulator, android, web, wasm
import { App, VStack, Text, Button, State } from "perry/ui"
const count = State(0)
App({
title: "Counter",
width: 400,
height: 300,
body: VStack(16, [
Text(`Count: ${count.value}`),
Button("Increment", () => count.set(count.value + 1)),
]),
})
$ perry counter.ts -o counter
$ ./counter # Opens a native macOS/Windows/Linux window
This produces a ~3MB native app with real platform widgets — no Electron, no WebView.
How It Works
TypeScript (.ts)
↓ Parse (SWC)
↓ Lower to HIR
↓ Transform (inline, closure conversion, async)
↓ Codegen (LLVM)
↓ Link (system linker)
↓
Native Executable
Perry uses SWC for TypeScript parsing and LLVM for native code generation. Types are erased at compile time (like tsc), and values are represented at runtime using NaN-boxing for efficient 64-bit tagged values.