Two open-source projects now make a similar promise: write TypeScript or JavaScript, compile it ahead of time, and ship a native executable without requiring Node.js on the target machine.
Those projects are Perry and scriptc.
Their headlines sound similar, but they are not trying to build exactly the same product. Perry is growing into a cross-platform application platform with native UI, mobile targets, Node-compatible APIs, threading, and native integrations. scriptc is more narrowly centered on compiling ordinary TypeScript while making the boundary between static code, dynamic JavaScript, and unsupported code visible.
That distinction matters more than a benchmark table.
This comparison is based on each project's official repository, documentation, and published test methodology as reviewed on August 18, 2026. Perry was reviewed at commit e53de81, and scriptc at commit a737fde. Both projects are moving quickly, so check their current documentation before making a production decision.
Perry vs scriptc: the short answer
| Question | Perry | scriptc |
|---|---|---|
| Primary direction | A broad native application platform built around TypeScript | A TypeScript-to-native compiler with an explicit static/dynamic/rejected contract |
| Frontend | SWC parser, then Perry's HIR and transforms | The TypeScript compiler for parsing and type checking, then a typed IR |
| Native backend | LLVM code generation and a system linker | LLVM IR compiled by clang; a readable C backend is also available for inspection |
| Default native path | No JavaScript engine; an optional JS runtime requires explicit opt-in | No JavaScript engine in a static build; --dynamic explicitly embeds quickjs-ng |
| TypeScript model | A practical subset; most type annotations are erased | Type information drives lowering, generic specialization, and checked boundaries |
| Node.js compatibility | A large native implementation of Node and web APIs, plus package-specific bindings | Supported Node APIs are implemented in the native runtime and tested against Node behavior |
| npm strategy | Native bindings, selected packages compiled through compilePackages, or an explicitly enabled JS runtime |
npm package code normally enters an explicit dynamic island; static npm compilation is experimental |
| Native UI | Built-in declarative UI mapped to AppKit, UIKit, GTK4, Win32, and the DOM | No comparable application UI framework is documented |
| Cross-platform scope | Desktop, Apple platforms, Android/Wear OS, and Web/WASM | macOS, Linux, Windows, WASI, plus mobile library-mode archives |
| License | MIT | Apache-2.0 |
In practical terms, Perry is the more application-oriented choice. scriptc is the more compiler-contract-oriented choice.
What does "compile TypeScript to native" actually mean?
Neither project simply runs tsc and packages the resulting JavaScript. Both lower supported TypeScript or JavaScript constructs into native code and link support code for features such as strings, arrays, asynchronous work, and operating-system APIs.
For Perry, the documented pipeline is:
TypeScript
-> SWC parser
-> Perry HIR
-> optimization and lowering passes
-> LLVM
-> object file
-> system linker
-> native executable
Perry's architecture documentation also describes its runtime library, including JavaScript value representations, garbage collection, arrays, strings, and UI handles.
For scriptc, the documented pipeline is:
TypeScript
-> TypeScript compiler: parse and type-check
-> lowering
-> typed IR
-> LLVM IR
-> clang
-> native executable
scriptc also retains a readable C backend for debugging and inspection. Its How It Works documentation says that the LLVM backend is the production default and that runtime features are split into link-gated units.
This is an important clarification: "no runtime" in compiler marketing should usually be read as "no Node.js or general JavaScript engine in the normal static output," not "the executable contains no support code at all." Both projects link native runtime components needed to preserve useful JavaScript behavior.
The biggest architectural difference: how TypeScript types are used
Perry uses SWC to parse TypeScript and lowers the result into its own high-level intermediate representation. Its documentation describes support for a practical subset of TypeScript and states that interfaces, type aliases, and most annotations are erased. Perry also documents that annotations do not automatically become runtime validators. These rules are explicit in Perry's supported features and limitations.
scriptc uses the TypeScript compiler itself for parsing and type checking. It then uses the checker's type and narrowing results when building its typed IR. Generics can be monomorphized, unions can become tagged representations, and crossings from dynamic code into statically typed code can be validated at runtime. The project documents this model in How It Works.
That does not mean scriptc accepts every program that tsc accepts. Its limitations page says it uses its own type world based on es2025 plus its ambient declarations, and it deliberately tightens some signatures to match what the compiler can implement. A program may therefore need changes even if it already passes ordinary TypeScript checking.
The trade-off is clear:
- Perry owns more of the language-to-runtime mapping and can shape it around a broad native platform.
- scriptc treats TypeScript's type information as part of the compilation contract and exposes a stricter boundary around what it can prove and lower.
Static compilation, dynamic JavaScript, and npm packages
Both projects prefer engine-free native code, but neither can statically compile arbitrary JavaScript behavior. Dynamic property patterns, runtime-generated code, and real-world npm packages eventually force a choice: reject the code, port it, replace it with a native implementation, or embed an interpreter.
Perry's npm and JavaScript runtime strategy
Perry offers several paths for dependencies:
- built-in or third-party native bindings can map familiar package APIs to Rust functions linked into the executable;
- selected pure TypeScript or JavaScript packages can be pulled into Perry's compiler through
perry.compilePackages; - JavaScript files that need interpretation can use Perry's optional JS runtime.
The important safety and deployment detail is that the interpreter is not supposed to appear silently. Perry's current JS runtime opt-in documentation describes perry-jsruntime as QuickJS-based and says the build refuses to link it unless the application opts in through configuration, a CLI flag, or an environment variable.
Perry also labels its current compilePackages porting workflow as experimental. Packages based heavily on features such as Proxy, or packages that depend on Node's native addon ABI, may need replacement or a Perry-specific native binding.
scriptc's dynamic island
scriptc divides a program into three visible tiers:
- code compiled statically;
- code that can run only when
--dynamicis enabled; - code that is rejected.
Its coverage command reports how many statements compile statically and identifies dynamic or unsupported sites with diagnostic codes. A static build never silently adds an engine.
When --dynamic is enabled, scriptc embeds quickjs-ng for npm package implementations and any-typed code. Values crossing back into static code are copied and validated. The npm dependencies documentation explicitly warns that CPU-bound dependency code in this island is slower than V8 and that its Node built-ins are shims rather than Node itself.
scriptc also has an experimental --npm-static mode, but its own documentation says package coverage is partial and unsupported sites can remain deferred or prevent static compilation.
The key difference is not that one project has an escape hatch and the other does not. Both do. The difference is how prominently the boundary is exposed. scriptc makes the static/dynamic split a first-class report for each program, while Perry combines native compilation with package bindings, package porting, and explicit interpreter opt-in as part of a wider application platform.
Node.js compatibility: published claims are not the same as guarantees
Perry documents native implementations for a broad set of Node modules and web APIs. Its current supported-features page reports roughly 97% passing results across 53 node:* modules from Node's own test suite, with roughly 95% overall Node/TypeScript compatibility. Those are Perry's published project results, not an independent certification that every Node application will compile unchanged.
The limitations remain significant for some projects. Perry documents restrictions around runtime-generated code, general user-space CommonJS require(), reflection, and dynamic language features. Its package-porting guide also explains why Node-API/N-API, NAN, V8, or libuv-based native addons cannot simply pass through compilePackages.
scriptc takes a different evidence approach. Its repository says the test corpus runs each program under Node and as a compiled binary, then compares stdout, stderr, and exit codes byte for byte. It also reruns the corpus with AddressSanitizer and a reference-count audit. That methodology is documented in both the repository README and How It Works.
However, scriptc does not claim universal Node compatibility. Its limitations documentation lists deliberate behavioral differences, unsupported standard-library surfaces, constraints on any, dense-array behavior, FFI restrictions, and differences inside the dynamic island.
So the fair reading is:
- Perry publishes a wide compatibility surface and a high project-reported pass rate.
- scriptc emphasizes differential testing and enumerated divergences for the narrower surface it accepts.
- neither result means that an arbitrary Node.js application or npm dependency graph is guaranteed to work.
Cross-platform native UI, mobile apps, and WebAssembly
This is where Perry's broader product scope becomes most obvious.
Perry includes a declarative perry/ui framework. Its widgets map to AppKit on macOS, UIKit on iOS, GTK4 on Linux, Win32 on Windows, and DOM elements on the web. Perry's platform overview also covers Android, Wear OS, watchOS, tvOS, visionOS, and WebAssembly-related targets.
scriptc targets macOS, Linux, Windows, and WebAssembly through WASI Preview 1. Its current platform documentation also describes iOS and Android static archives in library mode, intended to be linked into a host mobile application. It does not document a Perry-like cross-platform widget framework.
That creates a straightforward decision boundary. If the goal is to build a native desktop or mobile interface directly from the same TypeScript-oriented platform, Perry is attempting to provide much more of the stack. If the goal is to compile typed application logic, a CLI, a server, a WASI module, or a library core while keeping the host UI separate, scriptc's narrower model may be easier to reason about.
What about TypeScript-to-native performance?
Perry publishes an open benchmark harness and reports both workloads it wins and workloads where Node.js or Bun is faster. That is better evidence than a table containing only favorable cases, and the exact harness is linked from Perry's README.
But those numbers do not answer "Is Perry faster than scriptc?" scriptc uses different runtime representations, a different compatibility contract, and different dynamic fallback behavior. No same-program, same-commit, same-machine comparison between the two projects was performed for this article.
A fair benchmark would need to separate at least these cases:
- fully static code in both compilers;
- code that activates either project's optional JavaScript engine;
- startup time, steady-state throughput, peak memory, and binary size;
- correct output and error behavior, not only elapsed time;
- the exact compiler commits, optimization flags, toolchain, and operating system.
Until that experiment exists, a performance winner would be speculation.
Which TypeScript native compiler should you try?
Try Perry first when:
- native desktop or mobile UI is central to the project;
- you want a broad batteries-included platform around TypeScript;
- Perry's native modules or package-specific bindings cover your stack;
- its real-thread APIs and cross-platform application targets are important.
Try scriptc first when:
- you want the TypeScript compiler's type information to drive native lowering;
- you need a report showing exactly which parts of your application stay static;
- explicit rejection is preferable to silently accepting unsupported behavior;
- you are building a CLI, server, WASI module, or embeddable native library without needing a built-in UI toolkit.
For either project, start with a representative slice of the real application rather than a Fibonacci demo. Include the npm dependencies, Node APIs, error paths, asynchronous behavior, target platforms, and deployment constraints that will determine whether the experiment succeeds.
Conclusion
Perry and scriptc share an attractive headline: compile TypeScript to native code and ship without requiring Node.js. Under that headline, they optimize for different outcomes.
Perry is building outward toward a complete cross-platform native application environment. scriptc is building inward around an inspectable compilation contract: static, explicitly dynamic, or rejected.
That is why the useful question is not simply "Which compiler is faster?" It is "Which project's compatibility boundary and product scope match the application I want to build?"
Primary sources
- Perry repository at the reviewed commit
- Perry compiler architecture
- Perry supported TypeScript features and Node.js compatibility
- Perry limitations
- Perry JS runtime opt-in
- Perry package-porting guide
- Perry native UI overview
- Perry platform overview
- scriptc repository at the reviewed commit
- scriptc introduction and compilation tiers
- scriptc architecture and differential testing
- scriptc coverage reports
- scriptc npm dependency model
- scriptc limitations
- scriptc platform support
Top comments (0)