🔥 TypeScript 7.0 베타: tsc가 Go로 환생했다

#typescript#tsgo#compiler#performance#release
1051자
13분

TypeScript 로고 옆에 Go gopher가 서 있는 개념 일러스트

어젯밤에 피드를 넘기다가 Microsoft TypeScript 블로그에서 멈췄다. 제목에 "7.0 Beta"가 박혀 있었고, 본문 맨 위 설치 커맨드가 @typescript/native-preview@beta였다. 패키지 이름이 모든 걸 말해준다. 이번엔 진짜다.

TypeScript 팀이 2025년 3월에 예고했던 Go 포팅이, 약 13개월 만인 2026년 4월 21일 베타로 나왔다. 작성자는 Daniel Rosenwasser. 이전까지 네이티브 프리뷰 너이틀리(nightly)로 돌던 바이너리가 이제 beta 태그를 달고 정식 릴리스 트랙에 진입했다.

tsgo를 5분 안에 써보기

베타는 기존 tsc와 공존한다. 바이너리 이름 자체가 다르다.

bash
npm install -D @typescript/native-preview@beta
npx tsgo --version
# Version 7.0.0-beta
bash
npm install -D @typescript/native-preview@beta
npx tsgo --version
# Version 7.0.0-beta

tsgo가 Go로 짠 새 컴파일러다. 안정화되면 이름이 다시 tsc로 바뀌고 패키지는 typescript로 돌아간다. 지금은 과도기라서 두 이름을 일부러 분리해 둔 구조다.

처음 돌려보고 눈에 띈 건 병렬 처리 플래그다. 새 컴파일러는 타입 체킹을 여러 워커로 쪼개서 돌린다.

  • --checkers: 타입 체킹 워커 수, 기본 4
  • --builders: 프로젝트 레퍼런스(모노레포)를 동시에 빌드할 개수
  • --singleThreaded: 디버깅이나 비교 벤치용 강제 단일 스레드

CPU 코어가 많은 CI 머신이면 --checkers를 올려 더 빠르게 돌릴 수 있고, 반대로 메모리가 빠듯한 노트북에서는 낮춰야 한다. 다만 공식 문서가 친절하게 경고하듯, 워커 수를 늘리는 건 메모리와 맞바꾸는 선택이다. 그리고 --builders는 결과물에 영향을 주지 않지만 --checkers는 병렬화 특성상 내부 객체 정렬에 개입하기 때문에, 출력 안정성을 위해 stableTypeOrdering이 7.0에서 강제로 켜져 있다. 끌 수 없다.

속도 숫자, 그리고 헤지

공식 블로그는 "often about 10 times faster"라는 표현을 쓴다. 이 "often"이 중요하다.

Microsoft가 네이티브 포트 발표에서 공개한 원본 수치는 이렇다. VS Code 본체가 77.8초에서 7.5초로, 10배 남짓. 작은 라이브러리인 rxjs(2.1k LOC)가 11.0배, 중간 크기의 tRPC(18k LOC)가 9.1배. 12월 프로그레스 업데이트에서는 Sentry가 133.08초에서 16.25초로 떨어졌다. 메모리는 대체로 절반 수준까지 준다.

숫자만 보면 황홀한데, 프로젝트마다 편차가 크다는 점은 짚고 가야 한다. rxjs처럼 작은 코드베이스에서도 11배가 나오는가 하면, 구조상 타입 추론이 지배적인 프로젝트에서는 그만큼 떨어지지 않을 수도 있다. 커뮤니티 벤치 해설이 강조하듯, 벤치 수치를 그대로 내 레포에 복사해서 기대치로 삼지 말고, 일단 tsgo --singleThreaded로 싱글 스레드 기준 속도부터 재 보는 게 낫다.

디폴트가 바뀌었다: 조용한 파괴력

속도 이야기에 가려지기 쉽지만, 실제로 내 코드베이스를 부수는 건 컴파일러 아키텍처가 아니라 바뀐 기본값이다. 7.0에서 달라진 주요 디폴트는 이렇다.

  • strict가 기본 true
  • module이 기본 esnext
  • types가 기본 []. @types/*를 자동으로 모두 끌어오던 동작이 사라졌다. 필요한 패키지는 명시해야 한다
  • noUncheckedSideEffectImports가 기본 true
  • libReplacement가 기본 false
  • stableTypeOrdering이 강제 true

그리고 아예 제거된 것들.

  • target: es5downlevelIteration. 최저 타깃은 es2015로 올라갔다
  • moduleResolution: node, node10, classic. nodenextbundler로 옮겨야 한다
  • module: amd, umd, systemjs, none
  • baseUrl. 경로 매핑은 paths로 통일
  • esModuleInteropallowSyntheticDefaultImportsfalse로 끄는 옵션
  • 네임스페이스 선언에서 module 키워드
  • JSDoc 일부. @enum, @class, 포스트픽스 !는 더 이상 인식되지 않는다

JSDoc으로 타입을 붙여 쓰던 JS 프로젝트는 7.0에서 오류가 늘어날 가능성이 있다. @enum@constructor는 7.0이 아예 모른다. JS 베이스 라이브러리를 유지보수하는 사람이라면 이쪽이 더 아플 수 있다.

공존 전략: tsc6로 버티기

이런 파괴력을 감안해, 팀은 영리한 탈출구를 하나 남겨놨다.

@typescript/typescript6 패키지를 설치하면 tsc6 바이너리를 그대로 쓸 수 있다. 기존 typescript의 자리에 6.0을 끼워 넣는 npm 별칭도 허용된다.

json
{
  "devDependencies": {
    "typescript": "npm:@typescript/typescript6@^6.0.0",
    "@typescript/native-preview": "beta"
  }
}
json
{
  "devDependencies": {
    "typescript": "npm:@typescript/typescript6@^6.0.0",
    "@typescript/native-preview": "beta"
  }
}

이렇게 두면 한쪽에선 CI가 6.0으로 안정 빌드를 돌리고, 다른 쪽에선 개발자가 tsgo로 로컬 타입 체킹을 빠르게 맛볼 수 있다. 마이그레이션을 한 번의 PR로 밀어붙이는 대신, 같은 레포 안에서 두 컴파일러를 나란히 돌려놓고 디프를 지우는 식의 접근이 가능하다.

베타가 정직하게 말하는 한계

Rosenwasser는 발표문에서 한계도 가지런히 적어뒀다. 이 부분이 좋았다. 릴리스 노트에서 흔히 뭉개는 항목들을 그대로 써 놓는 글에는 믿음이 간다.

  • --watch 모드가 아직 최적화 전. 대규모 프로젝트에서 워치가 충분히 빠르지 않을 수 있다고 공식 로드맵이 밝혀 뒀다
  • Strada API(구 프로그래매틱 API) 미지원. 기존 컴파일러 API에 의존하던 툴체인(빌드 플러그인, 타입 서버 래퍼 등)은 7.0에서 바로 못 돈다. 프로그래매틱 API는 7.1 이후로 밀렸다
  • JS 원본에서의 .d.ts 에밋이 아직 구현 중

에디터 쪽은 12월 프로그레스 업데이트에서 auto-imports, find-all-references, rename이 다시 구현됐다고 했지만, 개별 사용자 리포트(예: 이슈 2555)에서 보이듯 모든 시나리오가 완벽히 돌아가는 건 아니다. 글로벌로 깔지 말고, 실험용 폴더 안에서 로컬 devDependency로만 만지는 걸 추천한다.

왜 Go였나: Hejlsberg와 Cavanaugh의 선택

2025년 3월에 Anders Hejlsberg가 "Project Corsa"라는 이름으로 이 작업을 처음 공개했을 때, 커뮤니티의 첫 질문은 "왜 Rust 아니고 Go냐"였다. 팀이 typescript-go 공식 디스커션 #411에서 내놓은 답을 읽으면서 가장 솔직하다고 느꼈다.

요지는 이렇다. 기존 TypeScript 컴파일러(코드명 Strada)와 구조적으로 가장 가까운 언어를 골라 "라인 단위 포팅"이 가능해야 했다. Rust로 처음부터 새로 쓰면 타입 체킹 시맨틱이 미묘하게 어긋날 위험이 있고, 그렇게 나온 컴파일러는 기존 TypeScript와 호환되지 않는 다른 언어가 된다. Go는 GC 지원, 메모리 레이아웃 제어, 그래프 처리 친화적인 런타임 덕분에 기존 JS 코드의 구조와 시맨틱을 거의 그대로 옮겨올 수 있었다.

이 말이 중요한 건, 7.0이 "다시 쓴 TypeScript"가 아니라 "같은 TypeScript를 Go로 옮긴 것"임을 보여주기 때문이다. 실제로 2ality의 분석공식 네이티브 포트 글이 강조하는 지점도 같다. 타입 체킹 로직은 6.0과 구조적으로 동일하다.

이 선택은 "호환성을 포기하고 이상적 언어로 다시 쓰는 것"보다 "현실적인 속도로 현실적인 호환성을 얻는 것"을 우선했다는 뜻이다. 대규모 채택을 노리는 컴파일러 프로젝트의 교과서적인 결정처럼 보인다.

내가 지금 당장 갈아타지 않는 이유

나는 아직 프로덕션 레포를 7.0 베타로 넘기지 않을 생각이다. 이유는 셋이다.

첫째, Strada API 부재. 내가 돌리는 빌드 파이프라인 중 일부는 TypeScript 프로그래매틱 API에 의존한다. 이 API가 7.1 이후로 밀렸기 때문에, 해당 툴체인들은 그전까지 6.0 트랙에 발을 걸치고 있어야 한다.

둘째, 기본값 변화. types: []가 디폴트가 된 것과 baseUrl 제거만 해도 내 모노레포는 한참 깨질 것이다. 이 마이그레이션은 충분한 시간을 잡고 별도 PR로 나눠서 처리하고 싶다.

셋째, 벤치 전에 단정할 수 없다. 공식 표에서도 프로젝트마다 배율이 다르다. 내 레포에서 실제로 어느 정도 빨라지는지 싱글 스레드 기준부터 재 보기 전까지는, "10배 빨라진다"라는 기대치를 CI 설계에 녹이고 싶지 않다.

대신 사이드 프로젝트 하나를 골라 tsgo를 로컬에서만 돌려볼 생각이다. 체감 차이를 보고, 베타 이슈 트래커에 리포트할 거리를 찾고, 안정판이 나올 때까지 @typescript/typescript6을 나란히 깔아둔 채 관찰하는 식이다. 다음 몇 주 안에 릴리스 후보(RC)가 올 거고, 2개월 안쪽에 안정판이 나올 예정이다. 그때쯤 Strada API와 .d.ts 에밋이 어디까지 복구되는지가 승부처가 된다.

그날까지, 일단 tsc6을 챙기자.

참고 자료

YouTube 영상

채널 보기
직교성과 벡터 투영 | 선형대수학
AI는 왜 수백 차원의 벡터를 사용할까? 고차원 공간과 행렬 | 선형대수학
행렬의 기본 연산 - 행렬 덧셈, 스칼라 곱, 전치 | 선형대수학
Trie 자료구조 파이썬 구현: Search와 Starts With 연산 | Trie 자료구조 이야기
행렬의 가장 중요한 연산 - 행렬 곱셈 | 선형대수학
투영과 예측, 그리고 선형 결합 | 선형대수학
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학