v2Swift backend for iOS and macOS

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.

Runs onAndroid · ARTJVMiOSmacOSWebPlanned

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.ts
check + lower

TSN IR

Typed, checked and platform-neutral. The semantics are fixed here, once.

one shared spec
Java backendAndroid · ARTJVM
Java sourcejavacD8 / R8DEX
Swift backendiOSmacOS
Swift sourceswiftcLLVMmachine code
JavaScript backendPlannedWeb
JavaScriptbundler
Add a backend

Every IR operation sits in one exhaustive union, so a new backend learns at compile time what it still has to handle.

Backend roadmap

02Performance

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

TypeScript NativeJava backend
1.00× Kotlin
0.668 ns
Direct KotlinHandwritten baseline
0.667 ns
Rhino AOT, explicitHand-written bindings
59.19 ns
Hermes HBC + NitroStandalone engine
93.65 ns
Rhino AOT, reflectiveReflective Java interop
141.52 ns

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 results

140×

faster than Hermes with Nitro on a scalar host call, 0.668 against 93.65 ns

0.66ns

per numeric loop iteration with no host calls, against 0.79 ns in direct Kotlin

1.02×

the time of direct Kotlin for a synchronous update of a real Android View

03The 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
TypeScriptJVMSwift
bytebyteInt8
intintInt32
longlongInt64
floatfloatFloat
numberdoubleDouble
charcharUInt16
booleanbooleanBool

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.

MathPromiseasync / awaitMapSetArrayBufferErrorArrayStringDataViewUint8Arrayclassesclosuresfor…ofdestructuring

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.

$ ts-artc checkscale.ts:1:23: error TSA2001: A type annotation is required.1 │ export function scale(x) {

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.

shared/counter.tsTypeScript
counter.tsMainActivity.ktCounterView.swift
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
}
Android · Kotlin
button.setOnClickListener {
  state = CounterTs.next(state)
  label.text = CounterTs.describe(state)
}
iOS · SwiftUI
Button("Increment") {
  state = CounterTs.next(state)
}
Text(CounterTs.describe(state))

05Get 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.

01

Write TypeScript

Put modules next to your app sources and use native types where they matter.

shared/counter.ts
export function next(
  count: int,
  step: int,
): int {
  return count + step
}
02

Add the build plugin

Gradle for Android and the JVM, SwiftPM for iOS and macOS. Each runs its backend.

build files
// app/build.gradle.kts
plugins {
  id("com.margelo.typescript-art")
}

// Package.swift
.target(name: "App", plugins: [
  .plugin(name: "TypeScriptNative"),
])
03

Call it natively

The generated Java class and Swift type have native signatures. There is no bridge to set up.

call sites
// 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.