Microsoft가 셀프호스팅을 포기한 날
2026년 7월 8일, TypeScript 7.0이 정식 출시됐다. 이 릴리스의 핵심은 새 문법이 아니다. 컴파일러 그 자체다. TypeScript로 작성해 JavaScript로 컴파일해서 돌리던 tsc를, Microsoft는 2025년 3월 포팅 계획을 공개한 지 1년 4개월 만에 Go로 통째로 옮겨 정식판까지 끌고 왔다. 타입 체킹의 동작과 의미는 그대로 두고 구현 언어만 바꿨는데, 대략 10배가 빨라졌다. 포팅 계획을 처음 공개할 때 내놓은 벤치마크에서, 150만 줄짜리 VS Code 저장소의 타입 체크는 77.8초에서 7.5초가 됐다.

TypeScript는 셀프호스팅 컴파일러의 대표 사례였다. 자기 언어로 자기를 컴파일한다는 건 그 언어가 큰 소프트웨어를 감당할 만큼 성숙했다는 증표였고, TypeScript 팀은 그걸 10년 넘게 지켜왔다. 그 증표를 Microsoft가 스스로 내려놓은 것이다.
한 번의 사건이 아니라 방향이다
이게 TypeScript만의 이야기라면 에세이로 쓸 일도 없었을 거다. 2020년 esbuild가 Go로 번들러를 다시 쓰면서 시작된 흐름은 SWC, Lightning CSS, Turbopack, Biome으로 이어졌고, 올해 들어 완전히 대세가 됐다.
5월 7일 Rust 기반 번들러 Rolldown이 1.0에 도달했다. Vite 8은 개발 서버의 esbuild와 프로덕션 빌드의 Rollup을 둘 다 Rolldown 하나로 교체했다. @vitejs/plugin-react v6는 내부의 Babel을 걷어내고 JSX 변환과 Fast Refresh를 Rust로 짠 Oxc에 맡겼다. React Compiler의 Rust 포트는 그보다 앞서 작년에 이미 Oxc에 머지돼 있었다. 이제 새로 만드는 JavaScript 프로젝트의 빌드 경로에서, 정작 JavaScript가 실행되는 구간은 거의 남지 않았다.

JavaScript가 밀려난 이유는 속도만이 아니다
"네이티브가 더 빠르니까"로 요약하면 절반만 맞다. 언어를 바꿔 얻은 몫도 있지만, 속도의 상당 부분은 공유 메모리 병렬성에서 왔다. 여러 스레드가 하나의 AST를 같이 읽으며 파일들을 동시에 체크하는 것, JavaScript에서는 이게 구조적으로 안 된다. 워커 간에 객체를 공유할 수 없어서 데이터를 넘길 때마다 복제 비용을 치러야 하기 때문이다.
JIT도 발목을 잡는다. JIT은 오래 도는 서버에서는 훌륭하지만, CLI 도구는 실행할 때마다 콜드 스타트다. 엔진이 코드를 최적화하기도 전에 작업이 끝난다. 수백만 개의 AST 노드를 만들고 버리는 작업은 가비지 컬렉터에도 최악의 워크로드다.
이 한계들은 10년 전에도 있었다. 달라진 건 입력의 크기다. 모노레포가 표준이 되고 코드베이스가 수십만 줄 단위로 커지면서, 도구의 성능은 있으면 좋은 게 아니라 없으면 안 되는 조건이 됐다. 78초짜리 타입 체크와 8초짜리 타입 체크는 같은 도구가 아니다. 전자는 커밋 전에 한 번 돌리는 검사기고, 후자는 저장할 때마다 도는 피드백 루프다.
셀프호스팅은 원래 무엇을 위한 것이었나
그런데 애초에 JavaScript 도구를 왜 JavaScript로 만들었을까. 자부심 때문만은 아니다. 실용적인 이유가 있었다. 사용자가 곧 기여자가 될 수 있다는 것. 웹팩 설정을 만지던 개발자가 로더의 버그를 직접 고치고, Babel 사용자가 자기 팀에 필요한 플러그인을 하루 만에 짜서 올리는 일이 가능했다. 수천 개에 달했던 Babel 플러그인 생태계는 도구의 언어와 사용자의 언어가 같았기 때문에 존재할 수 있었다.
무너진 건 성능이 아니라 이 전제다. 지금의 빌드 도구는 커뮤니티가 십시일반 굴리는 프로젝트가 아니라 풀타임 팀이 유지보수하는 제품이다. tsc는 Microsoft가, Vite와 Rolldown은 VoidZero가 만들고, 그 VoidZero는 지난 6월 Cloudflare에 인수됐다. 전담 팀이 있으면 "아무나 기여할 수 있는가"의 무게는 줄고, "수백만 사용자의 빌드가 몇 초냐"의 무게는 커진다. 언어를 고르는 기준 자체가 달라진 것이다.
대가는 이미 나타나고 있다
공짜는 아니다. 대가가 크게 세 가지로 나타나고 있다.
첫째, Babel 플러그인 생태계가 밀려나기 시작했다. Vite 8에서 React Compiler처럼 Babel에 기반한 도구를 쓰려면 이제 @rolldown/plugin-babel을 따로 붙여야 한다. Rust 파이프라인 옆에 JavaScript 변환기를 한 번 더 세우는 셈이라, 애써 얻은 속도를 도로 반납하게 된다.
둘째, 트레이드오프의 종류가 바뀌었다. Rolldown 팀은 작년 6월 Oxc에 있던 이 Rust 포트를 Rolldown 바이너리에 함께 넣으려다 막판에 뺐는데, 이유가 바이너리 크기였다. 28.7MB가 33.8MB로 18% 커지는 걸 모든 사용자에게 지울 수 없다는 판단이었다. npm 패키지 시절에는 존재하지 않던 종류의 고민이다. 도구가 바이너리 제품이 되면 고민의 종류도 달라진다.
셋째, 디버깅이 블랙박스가 됐다. 번들러가 이상하게 동작할 때 node_modules 안의 코드에 console.log를 찍어보던 시대는 끝났다. 프론트엔드 개발자가 자기 도구의 내부를 들여다볼 수 있는 능력은, 숫자로 안 잡힐 뿐 이미 줄었다.
그래서 뭘 봐야 하나
두 가지만 짚고 싶다.
첫째, JavaScript는 파이썬과 같은 처지가 됐고, 이건 생각보다 나쁜 소식이 아니다. 파이썬 사용자 대부분은 NumPy가 C와 포트란으로 짜여 있다는 사실에 아무 불편이 없다. 애플리케이션을 쓰는 언어와 그 밑의 인프라를 짓는 언어가 갈라지는 건, 생태계가 그만큼 커졌다는 뜻이기도 하다. 자기가 매일 쓰는 언어로 컴파일러까지 짤 수 있어야 한다는 요구는, 돌아보면 생태계가 작던 시절에나 성립하던 조건이었다.
둘째, 지켜봐야 할 것은 구현 언어가 아니라 개입 지점이 어디 남는가다. 셀프호스팅이 정말로 보장했던 건 "도구가 같은 언어로 짜여 있다"가 아니라 "사용자가 도구에 개입할 수 있다"였다. 전자는 수단이고 후자가 목적이다. Rolldown이 코어를 Rust로 옮기면서도 플러그인 API만은 JavaScript로 유지하는 데 공을 들이는 이유가 여기 있다. 코어는 넘겨주되 확장 지점은 남긴다는 타협이고, 이 경계선이 어디에 그어지느냐가 앞으로 몇 년간 각 도구의 성패를 가를 거다.
컴파일러의 언어가 바뀌는 동안 우리가 쓰는 언어는 그대로다. 잃은 것은 도구를 뜯어볼 권리의 일부이고, 얻은 것은 저장할 때마다 도는 8초짜리 피드백 루프다. 이 거래가 남는 장사인지는 각자의 작업에 따라 다르겠지만, 적어도 어느 쪽을 내주고 어느 쪽을 받았는지는 알고 있어야 한다.
참고 자료
- Anders Hejlsberg, A 10x Faster TypeScript (2025)
- Microsoft, Announcing TypeScript 7.0 (2026)
- VoidZero, Announcing Rolldown 1.0 (2026)