libregexp compiles every non-sticky pattern with a `.*?` search prefix and interprets its bytecode one opcode at a time, so an unanchored search pays a backtrack push/pop and ~7 dispatches per subject position, and each `x+` iteration pushes a backtrack state. Profiles of regex-logs and template-render showed 65-85% of samples inside lre_exec. scr_regex_native.h translates the compiled bytecode body (the bytecode stays the source of truth for parsing, flags, case folding and capture numbering) into a small program for one-byte subjects: - single-character opcodes become 128-entry tables computed by evaluating the interpreter's own predicate (lre_canonicalize, lre_is_space, range pairs) for each ASCII code unit, so /i and /u are exact on these subjects; - quantifiers over one character become counted loops that backtrack by count; greedy loops whose continuation cannot start with a byte of the loop's class (or is just saves + match) are possessive; - runs of literal bytes become one memcmp; - splits, gotos, saves, capture resets, counted group loops and empty-check registers keep libregexp's exact backtracking order and undo semantics; - unanchored searches start only at bytes that can begin a match (memchr for a single byte) and only at 0 for non-multiline `^` patterns. Lookaround and back references, and all non-ASCII subjects, keep lre_exec. scr_exec is the single dispatch point, so every match loop is shared. The program is built with the bytecode and cached on ScrRegex (new `native` slot; the emitter's %ScrRegex type and literal initializer gain one null pointer). Tests: packages/runtime/test/test_regex_native.c fuzzes the matcher against lre_exec (random patterns over the subset and its bail-outs, every start index, all capture slots; ASan+UBSan), wired into regex.test.ts; a sweep of 8 seeds x 100k patterns (~575k translated, ~70.7M checks) found no mismatch. tests/corpus/regex-native-matcher.ts pins each construct through the public APIs on ASCII subjects and non-ASCII twins. Squashes 1d210a03: build the fuzz test with -D_GNU_SOURCE on Linux; the vendored cutils.h needs CLOCK_MONOTONIC, which strict -std=c11 hides on glibc.
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.