TypeScript,
compiled native.
Write TypeScript with real native types. One compiler lowers it to TSN IR, then emits Java for Android and the JVM and Swift for iOS and macOS. No JavaScript engine, no bridge, no reflection.
export class Counter {
private count: int = 0
increment(step: int): int {
this.count += step
return this.count
}
}public class Counter {
int count = 0;
public Counter() {}
public int increment(int step) {
this.count += step;
return this.count;
}
}public final class Counter {
var count: Int32 = 0
public init() {}
public func increment(_ step: Int32) -> Int32 {
count &+= step
return count
}
}01How it works
One IR,
every platform.
TypeScript Native checks your code once and lowers it to TSN IR, a typed intermediate representation that pins down the semantics. Each backend emits idiomatic source for its platform, and the platform toolchain takes it from there.
TypeScript
Your sources with int, float and the other native types.
counter.tsTSN IR
Typed, checked and platform-neutral. The semantics are fixed here, once.
one shared specEvery IR operation sits in one exhaustive union, so a new backend learns at compile time what it still has to handle.
Backend roadmap02Performance
As fast as
handwritten Kotlin.
We hold every backend to the code you would write by hand. On Android the Java backend lands within noise of direct Kotlin on a scalar host call, and two orders of magnitude ahead of an embedded JavaScript engine.
Scalar host call
Nanoseconds per iteration · lower is better
Java backend on Android. Median of five fresh processes, 40 warmups and 20 measured invocations each, API 36 ARM64 emulator on an Apple M5 Max, November 9, 2026, original single-file compiler. Swift results against handwritten Swift follow.
Methodology and raw results140×
faster than Hermes with Nitro on a scalar host call, 0.668 against 93.65 ns0.66ns
per numeric loop iteration with no host calls, against 0.79 ns in direct Kotlin1.02×
the time of direct Kotlin for a synchronous update of a real Android View03The language
TypeScript you know.
Types every platform knows.
The portable language follows ECMAScript 2026 and adds what native code needs: fixed-width primitives, checked casts and imports from each platform SDK.
Real primitive types
Pick an exact width for fields, parameters and results. An int stays a 32-bit int on the JVM and becomes Int32 in Swift. No boxing, no surprises.
byteshortintlongfloatdoublecharboolean| TypeScript | JVM | Swift |
|---|---|---|
| byte | byte | Int8 |
| int | int | Int32 |
| long | long | Int64 |
| float | float | Float |
| number | double | Double |
| char | char | UInt16 |
| boolean | boolean | Bool |
Call any platform SDK
Import Android classes from their Java package and Apple frameworks from their Swift module. Types come from the real SDK.
// Android: Java packages
import { TextView } from "android.widget"
import { Bundle } from "android.os"
// iOS and macOS: Swift modules
import UIKit from "UIKit"
import { URLSession } from "Foundation"ECMAScript 2026 semantics
One documented behavior on every backend for Map, Promise, typed buffers and the rest of the standard library.
Idiomatic native APIs
Every module becomes an ordinary Java class and Swift type. Call it from Kotlin, Java or Swift without glue code.
// spacing.ts
export function doubleSpacing(
points: int,
): int { return points + points }
// Kotlin
val gap = SpacingTs.doubleSpacing(6)
// Swift
let gap = SpacingTs.doubleSpacing(6)Errors at build time
Anything outside the language fails the build with a located diagnostic, never at runtime.
04Everywhere
Write it once.
Ship it native everywhere.
Share the logic and keep your UI in Kotlin or SwiftUI, or write the whole screen in TypeScript. The generated APIs read like code you would write by hand.
export interface CounterState {
readonly count: int
readonly step: int
}
export function next(state: CounterState): CounterState {
return { count: state.count + state.step, step: state.step }
}
export function describe(state: CounterState): string {
return "Count: " + state.count
}button.setOnClickListener {
state = CounterTs.next(state)
label.text = CounterTs.describe(state)
}Button("Increment") {
state = CounterTs.next(state)
}
Text(CounterTs.describe(state))TypeScript Counter
Count: 3
IncrementCounter
Count: 3
Increment05Get started
From one file
to every platform.
Add the build plugin to your Android or Apple project, put TypeScript next to your sources and call it like any other native code. You do not need Node or npm to build an app.
Write TypeScript
Put modules next to your app sources and use native types where they matter.
export function next(
count: int,
step: int,
): int {
return count + step
}Add the build plugin
Gradle for Android and the JVM, SwiftPM for iOS and macOS. Each runs its backend.
// app/build.gradle.kts
plugins {
id("com.margelo.typescript-art")
}
// Package.swift
.target(name: "App", plugins: [
.plugin(name: "TypeScriptNative"),
])Call it natively
The generated Java class and Swift type have native signatures. There is no bridge to set up.
// Kotlin
val n = CounterTs.next(count, 1)
// Swift
let n = CounterTs.next(count, 1)Ship TypeScript
as native code.
Open source under the MIT License. Start from the Android or iOS starter, or try the compiler in your browser first.