Vercel Labs가 TypeScript를 Node나 V8 없이 네이티브 실행 파일로 찍어내는 실험적 컴파일러 scriptc를 내놨다. 코드는 대부분 정적으로 컴파일하고, 타입이 흐려지는 부분만 내장된 620KB짜리 quickjs-ng 엔진에 떠넘긴다.
같은 TypeScript CLI를 켜는 데 Node 24.18.0은 61.78ms, Bun 1.3.12는 21.29ms가 걸린다. Vercel Labs가 내놓은 실험적 컴파일러 scriptc로 네이티브 실행 파일을 만들면 1.78ms다. 이 바이너리 안에는 Node도 V8도, 자바스크립트 엔진 자체도 없다.
scriptc는 Apache 2.0 라이선스로 공개된 오픈소스 프로젝트다. 파싱과 타입 체크는 실제 TypeScript 컴파일러가 그대로 맡고, 그 결과를 타입이 붙은 중간 표현으로 낮춘 뒤 C, LLVM IR, 어셈블리, 오브젝트 파일, 네이티브 실행 파일, 또는 WASI Preview 1을 통한 WebAssembly로 찍어낸다.
세 갈래로 갈리는 코드
컴파일러는 모든 구문을 세 갈래 중 하나로 떨어뜨린다. 기본은 정적 컴파일이다. --dynamic 플래그를 주면 npm 패키지나 타입이 흐려지는 코드를 내장된 620KB짜리 quickjs-ng 엔진에서 동적으로 돌린다. 그것도 안 되면 컴파일 시점에 SC 코드와 고쳐 쓸 방법을 알려주며 그냥 거부한다.
Hono를 올리는 데는 --dynamic이 필요했고, 그 결과 서버 코드의 62%가 QuickJS 안으로 들어갔다. 처리량은 Bun의 초당 7만 500건에서 초당 1만 8400건으로 떨어졌다. 해커뉴스에는 바이트 배열 벤치마크에서 scriptc가 Node 24보다 7.5배 느렸다는 사용자 보고도 올라왔다 — 클로드로 scriptc 전용 최적화까지 시도한 뒤였다. 대신 실행 파일은 12배 빨리 뜨고(1.5ms 대 18.6ms), 메모리는 72배 덜 쓰고(2.5MiB 대 181MiB), 런타임 의존성 없는 370KB 단일 파일로 나왔다.
왜 이렇게 갈리나
Filip Pizlo는 모든 수를 실수(float)로 다루고 정수 추론을 뒤로 미루는 방식이 '빠른 JS의 절반을 건너뛴다'고 지적했다. quickjs-ng에 의존하는 구조도 성능을 노리는 프로젝트치고는 궁합이 나쁘다고 봤다 — any 타입은 실제 코드에 흔해서, 결국 QuickJS라는 섬으로 계속 떨어지게 된다는 뜻이다. 다른 사용자는 로컬에 있는 프로젝트마다 커버리지를 돌려봤는데 전부 수백 개의 에러가 나서 실질적으로 못 쓰겠다고 썼다. 처음부터 서드파티 라이브러리 없이 새로 짠 코드만 컴파일된다면, Rust·Go·Zig·Swift처럼 원래 컴파일 언어를 쓰지 않을 이유가 없다는 반론이다.
반복해서 나오는 걱정은 지속 가능성이다. 댓글들은 Vercel의 또 다른 프로젝트 zerolang이 공개 몇 주 만에 커밋이 끊긴 사례를 든다. Remo Jansen은 TypeScript 6 컴파일러 자체를 scriptc로 컴파일해보려다 내부 컴파일러 오류로 실패했다. 그래도 콜드 스타트는 3.6ms로 Node의 48.9ms보다 빨랐고, 반대로 연산이 몰리는 구간에서는 2.33초로 훨씬 느렸다.
Simon Willison은 최근 한 주 동안 코딩 에이전트들이 91만 8000줄을 쳐냈다는 점을 짚으면서, C나 Rust를 직접 쓰지 않고도 작고 빠른 바이너리를 만들 수 있게 된 건 '가치 있는 능력'이라고 썼다.