TypeScript 7.0 출시 — 왜 Rust가 아닌 Go를 선택했을까

Maru

@maru

TypeScript 7.0 출시 — 왜 Rust가 아닌 Go를 선택했을까

TypeScript 7.0 출시 — 왜 Rust가 아닌 Go를 선택했을까

TypeScript 7.0이 컴파일러와 언어 서비스를 Go 네이티브 바이너리로 포팅하며 공식 출시되었습니다. 기존 Node.js 엔진 기반에서 전환되면서 빌드 속도는 약 10배 빨라졌고 메모리 사용량은 절반으로 감소했습니다. 개발 도구 업계의 대세인 Rust 대신 Go를 선택한 기술적 배경과, 당장 프로덕션 환경에 도입할 때 주의해야 할 생태계 호환성 해결책을 정리합니다.

왜 Rust가 아니라 Go였을까? 순환 참조와 GC의 선택

최근 많은 개발 도구가 성능 향상을 위해 Rust로 재작성되고 있지만, 마이크로소프트의 TypeScript 7.0 개발 팀은 Go를 최종 선택했습니다. 이 결정의 핵심 배경에는 컴파일러의 핵심 데이터 구조인 추상 구문 트리(AST)의 특성이 있습니다. AST는 수많은 노드가 서로를 복잡하게 참조하는 순환 포인터 구조를 띱니다.

Rust의 엄격한 소유권 모델과 대여 검사기 환경에서 이러한 순환 참조 그래프를 관리하려면 Rc, Arc, RefCell 같은 포인터 래퍼로 코드를 복잡하게 감싸거나 unsafe 블록을 남용해야 합니다. 이는 기존 엔진인 스트라다(Strada)의 아키텍처를 그대로 이식하는 작업을 사실상 불가능하게 만들었습니다.

반면 가비지 컬렉션(GC)을 기본 지원하는 Go는 복잡한 메모리 토폴로지를 매끄럽게 처리할 수 있습니다. 덕분에 개발 팀은 복잡한 우회 코드 없이 기존 컴파일러의 버그 동작까지 완벽히 일치시키는 파일 단위의 안정적인 포팅 작업을 성공적으로 완료할 수 있었습니다.

10배 빠른 빌드 속도와 메모리 반감 효과

Go 언어로 완전히 새로 구현된 TypeScript 7.0의 성능 향상은 실측 수치로 고스란히 증명됩니다. Visual Studio Code 블로그에 따르면, 약 150만 라인에 달하는 대규모 코드베이스를 컴파일하는 데 걸리는 시간이 기존 125.7초에서 10.6초로 10배 이상 단축되었습니다. 빌드 과정에서의 메모리 점유율 또한 약 18% 가량 감소하여 개발 장비의 메모리 부담을 크게 덜어냈습니다.

이 극적인 속도 향상은 단순 빌드뿐만 아니라 일상적인 코드 작성 경험에도 고스란히 반영됩니다. 에디터 내에서 가장 빈번히 사용하는 선언 이동 기능의 반응 속도도 대폭 줄어들어 체감상 지연이 완전히 사라졌습니다. 자동 완성과 이름을 변경하는 리팩토링 역시 네이티브 백그라운드 스레드에서 즉각적으로 연산 처리됩니다.

이러한 병목 해결이 가능했던 핵심 이유는 Go의 네이티브 컴파일 바이너리 구조와 공유 메모리 기반의 멀티스레딩입니다. 싱글스레드 제약이 있던 기존 Node.js 엔진 환경에서 벗어나 멀티코어를 적극적으로 활용하면서 대규모 모노레포 환경에서도 막힘없는 퍼포먼스를 보여줍니다.

과도기의 그늘: 컴파일러 API 미지원과 호환성 해결책

TypeScript 7.0의 압도적인 성능 향상 뒤에는 생태계 전체가 겪어야 할 뼈아픈 과도기적 한계가 존재합니다. 바로 컴파일러 내부 코드를 호출할 수 있게 해주는 프로그래밍 방식 컴파일러 API가 이번 7.0 버전에서 완전히 누락된 점입니다. 이로 인해 typescript-eslint, ts-morph를 비롯해 Vue, Svelte, Astro 등 자체 컴파일러를 갖춘 도구 생태계가 정상 작동하지 않는 호환성 단절이 발생했습니다.

이 과도기적 문제를 극복하기 위해 공식 개발 팀은 TypeScript 7.1에서 안정적인 API가 복원되기 전까지 사용할 수 있는 우회 방안을 제시했습니다. 바로 npm 패키지 별칭 기능을 사용해 빌드 엔진과 주변 생태계가 참조하는 컴파일러 패키지를 다르게 매핑하는 멀티 설치 전략입니다.

이 해결책은 package.json 설정에 다음과 같이 패키지 별칭을 정의하는 방식으로 간단히 적용할 수 있습니다.

json
{
  "devDependencies": {
    "typescript": "npm:@typescript/typescript6@latest",
    "@typescript/native": "npm:typescript@latest",
}

이렇게 정의하면 기존 린터나 주변 개발 도구는 기존 호환 패키지인 @typescript/typescript6을 찾아가 안정적으로 구동됩니다. 반면 실제 프로덕션 빌드나 빠른 타입 체크 작업을 실행할 때는 Go 네이티브 기반의 새로운 @typescript/native 패키지를 명시적으로 호출함으로써 도구 생태계의 안정성과 압도적인 빌드 속도 혜택을 동시에 누릴 수 있습니다.

더욱 엄격해진 설정 기본값과 마이그레이션 대비

TypeScript 7.0은 과거의 유산을 과감히 정리하고 현대적인 개발 환경에 맞춘 엄격한 설정을 기본값으로 강제합니다. 오랫동안 하위 호환성을 위해 유지되던 target: es5, baseUrl, moduleResolution: node 설정이 완전히 제거되었습니다. 7.0 컴파일러는 이 설정들을 발견하면 즉시 강력한 에러를 발생시키므로, 기존 프로젝트를 업그레이드할 때 가장 먼저 tsconfig.json을 수정해야 합니다.

코드 안정성을 위한 제약도 한층 강화되었습니다. 이제 strict: true가 기본 동작으로 못박혔으며, 전역 타입을 자동으로 불러오던 types 옵션도 빈 배열이 기본값이 되어 의도치 않은 타입 오염을 원천 차단합니다. 제거된 기존 모듈 해석 방식을 대체하려면 최신 번들러 환경에 맞는 설정을 명시해야 합니다.

TypeScript 7.0 기준의 안정적인 최신 권장 설정은 다음과 같습니다.

json
{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "bundler",
    "strict": true
  }
}

대신 멀티스레드 기반의 네이티브 컴파일러를 효율적으로 다룰 수 있는 하드웨어 제어 옵션이 추가되었습니다. 병렬 타입 체크 스레드 개수를 지정하는 --checkers와 모노레포 프로젝트의 병렬 빌드를 제어하는 --builders 옵션을 제공하여 로컬 개발 장비나 CI 환경의 성능을 극대화할 수 있습니다. 빌드 파이프라인에서 원인을 알 수 없는 병렬 처리 오류가 발생할 때를 대비한 단일 스레드 디버깅용 --singleThreaded 옵션도 함께 도입되었습니다.

결론: 지금 바로 업그레이드해야 할까?

TypeScript 7.0은 Go 네이티브 컴파일러 도입을 통해 빌드 성능을 극적으로 끌어올린 기념비적인 릴리스입니다. 하지만 성능 향상의 달콤함 뒤에는 컴파일러 API 미지원이라는 생태계 격변이 도사리고 있습니다. 따라서 프로젝트의 성격과 빌드 병목의 심각성에 따라 도입 전략을 다르게 가져가야 합니다.

대규모 모노레포를 운영 중이거나 빌드 속도가 개발 생산성을 극도로 저해하는 상황이라면 호환 패키지를 활용한 마이그레이션을 적극적으로 검토해 볼 만합니다. 앞서 살펴본 패키지 별칭 설정으로 린터와 빌드 도구를 우회 연동하면 10배 빠른 컴파일 속도를 즉시 누릴 수 있습니다. 반면, 복잡한 커스텀 트랜스포머를 사용하거나 빌드 안정성이 최우선인 일반적인 서비스라면 무리하게 업그레이드를 서두를 필요가 없습니다.

가장 현명한 전략은 하위 호환성을 위해 제거된 구식 설정 항목들을 미리 정리하며 대비하는 것입니다. 생태계 도구들이 온전히 동작할 수 있도록 컴파일러 API가 다시 제공되는 7.1 버전의 출시를 기다리는 것도 좋습니다. 이번 7.0은 성능이라는 확실한 보상이 있는 만큼, 철저히 준비한다면 다가올 네이티브 툴체인 시대를 가장 빠르고 안전하게 맞이할 수 있을 것입니다.


참고 링크

(Edited)