TypeScript 7로 넘어가기 전 짚어야 할 호환성 문제와 CI 최적화
٢٩ يوليو ٢٠٢٦
0
컴퓨터/소프트웨어Related Video
8:32TypeScript 7 공식 출시, 진짜 압도적으로 빠릅니다
Better Stack
Comments (0)
Log in to leave a comment
No posts yet
8:32Better Stack
Log in to leave a comment
No posts yet
컴파일러를 Go로 다시 짜서 만든 TypeScript 7이 나왔다. 타입 검사 속도가 최대 12배 빨라졌다는 소식에 대규모 모노레포를 운영하는 팀들은 당장이라도 바꾸고 싶을 것이다. 실제로 Vanta나 VS Code 엔지니어링 팀은 CI 파이프라인 빌드 시간을 80% 이상 줄였다고 밝혔다.
하지만 무작정 적용했다간 CI가 통째로 터진다. 네이티브 바이너리로 바뀌면서 기존 정적 분석 API 지원을 후속 버전으로 미뤘기 때문이다. @typescript-eslint나 ts-morph처럼 require('typescript')로 내부 API를 들여다보던 도구들이 일제히 먹통이 된다. 성능은 매력적이지만 도구 사슬이 깨지는 건 다른 문제다. 배포 중단을 막으면서 속도 이점만 챙기려면 몇 가지 우회책이 필요하다.
TypeScript 7.0의 tsc 바이너리는 Node.js 내부 모듈 호출을 완전히 막는다. 기존처럼 JS 환경에서 임포트해 쓰던 패키지들이 빌드 시점에 즉시 에러를 낸다. 준비 없이 버전만 올렸다간 전체 CI 파이프라인이 마비된다.
파이프라인 최상단에 진단 스크립트를 걸어두는 편이 안전하다. 워크스페이스 내 모든 package.json과 tsconfig.json을 훑어서 TS 7에서 거부하는 옵션이나 패키지를 찾아내는 방식이다. ignoreDeprecations나 target: es5 같은 구버전 설정이 남아있다면 즉시 에러를 뱉고 프로세스를 멈추게 만든다.
// scripts/check-ts7-compatibility.mjs
import fs from 'node:fs';
import { globSync } from 'glob';
const INCOMPATIBLE_DEPS = [
'ts-morph',
'ts-node',
'@babel/plugin-transform-typescript',
'typescript-eslint',
'@typescript-eslint/parser'
];
const DEPRECATED_TSCONFIG_OPTIONS = ['target:es5', 'moduleResolution:node', 'baseUrl', 'ignoreDeprecations'];
function runDiagnostics() {
console.log('TypeScript 7 호환성 사전 진단 시작...');
let hasError = false;
const packageFiles = globSync('<strong>/package.json', { ignore: '</strong>/node_modules/<strong>' });
for (const file of packageFiles) {
const content = JSON.parse(fs.readFileSync(file, 'utf8'));
const allDeps = { ...content.dependencies, ...content.devDependencies };
for (const dep of INCOMPATIBLE_DEPS) {
if (allDeps[dep]) {
console.warn(`[의존성 경고] ${file}: '${dep}' 패키지는 TS7 네이티브 API와 호환되지 않습니다.`);
hasError = true;
}
}
}
const tsconfigFiles = globSync('</strong>/tsconfig*.json', { ignore: '**/node_modules/**' });
for (const file of tsconfigFiles) {
const rawContent = fs.readFileSync(file, 'utf8');
for (const opt of DEPRECATED_TSCONFIG_OPTIONS) {
if (rawContent.includes(opt)) {
console.error(`[설정 오류] ${file}: 무효화된 옵션 발견 -> '${opt}'`);
hasError = true;
}
}
}
if (hasError) process.exit(1);
}
runDiagnostics();
ts.createProgram()이나 ts.transform()에 의존하는 커스텀 AST 트랜스포머도 작동하지 않는다. TS 7.0은 외부 JS 플러그인 주입을 받지 않는다. 이런 로직은 SWC나 Babel 같은 Rust/C++ 바인딩 모듈로 바꾸거나, 컴파일 단계 밖으로 떼어내야 한다.
| 컴파일러 옵션 | TypeScript 6.0 동작 | TypeScript 7.0 동작 | 대응 방법 |
|---|---|---|---|
target |
es5 사용 시 경고 |
Hard Error (컴파일 중단) | es2022 이상으로 변경 |
moduleResolution |
node 설정 허용 |
Hard Error | bundler 또는 node16으로 변경 |
baseUrl |
단독 사용 허용 | Hard Error | compilerOptions.paths 단독 사용으로 전환 |
ignoreDeprecations |
경고 무시 작동 | 옵션 무효화 및 에러 | 해당 설정 완전 삭제 |
strict |
기본값 false |
기본값 true |
tsconfig.json 내 명시적 값 설정 |
TypeScript 7은 Go의 스레딩 모델을 활용한다. 타입 검사를 병렬로 처리하는 --checkers와 프로젝트 레퍼런스 빌드를 제어하는 --builders 옵션을 새로 제공한다. Slack 엔지니어링 팀은 이 옵션을 조합해 타입 체킹 시간을 20분에서 4.5분으로 단축했다. 하지만 코어 수에 맞지 않게 스레드를 과하게 잡으면 CPU 컨텍스트 스위칭 오버헤드가 커지고 OOM(Out of Memory)으로 빌드가 터진다.
러너 스펙에 맞춰 아래 공식으로 스레드를 잡는 편이 좋다. 가상 머신의 할당 코어 수를 , 총 메모리를 , OS 예약 메모리를 (), 워커당 평균 메모리 점유량을 ()라고 할 때 단일 프로젝트 최적 스레드 수 는 다음과 같이 나온다.
전체 스레드 합계인 가 를 넘지 않아야 한다. 예를 들어 8 vCPU / 16GB 러너 환경이라면 --checkers 4, --builders 2 설정이 적당하다.
name: Monorepo Parallel Typecheck
on:
push:
branches: [main]
jobs:
typecheck:
runs-on: ubuntu-latest-8-core
steps:
- name: Checkout Codebase
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Purge Legacy TS 6.0 Cache
run: find . -name "*.tsbuildinfo" -not -path "*/node_modules/*" -delete
- name: Execute TS 7 Parallel Check
run: npx tsc --build --checkers 4 --builders 2 --verbose
주의할 점이 있다. TS 7의 증분 컴파일 엔진은 기존 TS 6의 .tsbuildinfo 캐시 파일과 포맷이 안 맞는다. 빌드 전에 find . -name "*.tsbuildinfo" -delete로 구버전 캐시를 지우지 않으면 세그멘테이션 오류가 난다.
에디터용 언어 서버도 Go 바이너리 기반 LSP로 개편되었다. AWS CodeBuild 테스트 결과에 따르면 대규모 모노레포에서 파일을 열었을 때 첫 타입 에러가 뜨기까지 걸리는 시간이 17.5초에서 1.3초로 줄었다.
팀원들의 VS Code 환경을 통일하려면 모노레포 루트의 .vscode/settings.json에 아래 설정을 넣으면 된다.
{
"typescript.tsdk": "node_modules/typescript/lib",
"js/ts.experimental.useTsgo": true,
"typescript.enablePromptUseWorkspaceTsdk": true,
"typescript.preferences.preferTypeOnlyAutoImports": true,
"files.associations": {
"*.tsbuildinfo": "json"
}
}
Vue나 Svelte 같은 템플릿 언어 플러그인과 충돌이 나서 구문 분석이 죽는 경우가 가끔 생긴다. 이럴 땐 명령 팔레트(Ctrl+Shift+P)에서 TypeScript: Select TypeScript Version...을 눌러 기존 TS 6.0 엔진으로 돌려놓고 작업하면 된다.
프로젝트에 레거시 패키지가 엉켜 있어 당장 ESLint를 TS 7 환경으로 못 올린다면, 컴파일러를 두 개 같이 쓰는 듀얼 엔진 구성으로 우회할 수 있다. 정적 분석 도구에는 TS 6 API를 연결하고, 실제 타입 검사와 빌드만 TS 7 네이티브 바이너리에 맡기는 식이다.
package.json에서 패키지 별칭(Alias)을 지정해 두 컴파일러를 동시에 설치한다.
{
"name": "monorepo-root",
"private": true,
"devDependencies": {
"typescript": "npm:@typescript/typescript6@^6.0.2",
"@typescript/native": "npm:typescript@^7.0.2",
"eslint": "^9.0.0",
"typescript-eslint": "^8.0.0"
},
"scripts": {
"typecheck": "ts-native --build",
"typecheck:legacy": "tsc6 --noEmit",
"lint": "eslint ."
}
}
이 상태에서 CI에 섀도우 빌드(Shadow Build) 스크립트를 붙여두면 안전하다. 기존 TS 6의 진단 결과와 TS 7의 진단 결과를 diff로 비교해서, 템플릿 리터럴 타입이나 조건부 타입 추론에서 결과가 달라지는지 모니터링할 수 있다.
#!/usr/bin/env bash
# scripts/ci-shadow-build.sh
set -e
echo "=== 1. 기존 TS 6.0 컴파일러 진단 출력 생성 ==="
npx tsc6 --noEmit --pretty false > ./ts6-baseline.log 2>&1 || true
echo "=== 2. 신규 TS 7.0 네이티브 컴파일러 진단 출력 생성 ==="
npx --package @typescript/native tsc --noEmit --pretty false > ./ts7-output.log 2>&1 || true
echo "=== 3. 진단 결과 Diff 대조 검증 ==="
DIFF_RESULT=$(diff ./ts6-baseline.log ./ts7-output.log || true)
if [ -z "$DIFF_RESULT" ]; then
echo "TS6와 TS7의 컴파일 진단 결과가 정확히 일치합니다."
exit 0
else
echo "진단 결과 간 차이점이 발견되었습니다:"
echo "$DIFF_RESULT"
exit 0
fi
도구 사슬 호환성 문제가 정리될 때까지는 린트와 타입체크 역할을 분리해 운용하는 편이 현실적이다. 사전 진단 스크립트로 터질 만한 옵션을 정리하고, CI 인프라 스펙에 맞게 스레드를 조율하면 배포 파이프라인이 멈추는 불상사 없이 속도 개선 효과를 볼 수 있다.