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// @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/.finally | ✅ | lowering 방식 |
| Object literal (accessor, computed key) | ✅ | |
nameof<T>(), sizeof<T>() | ✅ | 언어 intrinsic |
Dynamic import() | ✅ | closed-world 방식 |
| npm 의존성 (일반 JS) | ❌ | Tsonic용 패키지만 |
| 런타임 reflection | ❌ | NativeAOT 제약 |
eval, 동적 코드 실행 | ❌ |
[💡 잠깐! 이 용어는?] Closed-world 분석: 프로그램에서 사용될 수 있는 모든 코드를 컴파일 타임에 알고 있다는 가정. NativeAOT가 동작하는 방식이며, 런타임에 새 코드를 로딩하는 동적 패턴과 충돌한다.
.NET 생태계와의 상호 운용
Tsonic의 흥미로운 부분은 .NET 라이브러리를 직접 사용할 수 있다는 점이다.
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 프레임워크의 능력 경계를 정리했다.
Bun이 빠른 건 맞다 — 그런데 당신의 이벤트 루프가 문제다
Bun으로 바꿔도 p99가 개선되지 않는 이유. 런타임 선택보다 먼저 봐야 할 진짜 병목 지점들.
V8의 Sea of Nodes 탈출기 — 왜 우아한 이론이 실전에서 무너졌는가
V8 팀이 10년간 사용한 Sea of Nodes IR을 포기하고 Turboshaft로 전환한 7가지 이유와 그 교훈을 정리한다.
TypeScript 6.0 Beta — TS 7 가기 전에 tsconfig부터 정리하자
TypeScript 6.0 Beta의 주요 변경 사항과 깨지는 기본값들을 정리하고, TS 7(Go 네이티브) 전환을 대비하는 마이그레이션 전략을 다룬다.
Bun vs Node.js vs Deno — 뭐가 다른지, 그래서 뭘 쓰면 좋은지 (2026 기준)
런타임 3대장 비교: 호환성(Node), 속도/번들(Bun), 올인원/보안(Deno). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.
AI 코딩의 맹점 — Artifacts 없이 에이전트는 기억을 잃는다
PRD, ADR, TDD가 AI 코딩 워크플로우에서 왜 선택이 아닌 필수인지, 실전 구조와 함께 살펴본다.
Next-Translate 3.0 — Turbopack과 App Router를 위한 i18n 재건
1년간 공백 후 돌아온 Next-Translate 3.0이 Turbopack 지원, 비동기 params, App Router 안정화를 한 번에 처리하는 방법.
V8 WasmGC 투기적 최적화 — 가상 메서드를 인라인으로 만드는 법
V8이 WasmGC의 가상 메서드 디스패치에 투기적 인라이닝을 도입해 Dart와 Java 앱에서 최대 8% 성능을 끌어낸 방법.