The native compiler failed on corpus programs that the Node-hosted
compiler compiles. Four compiler causes:
- A record that width-lifts into several members of a discriminated
union had no conversion plan. As a field of a larger record, that
removed the outer member it belonged to, and the outer record could
then lift into the only member left: a `recordSet` statement built with
a nested `recordGet` literal became a `break` statement, and a switch
on `kind` read it with the `recordSet` layout. The native compiler
stopped at the narrowing guard on 5585-record-results-inlined.ts and
similar programs. Such a record now converts through the discriminant:
each member it width-lifts into is a route, and the value's
discriminant selects the member at run time, as same-layout members
already do.
- Three compiler paths asserted a value that can be null, and the native
compiler threw "null is not representable in the target union":
stableCreateRequireBindingReason7 (`cache.get(decl)!` from a cache that
stores null for stable createRequire bindings; 2631-create-require,
2932-create-require-program), the Int32 contract check (`catchLocalId!`
for a bindingless `catch {}`; 4970-int32-call-abi) and
StringIndexBounds.binding (a memo that stores null). The caches are
read without assertions and the catch test checks for null.
- scriptc adopts alwaysStrict, esModuleInterop and
allowSyntheticDefaultImports from a project's tsconfig, but the TS 7
SDK schema no longer declares them. A native compiler keeps only
declared fields when it builds or parses a typed record, so these
options were dropped, and the default-import check refused
`import fs from "node:fs"` (SC1012, 2391-default-builtin-imports and the
3055 to 3057 module-setting variants). The options a project reports
are parsed with these fields declared, and Ts7CompilerOptions uses the
same type.
- An object-literal method kept its inferred return type where an arrow
function or function expression takes the return type of its
contextual signature. For a method that returns an array of statement
literals, a `varDecl` literal with `init: null` was converted to the
member whose `init` holds only expressions, and the conversion threw
(Array.fromAsync's array loop: 5854-array-from-async,
5855-array-from-async-js). A synchronous object-literal method now
takes its slot from the property of the same name on the literal's
contextual type and adopts its return the same way.
Adds corpus 6022 and 6024. 2391, 2631, 3055, 4970, 5585 and 5854 run in
the native-frontend self-host check, which now also links the dyn-async
unit when the program uses it, as the production build does.
scriptc
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses the types checked by the TypeScript compiler to generate machine code, and the output runs without Node.js installed. npm packages your program imports compile along with it. Code scriptc can't compile is reported at build time with a diagnostic and, where available, a rewrite hint.
scriptc is experimental and supports a subset of JavaScript, TypeScript, and Node.js APIs. Run scriptc coverage on an existing program to see how much of it compiles, and see the limitations and Node.js compatibility reference for details.
Installation
Install with Node.js 24 or later and npm:
$ npm install -g scriptc
The installed compiler runs natively on macOS, Linux, and Windows. Node.js is needed only to install it and to run WASI modules with scriptc run. Standalone compiler archives are also available from GitHub Releases.
Building executables requires a linker and the platform SDK or 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}`);
scriptc run compiles the file and runs the result:
$ scriptc run hello.ts
hello, world
scriptc build writes an executable:
$ scriptc build hello.ts -o hello >/dev/null
$ ./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 what compiles before you build. 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 >/dev/null
$ ./server
listening on http://localhost:8080
Use npm packages
npm packages compile into the executable with your code, so it doesn't 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 -o cli >/dev/null
$ ./cli
hello from scriptc
Package support is partial. See npm dependencies for which packages compile and how to check them.
Documentation
- Introduction: what scriptc is and what you can build.
- Quickstart: installation and your first executable.
- Coverage reports: check whether an existing program compiles.
- Platform support: cross-compilation and WebAssembly.
- 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.