TypeScript Native Compiler
July 20, 2026 · View on GitHub
A native ahead-of-time (AOT) and JIT compiler for TypeScript, built on LLVM/MLIR.
It compiles .ts files directly to native executables, WebAssembly, or runs them
on the fly via a built-in JIT — no Node.js or JavaScript runtime required.
CI Status
Contents
- What's new
- Roadmap
- Demo
- Try it online
- Example
- Running your code
- Building from source
- Community
- License
What's new
-
Implemented
try/catchexception handling in the JIT on Windows (x64 SEH unwinding via a custom LLJIT runtime) -
Migrared to LLVM 22.1.8
-
Migrated to Visual Studio 2026 (Windows build chain)
-
JavaScript Built-in objects library [Default Library repo]
-
Visual Studio Code project
tslang --new Test1
- CMake project (mixed C++/TypeScript,
TSLANGregistered as a first-class CMake language)
tslang --cmake Test1
Generates a ready-to-build project (CMakeLists.txt, CMakePresets.json, main.cpp, sample .ts sources and the cmake/ TSLANG language modules). Build it with:
cd Test1
cmake --preset default && cmake --build --preset default
- Strict null checks
let sn: string | null = null; // Ok
let s: string = null; // error
- Improved
Template Literal Types
type Color = "red" | "green" | "blue";
type HexColor<T extends Color> = `#${string}`;
- Public, private, and protected modifiers
class Point {
private x: number;
#y: number;
}
const p = new Point();
p.x // access error
p.#y // error
- Class from Tuple
class Point {
x: number;
y: number;
}
class Line {
constructor(public start: Point, public end: Point) { }
}
const l = new Line({ x: 0, y: 1 }, { x: 1.0, y: 2.0 });
- Compile-time
ifs
function isArray<T extends unknown[]>(value: T): value is T {
return true;
}
function gen<T>(t: T)
{
if (isArray(t))
{
return t.length.toString();
}
return "int";
}
const v1 = gen<i32>(23); // result: int
const v2 = gen<string[]>([]); // result: 0
-
Migrated to LLVM 19.1.3
-
Improved
generating debug information— more info here: Wiki:How-To
tslang --di --opt_level=0 --emit=exe example.ts
Roadmap
- Migrating to LLVM 22.1.8
- Shared libraries
- JavaScript Built-in classes library
Demo
See the release page for a recorded demo
Try it
Open the WASM example on Compiler Explorer
Chat Room
Want to chat with other members of the TypeScriptCompiler community?
- GitHub Discussions (preferred)
(legacy)
Example
abstract class Department {
constructor(public name: string) {}
printName(): void {
print("Department name: " + this.name);
}
abstract printMeeting(): void; // must be implemented in derived classes
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call super()
}
printMeeting(): void {
print("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
print("Generating accounting reports...");
}
}
function main() {
let department: Department; // ok to create a reference to an abstract type
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
//department.generateReports(); // error: department is not of type AccountingDepartment, cannot access generateReports
}
Run
tslang --emit=jit --opt --shared-libs=TypeScriptRuntime.dll example.ts
Result
Department name: Accounting and Auditing
The Accounting Department meets each Monday at 10am.
Run as JIT
File hello.ts
function main() {
print("Hello World!");
}
Build
tslang hello.ts
Result
Hello World!
Debugging JIT code with GDB (Linux)
JIT-compiled TypeScript can be debugged at source level with GDB — breakpoints on .ts lines,
stepping, and backtraces all work. The JIT registers every compiled object with the debugger via
the standard GDB JIT interface,
so an attached GDB picks up the DWARF debug info at runtime.
Two things are required:
- pass
--diso the compiler emits debug information; - run without
--opt(JIT debug registration is only enabled when optimizations are off).
gdb --args tslang --di hello.ts
(gdb) break hello.ts:2
Make breakpoint pending on future shared library load? (y or [n]) y
(gdb) run
The breakpoint stays pending until the JIT compiles and registers your code, then binds and stops with full source context:
Thread 1 "tslang" hit Breakpoint 1, main () at hello.ts:2
2 print("Hello World!");
From there the usual GDB commands work on the JIT'd frames: next, step, info locals, bt.
Notes:
- The
(No debugging symbols found in tslang)warning at startup refers to the compiler binary itself and is harmless — the debug info for your code arrives when the JIT registers it. - The
debugger;TypeScript statement is also supported: it compiles to a debug trap instruction, so execution stops exactly there when a debugger is attached. Without a debugger attached it terminates the process withSIGTRAP, so remove it when you are done. - For LLDB, enable its JIT loader first:
settings set plugin.jit-loader.gdb enable.
Compile as Binary Executable
Make sure you have download
tslangfrom releases first.
The compile process may use a few environment variables so the linker can find the
runtime libraries, then invoke tslang with --emit=exe. Edit the paths to match your
checkout — the C:\dev\... / ~/dev/... values are only examples.
| Variable | Points to |
|---|---|
GC_LIB_PATH | Boehm GC library (the garbage collector) |
LLVM_LIB_PATH | LLVM/MLIR libraries |
TSLANG_LIB_PATH | TSLANG runtime library |
DEFAULT_LIB_PATH | Default library |
Compile on Windows
Build
tslang --emit=exe hello.ts
Run
hello.exe
Result
Hello World!
Compile on Linux (Ubuntu 20.04 and 22.04)
Build
./tslang --emit=exe hello.ts --relocation-model=pic
Run
./hello
Result
Hello World!
Compiling as WASM
WASM build on Windows
Build
tslang.exe --emit=exe --nogc -mtriple=wasm32-unknown-unknown hello.ts
Run run.html
Click to expand the full run.html WebAssembly loader
<!DOCTYPE html>
<html>
<head></head>
<body>
<script type="module">
let buffer;
let buffer32;
let buffer64;
let bufferF64;
let heap;
let heap_base, heap_end, stack_low, stack_high;
const allocated = [];
const allocatedSize = (addr) => {
return allocated["" + addr];
};
const setAllocatedSize = (addr, newSize) => {
allocated["" + addr] = newSize;
};
const expand = (addr, newSize) => {
const aligned_newSize = newSize + (4 - (newSize % 4))
const end = addr + allocatedSize(addr);
const newEnd = addr + aligned_newSize;
for (const allocatedAddr in allocated) {
const beginAllocatedAddr = parseInt(allocatedAddr);
const endAllocatedAddr = beginAllocatedAddr + allocated[allocatedAddr];
if (beginAllocatedAddr != addr && addr < endAllocatedAddr && newEnd > beginAllocatedAddr) {
return false;
}
}
setAllocatedSize(addr, aligned_newSize);
if (addr + aligned_newSize > heap) heap = addr + aligned_newSize;
return true;
};
const endOf = (addr) => { while (buffer[addr] != 0) { addr++; if (addr > heap_end) throw "out of memory boundary"; }; return addr; };
const strOf = (addr) => String.fromCharCode(...buffer.slice(addr, endOf(addr)));
const copyStr = (dst, src) => { while (buffer[src] != 0) buffer[dst++] = buffer[src++]; buffer[dst] = 0; return dst; };
const ncopy = (dst, src, count) => { while (count-- > 0) buffer[dst++] = buffer[src++]; return dst; };
const append = (dst, src) => copyStr(endOf(dst), src);
const cmp = (addrL, addrR) => { while (buffer[addrL] != 0) { if (buffer[addrL] != buffer[addrR]) break; addrL++; addrR++; } return buffer[addrL] - buffer[addrR]; };
const prn = (str, addr) => { for (let i = 0; i < str.length; i++) buffer[addr++] = str.charCodeAt(i); buffer[addr] = 0; return addr; };
const clear = (addr, size, val) => { for (let i = 0; i < size; i++) buffer[addr++] = val; };
const aligned_alloc = (size) => {
const aligned_size = size + (4 - (size % 4));
if ((heap + aligned_size) > heap_end) throw "out of memory";
setAllocatedSize(heap, aligned_size);
const heapCurrent = heap;
heap += aligned_size;
return heapCurrent;
};
const free = (addr) => delete allocated["" + addr];
const realloc = (addr, size) => {
if (!expand(addr, size)) {
const newAddr = aligned_alloc(size);
ncopy(newAddr, addr, allocatedSize(addr));
free(addr);
return newAddr;
}
return addr;
}
const envObj = {
memory: new WebAssembly.Memory({ initial: 256 }),
table: new WebAssembly.Table({
initial: 0,
element: 'anyfunc',
}),
fmod: (arg1, arg2) => arg1 % arg2,
sqrt: (arg1) => Math.sqrt(arg1),
floor: (arg1) => Math.floor(arg1),
pow: (arg1, arg2) => Math.pow(arg1, arg2),
fabs: (arg1) => Math.abs(arg1),
_assert: (msg, file, line) => console.assert(false, strOf(msg), "| file:", strOf(file), "| line:", line, " DBG:", path),
puts: (arg) => output += strOf(arg) + '\n',
strcpy: copyStr,
strcat: append,
strcmp: cmp,
strlen: (addr) => endOf(addr) - addr,
malloc: aligned_alloc,
realloc: realloc,
free: free,
memset: (addr, size, val) => clear(addr, size, val),
atoi: (addr, rdx) => parseInt(strOf(addr), rdx),
atof: (addr) => parseFloat(strOf(addr)),
sprintf_s: (addr, sizeOfBuffer, format, ...args) => {
const formatStr = strOf(format);
switch (formatStr) {
case "%d": prn(buffer32[args[0] >> 2].toString(), addr); break;
case "%g": prn(bufferF64[args[0] >> 3].toString(), addr); break;
case "%llu": prn(buffer64[args[0] >> 3].toString(), addr); break;
default: throw "not implemented";
}
return 0;
},
}
const config = {
env: envObj,
};
WebAssembly.instantiateStreaming(fetch("./hello.wasm"), config)
.then(results => {
const { main, __wasm_call_ctors, __heap_base, __heap_end, __stack_low, __stack_high } = results.instance.exports;
buffer = new Uint8Array(results.instance.exports.memory.buffer);
buffer32 = new Uint32Array(results.instance.exports.memory.buffer);
buffer64 = new BigUint64Array(results.instance.exports.memory.buffer);
bufferF64 = new Float64Array(results.instance.exports.memory.buffer);
heap = heap_base = __heap_base, heap_end = __heap_end, stack_low = __stack_low, stack_high = __stack_high;
try
{
if (__wasm_call_ctors) __wasm_call_ctors();
main();
}
catch (e)
{
console.error(e);
}
});
</script>
</body>
</html>
Build
Build on Windows
Windows Requirements
Visual Studio 2026- ~50 GB of free disk space (LLVM/MLIR build dominates this)
The hardcoded
C:\dev\...paths in the scripts below are examples — adjust them to your checkout location.
First, precompile dependencies
cd TypeScriptCompiler
prepare_3rdParty.bat
To build TSLANG binaries:
cd TypeScriptCompiler\tslang
config_tslang_release.bat
build_tslang_release.bat
Build on Linux (Ubuntu 20.04 and 22.04)
Linux Requirements
gccorclangcmakeninja-build- ~50 GB of free disk space (LLVM/MLIR build dominates this)
- sudo apt-get install
libtinfo-dev
First, precompile dependencies
chmod +x *.sh
cd ~/TypeScriptCompiler
./prepare_3rdParty.sh
To build TSLANG binaries:
cd ~/TypeScriptCompiler/tslang
chmod +x *.sh
./config_tslang_release.sh
./build_tslang_release.sh
License
This project is licensed under the MIT License — see the LICENSE file for details.
Contributing
Issues and pull requests are welcome. See the Wiki for documentation, build notes, and how-to guides.


