ReferenceEffects answers one bit per function: does anything remove any heap edge? Any reference store in the transitive callees (including a constructor's stores into its own fresh `this` behind every `new`, a closure call, or atomics in symbol-id allocation) disabled borrowing of field-read and rebound-local arguments, walk locals, borrowed returns and loop element borrows. backend/llvm/reference-writes.ts keeps the location instead: per-function transitive write sets over class fields (keyed by hierarchy root), record fields, array elements, map entries, boxes, globals and the current function's own locals, with key 0 as an unknown heap write. Locals stay exact under unknown writes: no callee can rebind a caller's unboxed local. Constructor stores into their own `this` drop out of `new`/`super` summaries (checked: constructors run only through `new` and `super`). Indirect calls resolve closure targets through function-typed parameters, captures and adapters, otherwise the union of all closure bodies; runtime library families that run no user code count as inert. Uses: - arguments of borrowed parameters (direct, `new`, virtual) and of projection-only array-read parameters borrow when neither the callee nor a later argument writes a location they depend on; - stable locals initialized by a frame-unowned projection borrow when the rest of their block writes none of its locations; optional element reads into projection-only locals likewise; - loop element borrows need only an iteration that writes no array element (nor the array's binding); - walk locals and borrowed returns are admitted in bodies whose whole write set misses the walked locations. tsc-ts e469e33 self-check (single-threaded, executed, non-null and non-immortal RC operations in emitted code, measurement build): 505.3M -> 307.6M (-39%) before return moves (next commit). Output identical. Build time unchanged (98.5 s vs 99.9 s, Linux).
scriptc
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code to native instructions. Static builds run without Node.js or a JavaScript engine.
For npm dependencies and any-typed code, enable --dynamic to include the embedded quickjs-ng engine. Dependency JavaScript is included at build time.
scriptc is experimental and supports a subset of JavaScript, TypeScript, and Node.js APIs. See the limitations and Node.js compatibility reference before using it for an existing project.
Installation
Install with Node.js 24 or later and npm:
$ npm install -g scriptc
The installed compiler runs natively on supported macOS, Linux, and Windows hosts. Native compilation and execution do not require Node.js. Standalone compiler archives are also available from GitHub Releases.
Executable builds require a platform linker and SDK/sysroot. See the quickstart for prerequisites and platform support for host requirements, cross-compilation, and WebAssembly.
Build a program
Create hello.ts:
const who = process.argv.length > 2 ? process.argv[2] : "world";
console.log(`hello, ${who}`);
Compile and run it in one step:
$ scriptc run hello.ts
hello, world
Or write a standalone executable:
$ scriptc build hello.ts -o hello
$ ./hello scriptc
hello, scriptc
These examples use POSIX shell syntax. On Windows, build hello.exe and run it with .\hello.exe.
Use scriptc coverage hello.ts to check which operations compile statically, require dynamic execution, or are unsupported. See coverage reports for details.
Use Node APIs
Supported Node APIs compile to the native runtime. For example, server.ts:
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.setHeader("content-type", "application/json");
res.end(JSON.stringify({ path: req.url }));
});
server.listen(8080, () => {
console.log("listening on http://localhost:8080");
});
$ scriptc build server.ts -o server
$ ./server
listening on http://localhost:8080
Use npm packages
Pass --dynamic to embed an npm package's JavaScript in the executable. The result does not read node_modules at runtime.
Create cli.ts:
import pc from "picocolors";
console.log(pc.green("hello from scriptc"));
$ npm install picocolors
$ scriptc build cli.ts --dynamic -o cli
$ ./cli
hello from scriptc
Enabling --dynamic does not add support for unsupported statically typed calls. See npm dependencies for package resolution and the boundary between static and dynamic code.
Documentation
- Quickstart: installation and your first executable.
- CLI reference: commands, options, and output formats.
- Native program objects: use compiler output in external builds.
- Native FFI: call C ABI functions from compiled programs.
- WebAssembly modules: build and embed WASI modules.
- Examples: programs and integration examples in this repository.
Contributing
See CONTRIBUTING.md for source setup, testing, native artifact builds, and docs development.