Two arrows looping over a warm gradient
← Back to blog

From TypeScript to Havran, file by file

You don't rewrite anything. Havran reads your existing types and emits code your toolchain already understands, so you can adopt it one file at a time.

Adopting a new language usually means a big-bang rewrite. Havran is designed to avoid that entirely: it reads 100% of your TypeScript and JavaScript, and the code it generates is just TypeScript. You can convert a single file and leave the rest untouched.

Step 1: rename and relax

Start by translating one module. Most TypeScript maps over almost mechanically:

Havran
data class Product(val id: Long, val name: String, val price: Double)

fun total(items: List<Product>): Double =
  items.sumOf { it.price }
TypeScript
interface Product {
  readonly id: number
  readonly name: string
  readonly price: number
}

function total(items: ReadonlyArray<Product>): number {
  let acc = 0
  for (let i = 0; i < items.length; i++) acc += items[i].price
  return acc
}

Note the iteration lowers to an indexed for loop, not .reduce() — faster, and easier for engines to optimize.

Step 2: keep your imports

Havran imports your existing packages with no shims:

Havran
import { z } from "zod"

val schema = z.object(name = z.string())

What stays the same

  • Your bundler, test runner, and CI — unchanged.
  • Your node_modules — consumed directly, types and all.
  • Your output — ordinary .js + .d.ts .

What gets better

ConcernBeforeAfter
Equality === everywhere == means value equality
Null handlingoptional chainingenforced at compile time
Bundle sizeclasses & helperstiered lowering, tree-shaken
Migrate at your own pace. There's no point of no return.

When you're ready, convert the next file. That's the whole migration story.

← Back to blog