Tsonic — TypeScript를 네이티브 바이너리로 컴파일하는 실험

6 min read
TypeScriptNativeAOT.NET컴파일러성능
Tsonic — TypeScript를 네이티브 바이너리로 컴파일하는 실험

TypeScript를 네이티브로 실행하고 싶다면

TypeScript는 JavaScript로 트랜스파일된 뒤 런타임에서 실행된다. 이 구조는 편리하지만 네이티브 바이너리의 성능이나 배포 단순성을 원하는 경우엔 부족하다. Bun처럼 더 빠른 런타임으로 갈 수도 있지만, 아예 네이티브 코드로 컴파일하는 건 다른 이야기다.

Tsonic은 그 경로를 실험하는 프로젝트다. TypeScript 코드를 C#으로 변환하고, 다시 .NET NativeAOT로 네이티브 바이너리 또는 라이브러리를 만든다.


파이프라인 구조

TypeScript 소스
      ↓
  Tsonic 컴파일러 (AST 파싱)
      ↓
    C# 코드 생성
      ↓
  .NET NativeAOT 컴파일
      ↓
네이티브 바이너리 / .NET 어셈블리

비유하면 영어 소설을 독일어로 번역한 뒤, 독일어 출판사 인쇄기로 책을 찍어내는 것이다. 원본 언어(TypeScript)를 직접 인쇄하는 게 아니라, 중간 번역(C#)을 거쳐 최종 결과물을 만든다.

[💡 잠깐! 이 용어는?] NativeAOT (Native Ahead-of-Time Compilation): .NET 런타임 없이 실행 가능한 네이티브 바이너리를 만드는 .NET 기술. JIT 컴파일 없이 미리 기계어로 변환되기 때문에 콜드 스타트가 빠르고 배포가 단순하다.


기본 사용법

터미널
# 프로젝트 초기화 (기본 CLR 환경)
tsonic init
 
# JavaScript 스타일 API 사용
tsonic init --surface @tsonic/js
 
# Node.js 모듈 지원
tsonic init --surface @tsonic/nodejs
src/main.ts
// @tsonic/js surface를 사용하는 경우
import { console } from '@tsonic/js'
 
const name: string = 'world'
console.log(`Hello, ${name}!`)
 
// nameof() 등 Tsonic 고유 intrinsics
const propName = nameof<User>('firstName') // "firstName"

[💡 잠깐! 이 용어는?] Surface (Tsonic): Tsonic에서 런타임 환경의 성격을 결정하는 설정. CLR, @tsonic/js, @tsonic/nodejs 중 선택하며, 어떤 내장 API가 TypeScript 코드에서 접근 가능한지를 정의한다.


지원하는 TypeScript 기능

V1 기준 Tsonic이 처리하는 주요 기능들:

기능지원 여부비고
기본 타입, 인터페이스
Promise .then/.catch/.finallylowering 방식
Object literal (accessor, computed key)
nameof<T>(), sizeof<T>()언어 intrinsic
Dynamic import()closed-world 방식
npm 의존성 (일반 JS)Tsonic용 패키지만
런타임 reflectionNativeAOT 제약
eval, 동적 코드 실행

[💡 잠깐! 이 용어는?] Closed-world 분석: 프로그램에서 사용될 수 있는 모든 코드를 컴파일 타임에 알고 있다는 가정. NativeAOT가 동작하는 방식이며, 런타임에 새 코드를 로딩하는 동적 패턴과 충돌한다.


.NET 생태계와의 상호 운용

Tsonic의 흥미로운 부분은 .NET 라이브러리를 직접 사용할 수 있다는 점이다.

src/file-operations.ts
import { System } from '@tsonic/dotnet/system'
import { File } from '@tsonic/dotnet/system.io'
 
// .NET File API를 TypeScript에서 직접 사용
const content = File.ReadAllText('./data.txt')
System.Console.WriteLine(content)

TypeScript 문법을 유지하면서 NuGet 패키지와 .NET 표준 라이브러리에 접근할 수 있다.


언제 쓸 수 있을까

현재 Tsonic은 실험적 단계다. GitHub Stars 161개, MIT 라이선스로 공개됐다. 프로덕션에 쓸 수준은 아니지만, 관심을 가질 만한 시나리오는 있다.

시나리오적합도이유
.NET 팀에 TypeScript 인터페이스 제공높음상호 운용 강점
CLI 도구 네이티브 배포중간런타임 불필요
고성능 서버 애플리케이션낮음 (현재)생태계 미성숙
일반 웹 서비스낮음오버엔지니어링

마무리

Tsonic은 "TypeScript를 그대로 쓰면서 네이티브 성능을 얻을 수 있을까"라는 질문에 대한 하나의 실험 답안이다. C#을 중간 타겟으로 삼은 아이디어가 독특하고, .NET 생태계와의 연결은 특정 팀에게 실용적인 가치가 있다. 지금 당장 프로덕션에서 쓸 도구는 아니지만, TypeScript 컴파일 대상이 브라우저/Node.js를 넘어 확장되는 흐름 중 하나로 주목할 만하다.


참고:

관심 있을 만한 포스트

React Compiler의 한계 — 뭘 최적화하고 뭘 못 하는가

React Compiler가 자동 메모이제이션으로 해결하는 것과 해결하지 못하는 것. 컴파일러 기반 UI 프레임워크의 능력 경계를 정리했다.

ReactReact Compiler

Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다

Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.

BunNode.js

V8의 Sea of Nodes 탈출기 — 왜 우아한 이론이 실전에서 무너졌는가

V8 팀이 10년간 사용한 Sea of Nodes IR을 포기하고 Turboshaft로 전환한 7가지 이유와 그 교훈을 정리한다.

V8컴파일러

TypeScript 6.0 Beta — TS 7 가기 전에 tsconfig부터 정리하자

TypeScript 6.0 Beta의 주요 변경 사항과 깨지는 기본값들을 정리하고, TS 7(Go 네이티브) 전환을 대비하는 마이그레이션 전략을 다룬다.

TypeScripttsconfig

Bun vs Node.js vs Deno — 뭐가 다른지, 그래서 뭘 쓰면 좋은지 (2026 기준)

런타임 3대장 비교: 호환성(Node), 속도/번들(Bun), 올인원/보안(Deno). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.

BunNode.js

AI 코딩의 맹점 — Artifacts 없이 에이전트는 기억을 잃는다

PRD, ADR, TDD가 AI 코딩 워크플로우에서 왜 선택이 아닌 필수인지, 실전 구조와 함께 살펴본다.

AI 코딩Artifacts

Next-Translate 3.0 — Turbopack과 App Router를 위한 i18n 재건

1년간 공백 후 돌아온 Next-Translate 3.0이 Turbopack 지원, 비동기 params, App Router 안정화를 한 번에 처리하는 방법.

Next.jsi18n

V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법

V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.

V8WebAssembly