Perry: a TypeScript-to-native compiler that removes the JavaScript engine
Project brief: A native TypeScript compiler written in Rust. Compiles TypeScript directly to executables using SWC and LLVM.
At a glance
- What is it?
- Perry compiles TypeScript to machine code through SWC and LLVM, producing standalone binaries for desktop, mobile and web. It is for teams whose deployment problem is the runtime they have to ship, not the language they write.
- Who is it for?
- Adopt Perry when the thing you are trying to delete is the JavaScript engine: CLI tools, desktop apps, mobile apps, and services where cold start and memory footprint are the actual cost. Do not adopt it as a drop-in replacement for a Node service that depends on npm packages outside the roughly fifty the project lists, or on runtime behaviour its Node test suite does not cover.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem Perry attacks is the engine, not the language
Shipping TypeScript normally means shipping a JavaScript engine alongside it. A Node install on every server, an embedded runtime in every CLI, a browser engine in every desktop app. The README frames the cost in those terms: roughly 100 MB of embedded runtime per CLI, or a whole browser engine per desktop app. Perry's answer is to remove the engine entirely. SWC parses the TypeScript, LLVM compiles it to machine code, and the output is a standalone executable with no runtime to install. The audience is therefore narrow but real: people who like TypeScript and dislike what it forces them to deploy. CLI authors, desktop app developers, and anyone shipping to iOS, Android, watchOS or TV from one codebase. If your program is a long-running server that already has Node in the image and no cold-start problem, Perry is solving a problem you do not have.
SWC in, LLVM out: what the compilation pipeline actually does
The repository layout makes the pipeline legible. The Cargo workspace splits into perry-parser, perry-hir, perry-transform, perry-codegen, perry-dispatch and perry-runtime. Parsing and lowering happen in the first three; code generation and dispatch sit between the high-level representation and the runtime library that gets linked in. The Dockerfile confirms the shape of the artifact: it builds a release `perry` binary plus `libperry_runtime.a`, copies both into a slim Debian image, and sets `PERRY_RUNTIME_LIB` to point at the static library. That environment variable is the link between compiler and runtime, and it is the detail to check first if you build from source rather than installing the package. Around the core sit roughly forty extension crates named after the npm packages they implement: perry-ext-fastify, perry-ext-pg, perry-ext-mysql2, perry-ext-ioredis, perry-ext-ws, perry-ext-bcrypt, perry-ext-jsonwebtoken, perry-ext-sharp, perry-ext-mongodb and so on. That is the mechanism behind the claim that Node code mostly works: popular packages are reimplemented natively rather than interpreted. It also explains the ceiling. The set of working packages is the set of crates in that list, plus whatever the compatibility layer covers.
Installing Perry and compiling a first binary
The README gives three install paths, and the npm one is the shortest. It puts the `perry` command on your PATH.
npm install -g @perryts/perryHomebrew and winget are listed as alternatives in the same block, so macOS, Linux and Windows are all covered by a package manager rather than a source build. Once installed, the README's own first-use flow is a project scaffold followed by a run.
perry init my-app && cd my-app
perry run .The README does not spell out what `perry init` writes into the directory, so treat the scaffold as a starting point to inspect rather than a known layout. For a single file the documented form is the compile subcommand with an output flag.
perry compile src/main.ts -o myapp
./myappThe README states the result is a standalone native binary of roughly 330 KB for hello world, and that running it requires nothing installed. If you would rather not install anything locally, the Dockerfile supports a container path: build the image, then mount a directory and pass a TypeScript file to the entrypoint, which is `perry`.
docker build -t perry .
docker run -v $(pwd):/app perry /app/myfile.ts -o /app/myfileThat produces a Linux binary, which is the point of the compiler target in the Dockerfile. A macOS or Windows binary has to be produced on that platform.
The compatibility ceiling is the real limitation
Perry's own README states a pass rate of about 97 percent on Node's test suite across 53 `node:*` modules, and support for roughly 50 popular npm packages. Read that as a boundary, not a rounding error. Three percent of a test suite is not evenly distributed: it clusters in the corners of module behaviour that a given application may or may not touch. The extension crates are named after specific packages, which means an unlisted package is either handled by a generic path or is not handled. There is no documented fallback to a JavaScript engine, and adding one would defeat the design. The second limitation is the one the project publishes itself. Its full benchmark table includes `prime_sieve` and `matrix_multiply`, where Node and Bun beat Perry, alongside the rows Perry wins. Ahead-of-time compilation with a generational GC and escape analysis is not automatically faster than a JIT that has warmed up on a tight numeric loop. If your workload is that kind of loop, the engine you are trying to remove is also the thing making it fast. Third, the language surface is TypeScript plus plain JavaScript, with a SwiftUI-like UI API layered on top. Code that depends on dynamic patterns the compiler cannot lower will fail at compile time, which is the design working as intended, but it is still a porting cost that a Node migration does not have.
Perry against Node, Bun and Electron: different things get shipped
The comparison that matters is not speed, it is what ends up on the user's disk. Node ships your code plus a Node install. Bun ships one binary with the JavaScript engine embedded inside it. Electron ships an application bundle containing a browser engine. Perry ships one native binary with no engine at all. Bun is the closest alternative and the most instructive one: it also produces a single executable, but that executable contains the runtime, so its cold start includes engine boot and its memory profile includes the engine's. Perry's peak-memory figure in the README's JSON pipeline row is 3.5 MB against 11 MB for Bun and 36 MB for Node, which follows from there being no engine to hold resident. The trade is that Bun runs essentially any npm package because it is a JavaScript runtime, while Perry runs the packages that have native implementations. Electron is not a competitor on the same axis; it is what you use when you need a web rendering surface, and Perry's native UI path targets platform widgets instead, which is a different product decision with different consequences for how your UI code is written. Rust is the other comparison the README invites, and it is fair only in the direction stated: on convolution, fibonacci and an array-write loop, Perry's TypeScript runs level with or ahead of Rust in the published numbers. That is a statement about those three workloads, not about the two languages.
Maintenance, releases and what the MIT licence means here
The last push to the default branch was on 2026-07-04, the same day as the v0.5.1220 release. The version numbers move in large increments, and three releases land within about two weeks in the recent list, which suggests a fast release cadence rather than a stable one. That cuts both ways for upgrade cost. You get fixes quickly; you also get a moving target, and a compiler that changes its code generation between patch releases is something you want covered by tests in your own repository, not just in the project's. Building from source is a real option and a real cost: the Dockerfile pins `rust:1.75-bookworm` as the builder stage, the workspace has on the order of sixty member crates, and the release build compiles the runtime library separately. Budget a slow first build. On licensing, Perry is MIT, which is permissive and places few obligations on what you ship. The extension crates reimplement third-party packages, and those upstream packages carry their own licences; the repository does not present a consolidated licence inventory in what it publishes, so if you redistribute a binary that statically links a reimplementation of a package, verify the terms of that specific package rather than assuming MIT covers the whole artifact. That is a question for your own counsel, not something the README settles.
Editorial conclusion
Adopt Perry when the thing you are trying to delete is the JavaScript engine: CLI tools, desktop apps, mobile apps, and services where cold start and memory footprint are the actual cost. Do not adopt it as a drop-in replacement for a Node service that depends on npm packages outside the roughly fifty the project lists, or on runtime behaviour its Node test suite does not cover. Before committing, compile your real entry point and check three things: that every dependency resolves to a native implementation rather than a shim, that the binary starts on a machine without a toolchain installed, and that the modules you rely on appear in the parity results. The project's own benchmark table is the honest artifact here, and it includes the rows where V8 wins, which is more than most compiler projects publish.
Frequently asked questions
What is Perry?
Perry is a native TypeScript compiler written in Rust. It uses SWC to parse your code and LLVM to compile it to machine code, producing standalone executables for macOS, Windows, Linux, iOS, Android, watchOS and TV, with JavaScript or WebAssembly as alternative targets for the web.
How do I install Perry?
The README lists three package manager paths: `npm install -g @perryts/perry`, `brew install perryts/perry/perry`, or `winget install PerryTS.Perry`. You can also build it from source with the repository's Dockerfile, which produces the `perry` binary and `libperry_runtime.a`.
Does Perry work with existing npm packages?
The README states support for roughly 50 popular npm packages, including Fastify, Express, mysql2, pg, ioredis, ws, bcrypt and jsonwebtoken, implemented as native extension crates. Packages outside that set are not documented as supported.
How large is a binary compiled with Perry?
The README gives roughly 330 KB for a hello world binary, and cites a MongoDB GUI built with Perry that ships as an approximately 7 MB app. Perry links only the runtime your program uses.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/perryts-perry)