TypeScript 7.0 beta 적용 및 성능 비교
Microsoft가 TypeScript 7.0 beta를 공개했습니다. Go로 포팅한 네이티브 컴파일러고 "약 10배 빠르다"는 게 광고 문구입니다. 그 숫자가 실제 코드베이스에서도 나오는지, 따라붙는 호환성 비용은 뭔지 직접 돌려봤습니다.
대상 프로젝트는 React 19 + Vite 5로 굴리는 SPA고 .ts/.tsx 파일은 604개, composite와 project references 구조입니다. 측정 환경은 Apple M4 10코어, pnpm 10.11입니다.
설치 - 기존 5.x와 병렬 운영
7.0 beta는 정식 typescript 패키지가 아니라 별도 패키지로 풀렸습니다.
pnpm add -D @typescript/native-preview@beta
CLI는 tsgo로 깔립니다. 정식 7.0 GA가 나오면 typescript 패키지로 다시 합쳐지고 CLI도 tsc로 돌아갈 예정입니다. 그때까진 기존 typescript@5.8.3은 그대로 두고 tsgo만 옆에 깔아 비교하는 게 가장 안전한 선택입니다.
호환성 — 무엇이 깨졌나
tsconfig를 손 안 대고 tsgo로 그냥 돌리면 컴파일러 옵션 에러 두 개가 떨어집니다.
tsconfig.app.json(26,5): error TS5102: Option 'baseUrl' has been removed.
Use '"paths": {"*": ["./src/*"]}' instead.
tsconfig.app.json(28,15): error TS5090: Non-relative paths are not allowed.
Did you forget a leading './'?
TS 7은 baseUrl을 깔끔하게 빼버렸습니다. paths의 non-relative 매핑도 함께 막혔습니다. 즉
// Before (TS 5)
"baseUrl": "src",
"paths": { "@/*": ["*"] }
// After (TS 7)
"paths": { "@/*": ["./src/*"] }
이 변경은 TS 5에서도 그대로 동작합니다. 깨질 게 없는 정리 작업입니다.
다만 코드 레벨에서 한 군데 걸렸습니다. baseUrl: "src"에 기대고 있던 bare specifier 임포트가 딱 한 곳 남아 있었습니다.
// src/components/Tooltip/Tooltip.tsx
import Button from 'components/Button/Button'; // ❌ TS 7에서 해결 불가
다른 곳은 모두 @/components/Button/Button 형태 alias로 통일돼 있는데 이 파일만 옛날 방식이 살아 있었습니다. baseUrl이 빠지면서 그동안 가려져 있던 화석이 튀어나온 셈입니다.
원본 코드는 그대로 두고 비교만 돌릴 거라서 tsgo 전용 tsconfig 쪽에 임시 paths 매핑을 추가했습니다.
"paths": {
"@/*": ["./src/*"],
"components/*": ["./src/components/*"] // 임시 우회
}
그 외엔 다 작동했습니다.
| 항목 | 결과 |
|---|---|
composite + project references (-b) |
✅ |
moduleResolution: "bundler" |
✅ |
target: ES2021, jsx: "react-jsx", strict |
✅ |
isolatedModules, allowImportingTsExtensions |
✅ |
| 전체 src 타입체크 (604 파일) | ✅ 0 error |
| test config (vitest/jest-dom 타입 포함) | ✅ 0 error |
tsc -b → tsgo -b 교체 후 vite build 파이프라인 |
✅ |
성능 — 광고가 거짓은 아니지만
3회 평균. Cold = node_modules/.tmp (buildinfo) 삭제 후입니다.
| 작업 | TSC 5.8.3 | tsgo 7.0-dev | 배수 |
|---|---|---|---|
tsc -b --force (cold) |
3.39s | 0.71s | 4.8× |
tsc -b (warm, no change) |
0.20s | 0.16s | 1.3× |
test types (cold, --noEmit) |
2.61s | 0.53s | 4.9× |
풀 build (tsc -b && vite build) |
10.81s | ~4.4s | 2.5× |
광고에 적힌 "10배"는 이 규모에선 안 나왔습니다. 5배 정도. 코드베이스가 크고 타입 인스턴스화가 무거울수록 차이가 벌어지는 구조 같습니다. 테스트를 적용해본 프로젝트는 604개 파일이고 라이브러리 타입까지 합쳐도 200K 라인 수준이라 컴파일러 자체의 오버헤드가 작은 편입니다.
--extendedDiagnostics를 비교해 보면 재미있는 게 하나 있습니다.
| 메트릭 | TSC 5.8.3 | tsgo 7.0-dev |
|---|---|---|
| Total | 2.31s | 0.55s |
| Check | 1.74s | 0.29s |
| Memory | 431 MB | 388 MB |
| Types | 94,539 | 205,998 |
Types 카운트가 두 배 이상 차이 납니다. tsgo는 더 많은 타입을 인스턴스화하면서도 메모리는 덜 쓰고 시간은 1/6 수준입니다. Go 런타임의 메모리 공유 병렬화와 GC 특성이 그대로 보입니다. V8 단일 스레드 위에서 돌던 tsc와는 자원 모델 자체가 다릅니다.
병렬화 옵션 --checkers 스케일링도 같이 봤습니다.
--checkers |
Cold time |
|---|---|
| 1 | 1.04s |
| 4 (기본) | 0.68s |
| 8 | 0.71s |
--singleThreaded |
1.11s |
M4 10코어에선 4가 sweet spot입니다. 8을 줘도 saturation에 걸려 의미 있는 개선은 안 보입니다. 이 정도 규모에선 4 워커가 분산할 만큼의 작업량이 애초에 안 나옵니다.
어디에 가장 효과가 있나
이번 측정에서 가장 의미 있는 숫자는 풀 빌드 파이프라인의 **2.5×**입니다. 4.8×처럼 화려하진 않지만 CI에서 실제로 체감되는 건 이쪽입니다. 타입체크는 풀 빌드의 일부일 뿐이고 vite의 esbuild 단계는 어차피 빠르니까요. CI 빌드 시간을 좌우하는 부분은:
PR마다 도는 type-check — 5×. 가장 직접적인 개선
Docker 이미지 빌드의
tsc -b단계 — 풀 빌드 시간이 60% 줄어듭니다워치 모드의 cold restart — IDE 재시작이나 클린 빌드 직후 기다리는 시간이 짧아집니다
반대로 warm incremental은 차이가 거의 없습니다 (0.20s → 0.16s). .tsbuildinfo가 캐시되어 있으면 tsgo 쪽도 더 줄일 여지 자체가 작습니다. 일상 dev 루프에서 느끼는 차이는 생각보다 미미할 수 있다는 얘기입니다.
도입할 것인가
결론은 보류. 이유가 몇 가지 있습니다.
베타입니다. 안정 프로그래밍 API는 7.1 이후로 미뤄져 있고 에디터 쪽 의미론적 강조나 임포트 관리도 일부 미완성입니다.
@typescript-eslint가 아직 TS 5 기반입니다. ESLint 파이프라인은 베이스라인을 따라가니까, 컴파일러만 7로 올리면 결국 두 컴파일러를 동시에 들고 가야 합니다.현재 빌드가 이미 충분히 빠릅니다. 풀 빌드 10.8s를 4.4s로 줄여도 우리 워크플로의 병목은 거기가 아닙니다.
다만 준비는 해두는 게 맞습니다. GA 시점에 무리 없이 넘어가려면 지금부터 할 수 있는 작업이 있습니다.
tsconfig.app.json의baseUrl제거,paths를{ "@/*": ["./src/*"] }로 변경 (TS 5 호환)Tooltip.tsx의 bare specifier 임포트를@/components/...로 통일CI 빌드 시간 추적 — GA 직후 효과 측정이 가능하도록 베이스라인 기록
typescript-eslint가 도대체 무슨 일을 하길래
여기서 자연스럽게 따라붙는 의문이 있습니다. "린트 패키지 하나 때문에 컴파일러를 못 올리는 건가?" 그래서 typescript-eslint가 우리 파이프라인에서 실제로 뭘 하는지부터 정리해 봅니다.
ESLint 자체는 JS 파서로 코드를 토큰 단위로 봅니다. 타입 정보가 없습니다. 그래서 다음 같은 룰은 순수 ESLint로는 불가능합니다.
@typescript-eslint/no-floating-promises— Promise를 await/then 없이 떨궜는지 검출@typescript-eslint/no-misused-promises—if (asyncFn())처럼 Promise를 boolean 자리에 쓰는지@typescript-eslint/no-unsafe-*—any가 흘러다니는 경로 추적@typescript-eslint/await-thenable— Promise 아닌 값에 await 거는지@typescript-eslint/strict-boolean-expressions—if (str)처럼 nullable 문자열을 boolean으로 쓰는지
이런 type-aware 룰들은 typescript-eslint가 내부적으로 TypeScript 컴파일러 API(createProgram, getTypeChecker)를 호출해서 타입 정보를 얻은 다음 AST 위에서 판단합니다. 즉 typescript-eslint는 "TS 문법을 ESLint가 읽게 해주는 어댑터"가 아니라 TypeScript 컴파일러를 ESLint 안에서 호스팅하는 레이어입니다.
TS 7이 왜 문제냐면
tsgo는 Go로 다시 짜서 Node 프로세스 안의 동기 함수가 아니라 WASM/네이티브 바인딩으로 노출될 가능성이 큽니다. 그러면 컴파일러 API 호출이 비동기가 됩니다. 그런데 ESLint 코어는 아직 rule.create()가 동기 함수라는 전제로 굴러갑니다. 룰 안에서 await checker.getTypeAtLocation(...)을 못 쓴다는 얘기입니다. 이걸 풀려면 ESLint 내부 아키텍처 자체가 async 파서/룰을 지원해야 합니다. typescript-eslint 한 팀이 손쓸 수 있는 범위 밖입니다.
그럼 TS 7을 못 쓰는 건가? 아닙니다
여기가 핵심입니다. 컴파일러와 린터를 분리해서 운영하면 됩니다.
[빌드/타입체크] tsgo (TS 7) ← 빠름
[린트] typescript-eslint + typescript@5/6 ← 기존대로
이렇게 깔면:
pnpm build,pnpm type-check는 tsgo로 → 5배 빠름pnpm lint는 기존 typescript-eslint + TS 5/6 그대로 → 영향 없음node_modules에
typescript(5/6)와@typescript/native-preview(7)가 공존
실제로 정식 7.0 GA에선 이 시나리오를 위해 @typescript/typescript6 호환성 패키지를 따로 풀 예정입니다. tsgo 쓰면서도 typescript-eslint가 import할 TS 5/6 컴파일러를 옆에 깔아둘 수 있습니다.
그러면 왜 보류했냐
기술적으로 못 쓰는 게 아니라 운영 비용이 늘어나기 때문입니다.
두 컴파일러 동기화 부담: tsgo가 통과시키는 코드를 typescript-eslint(TS 5)가 다른 룰로 잡거나, 반대 상황이 생길 수 있습니다. 7.0의 새 기본값(
strict,noUncheckedSideEffectImports등)과 5.x 동작이 미세하게 어긋나는 케이스가 PR마다 마찰이 됩니다.type-aware 룰의 성능 이점이 안 살아남: tsgo가 빨라봤자 린트가 여전히 TS 5 컴파일러로 타입 정보를 만들어내니까, CI에서 가장 무거운 단계인 lint는 그대로입니다. tsgo 도입 효과의 절반이 죽습니다.
베타 + 안정 API 부재: 7.1 이후 안정화될 동안 기다리면, 그 시점엔 typescript-eslint도 일부 호환을 마쳤을 가능성이 있습니다.
| 항목 | typescript-eslint 없으면 |
|---|---|
| TS 문법 ESLint 인식 | 안 됨 (다른 어댑터로 대체 가능, 예: Oxlint) |
| 타입 기반 룰 (no-floating-promises 등) | 사실상 불가능 |
| TS 7 컴파일 사용 | 가능 (린트와 분리만 하면) |
요약하면 typescript-eslint는 타입 안전성을 코드 리뷰 자동화로 끌어올리는 핵심 도구입니다. TS 7을 못 쓰게 막는 게 아니라, TS 7으로 갔을 때 효과가 반감되고 운영이 복잡해진다는 게 실제 보류 사유입니다. "기술적 호환성"이 아니라 "가성비" 의 문제입니다.
typescript-eslint는 언제 따라오나
그래서 typescript-eslint가 정확히 어디까지 와 있는지도 짚어봤습니다. 결론부터 쓰면 TS 6는 이미 따라왔고, TS 7은 아직입니다.
TS 6: 이미 지원 완료
2026-04 기준 @typescript-eslint/parser@8.59.0의 peer 범위는 typescript: ">=4.8.4 <6.1.0"입니다. TS 6.0이 이미 들어가 있다는 얘기입니다. 트래킹 이슈 #12123도 Closed 상태고, lib.d.ts 재생성과 target: es5 폐지에 따른 fixture 업데이트, JSX 닫기 태그 토큰화 변경, 새 기본 컴파일러 옵션 대응까지 모두 끝났습니다.
우리 프로젝트는 @typescript-eslint/*@^8.15.0을 쓰고 있어서 minor bump만 하면 끝입니다. TS 6로 가는 길에 린트 쪽 장벽은 거의 없다고 봐도 됩니다.
TS 7: 공식 ETA 없음, 그리고 구조적 블로커가 있다
핵심은 버전 호환이 아니라 API 모델이 달라진다는 데 있습니다. 위에서 짚은 async 이슈가 그대로 작용합니다.
tsgo는 WASM/네이티브 바인딩으로 노출될 가능성이 큽니다. 그러면 API가 비동기로 가게 됩니다.
ESLint는 async parser를 아직 지원하지 않습니다. 린터 쪽 인프라부터 손봐야 한다는 뜻입니다.
typescript-eslint 팀은 #10940에서 **"tsgolint로 예산을 옮길 계획 없다, 기존 typescript-eslint를 계속 발전시키겠다"**고 못을 박았습니다. 같은 팀이 두 갈래를 동시에 끌고 가진 않겠다는 얘기입니다.
대안 진영도 살펴보면:
tsgolint (typescript-eslint 팀이 만든 Go 기반 PoC) — 초기 단계, 적극적으로 개발되고 있지 않고 프로덕션엔 부적합
Oxlint — typescript-go 통합 type-aware 린팅 프리뷰 중. 다른 베팅입니다.
종합하면 정식 TS 7(tsgo) 지원은 빨라야 GA + 수개월, 현실적으론 더 길어질 수도 있습니다. 그 사이엔 컴파일은 tsgo, 린트는 TS 5/6으로 컴파일러를 둘 끌고 가는 모델이 일반적인 운영 형태가 될 것 같습니다.
마이그레이션 계획
이 정보까지 반영하면 단계가 좀 더 선명해집니다.
단기 (지금 ~ 정식 7.0 GA 전): TS 5.8.3 유지. tsconfig 정리(
baseUrl제거, paths 상대화)와Tooltip.tsx임포트 통일만 선반영합니다.중기 (TS 6.0 stable 안정화 후): typescript와 typescript-eslint를 6.x로 minor 업. 린트 깨짐은 거의 없고, CI 빌드 시간 변화를 같이 측정해 두면 됩니다.
장기 (TS 7 GA + typescript-eslint 7 호환 확정 후): 컴파일러 7.0 전환. 그전에 들어가면 "린트는 6, 컴파일은 7"이라는 이중 운영을 떠안게 되니까, 충분한 가치가 보일 때만 움직이는 게 맞습니다.
정리하면 TS 6는 자연스럽게 흘러가는 흐름이고 TS 7은 별도 의사결정 게이트가 필요합니다. 이번 측정은 그 게이트를 통과시킬 만큼 데이터가 모이는지 미리 확인해 보는 사전 작업으로 의미가 있습니다.
남는 질문
이 5× 격차가 코드베이스가 커질수록 더 벌어질지가 궁금합니다. 광고된 10×에 닿는 임계점이 어디쯤인지는 별도 측정이 필요할 듯합니다.
composite빌드 그래프가 깊은 monorepo에서--builders옵션이 얼마나 먹히는지. 이 프로젝트는 references가 2개뿐이라 측정이 안 됐습니다.정식 7.0이
typescript패키지로 들어왔을 때 npm 의존성 트리에 어떤 충돌이 생길지.@typescript/typescript6호환성 패키지를 쓰는 시나리오가 실제로 어떻게 정착될지도 두고 봐야겠습니다.
변경된 파일
package.json—@typescript/native-previewdevDep +type-check:tsgo,test:types:tsgo스크립트tsconfig.tsgo.{json,app.json,node.json,test.json}— 비교용 별도 설정 (원본 tsconfig 보존)기존 tsconfig 및 소스 코드는 무수정


