[번역] TypeScript 6.0 RC 발표

Daniel Rosenwasser - 2026-03-06


오늘 우리는 TypeScript 6.0의 릴리스 후보(RC, Release Candidate) 버전을 발표하게 되어 매우 기쁩니다! RC 사용을 시작하려면 npm을 통해 다음 명령어를 입력하면 됩니다.
npm install -D typescript@rc
TypeScript 6.0은 현재의 JavaScript 코드베이스를 기반으로 하는 마지막 릴리스가 될 예정이라는 점에서 독특한 릴리스입니다. 작년에 발표한 바와 같이(최근 업데이트 참고), 우리는 네이티브 코드의 속도와 공유 메모리 멀티스레딩의 이점을 활용하기 위해 Go 언어로 작성된 TypeScript 컴파일러 및 언어 서비스의 새로운 코드베이스를 작업 중입니다. 이 새로운 코드베이스는 TypeScript 7.0 이상의 기반이 될 것입니다. TypeScript 6.0은 해당 릴리스의 직전 전조가 될 것이며, 여러 면에서 TypeScript 5.9와 7.0 사이의 가교 역할을 할 것입니다. 따라서 TypeScript 6.0의 대부분의 변경 사항은 TypeScript 7.0 도입을 위한 정렬 및 준비를 돕기 위한 것입니다.
그렇다고 해서 모든 기능이 정렬에만 치중된 것은 아닙니다. 이번 릴리스의 주요 하이라이트를 살펴본 후, 7.0에서 무엇이 바뀌는지와 어떻게 준비해야 하는지에 대해 자세히 알아보겠습니다.

Beta 이후 변경된 사항은 무엇인가요?

TypeScript 6.0 Beta 이후, 주로 TypeScript 7.0의 동작과 일치시키기 위해 몇 가지 주목할 만한 변경 사항을 적용했습니다.
한 가지 조정 사항은 제네릭(Generic) 호출에서의 함수 표현식, 특히 제네릭 JSX 표현식에서 발생하는 함수 표현식에 대한 타입 검사 방식입니다(이 풀 리퀘스트 참고). 이는 일반적으로 기존 코드의 버그를 더 많이 잡아내겠지만, 일부 제네릭 호출에서 명시적인 타입 인수가 필요할 수도 있습니다.
또한, import ... assert {...}와 같은 가져오기 단언(Import Assertion) 구문의 지원 중단을 import(..., { assert: {...}})와 같은 import() 호출로 확장했습니다.
마지막으로, 최신 웹 표준을 반영하여 DOM 타입을 업데이트했으며, Temporal API에 대한 일부 조정도 포함되었습니다.

this를 사용하지 않는 함수에서의 문맥 의존성 완화

매개변수에 명시적인 타입이 작성되지 않았을 때, TypeScript는 보통 기대되는 타입이나 동일한 함수 호출 내의 다른 인수를 통해 타입을 추론할 수 있습니다.
declare function callIt<T>(obj: {
produce: (x: number) => T,
consume: (y: T) => void,
}): void;

// 문제없이 작동합니다.
callIt({
produce: (x: number) => x * 2,
consume: y => y.toFixed(),
});

// 속성의 순서가 바뀌어도 문제없이 작동합니다.
callIt({
consume: y => y.toFixed(),
produce: (x: number) => x * 2,
});
여기서 TypeScript는 속성 순서와 상관없이 produce 함수에서 추론된 T를 기반으로 consume 함수의 y 타입을 추론할 수 있습니다. 하지만 이 함수들이 화살표 함수 구문 대신 메서드 구문으로 작성되었다면 어떨까요?
declare function callIt<T>(obj: {
produce: (x: number) => T,
consume: (y: T) => void,
}): void;

// 잘 작동하며, `x`는 number로 추론됩니다.
callIt({
produce(x: number) { return x * 2; },
consume(y) { return y.toFixed(); },
});

callIt({
consume(y) { return y.toFixed(); },
// ~
// 오류: 'y'의 타입이 'unknown'입니다.

produce(x: number) { return x * 2; },
});
이상하게도 callIt에 대한 두 번째 호출은 오류를 발생시킵니다. TypeScript가 consume 메서드에서 y의 타입을 추론하지 못하기 때문입니다. 여기서 일어나는 일은 TypeScript가 T의 후보를 찾으려 할 때, 매개변수에 명시적 타입이 없는 함수를 먼저 건너뛴다는 것입니다. 특정 함수들은 올바르게 검사되기 위해 추론된 T의 타입이 필요할 수 있기 때문입니다. 우리의 경우, consume 함수를 분석하기 위해 T의 타입을 알아야 합니다.
이러한 함수들을 *문맥 의존적 함수(Contextually Sensitive Functions)*라고 부릅니다. 기본적으로 명시적 타입이 없는 매개변수를 가진 함수들입니다. 결국 타입 시스템은 이러한 매개변수의 타입을 알아내야 하지만, 이는 제네릭 함수의 추론 방식과 약간 충돌합니다. 두 요소가 타입을 서로 다른 방향으로 "끌어당기기" 때문입니다.
function callFunc<T>(callback: (x: T) => void, value: T) {
return callback(value);
}

callFunc(x => x.toFixed(), 42);
// ^
// 여기서 `x`의 타입을 알아내야 하지만,
// 콜백을 검사하기 위해 `T`의 타입도 알아내야 합니다.
이를 해결하기 위해 TypeScript는 타입 인수 추론 중에 문맥 의존적 함수를 건너뛰고, 대신 다른 인수들을 먼저 검사하고 추론합니다. 문맥 의존적 함수를 건너뛰는 것이 효과가 없다면, 추론은 검사되지 않은 인수들을 인수 목록의 왼쪽에서 오른쪽 순서로 계속 진행합니다. 바로 위의 예시에서 TypeScript는 T에 대한 추론 중에 콜백을 건너뛰지만, 두 번째 인수 42를 보고 Tnumber라고 추론합니다. 그런 다음 다시 돌아와 콜백을 검사할 때 (x: number) => void라는 문맥적 타입을 갖게 되어 x 역시 number라고 추론할 수 있게 됩니다.
그렇다면 이전 예시에서는 무슨 일이 일어나고 있는 걸까요?
// 화살표 구문 - 오류 없음.
callIt({
consume: y => y.toFixed(),
produce: (x: number) => x * 2,
});

// 메서드 구문 - 오류 발생!
callIt({
consume(y) { return y.toFixed(); },
// ~
// 오류: 'y'의 타입이 'unknown'입니다.

produce(x: number) { return x * 2; },
});
두 예시 모두 produce에 명시적으로 타입이 지정된 x 매개변수를 가진 함수가 할당되었습니다. 동일하게 검사되어야 하지 않을까요?
문제는 미묘합니다. 메서드 구문을 사용하는 함수를 포함한 대부분의 함수는 암시적인 this 매개변수를 갖지만, 화살표 함수는 그렇지 않습니다. this를 사용하면 T의 타입을 "끌어오는" 것이 필요할 수 있습니다. 예를 들어, 포함하는 객체 리터럴의 타입을 아는 것이 결과적으로 T를 사용하는 consume의 타입을 요구할 수 있기 때문입니다.
하지만 우리는 this를 사용하지 않고 있습니다! 물론 함수가 런타임에 this 값을 가질 수는 있지만, 전혀 사용되지 않습니다!
TypeScript 6.0은 함수가 문맥 의존적인지 결정할 때 이 점을 고려합니다. 함수 내에서 this가 실제로 사용되지 않는다면, 해당 함수는 문맥 의존적인 것으로 간주되지 않습니다. 즉, 이러한 함수들은 타입 추론 시 더 높은 우선순위를 갖게 되며, 위의 모든 예시가 이제 정상적으로 작동합니다!
이 변경 사항Mateusz Burzyński의 작업 덕분에 제공되었습니다.

#/로 시작하는 서브패스 가져오기

Node.js에 모듈 지원이 추가되었을 때, "서브패스 가져오기(Subpath Imports)"라는 기능이 추가되었습니다. 이는 기본적으로 imports라는 필드를 통해 패키지 내부 모듈에 대한 내부 별칭(Alias)을 생성할 수 있게 해줍니다.
{
"name": "my-package",
"type": "module",
"imports": {
"#root": "./dist/index.js",
"#root/*": "./dist/*"
}
}
이를 통해 my-package 내부의 모듈은 ../../index.js와 같은 상대 경로를 사용하는 대신 #root에서 가져올 수 있으며, 기본적으로 다른 모듈이 다음과 같이 작성할 수 있게 해줍니다.
import * as utils from "#root/utils.js";
상대 경로를 사용하는 아래 방식 대신 말이죠.
import * as utils from "../../utils.js";
이 기능의 사소한 불편함은 개발자가 서브패스 가져오기를 지정할 때 # 뒤에 항상 무언가를 써야 했다는 점입니다. 여기서는 root를 사용했지만, ./dist/ 외에 매핑하는 디렉터리가 없는 경우에는 다소 불필요하게 느껴집니다.
번들러를 사용해 본 개발자들은 긴 상대 경로를 피하기 위해 경로 매핑(Path-mapping)을 사용하는 데 익숙합니다. 번들러의 익숙한 관례는 간단한 @/를 접두사로 사용하는 것이었습니다. 불행히도 서브패스 가져오기는 #/로 시작할 수 없었기에, 이를 프로젝트에 도입하려는 개발자들에게 많은 혼란을 주었습니다.
하지만 최근 Node.js에서 #/로 시작하는 서브패스 가져오기 지원을 추가했습니다. 이를 통해 패키지는 추가적인 세그먼트 없이 서브패스 가져오기에 간단한 #/ 접두사를 사용할 수 있습니다.
{
"name": "my-package",
"type": "module",
"imports": {
"#": "./dist/index.js",
"#/*": "./dist/*"
}
}
이는 최신 Node.js 20 릴리스에서 지원되며, 이에 따라 TypeScript도 --moduleResolution 설정의 node20, nodenext, bundler 옵션에서 이를 지원합니다.
이 작업은 magic-akari 덕분에 이루어졌으며, 구현된 풀 리퀘스트는 여기에서 확인할 수 있습니다.

--moduleResolution bundler--module commonjs 조합

TypeScript의 --moduleResolution bundler 설정은 이전에는 --module esnext 또는 --module preserve와만 함께 사용할 수 있었습니다. 그러나 --moduleResolution node(일명 --moduleResolution node10)가 지원 중단됨에 따라, 이 새로운 조합이 많은 프로젝트에 가장 적합한 업그레이드 경로가 되는 경우가 많습니다.
프로젝트 유형(예: 번들형 웹 앱, Bun 앱, Node.js 앱)에 따라 프로젝트는 대신 다음 중 하나로의 마이그레이션을 계획하는 것이 좋습니다.
  • --module preserve--moduleResolution bundler
  • --module nodenext
자세한 정보는 이 구현 풀 리퀘스트에서 확인할 수 있습니다.

--stableTypeOrdering 플래그

TypeScript의 네이티브 포트 작업의 일환으로, 6.0에서 7.0으로의 마이그레이션을 돕기 위해 --stableTypeOrdering이라는 새로운 플래그를 도입했습니다.
현재 TypeScript는 타입이 발견되는 순서대로 타입 ID(내부 추적 번호)를 할당하며, 이 ID를 사용하여 유니언(Union) 타입을 일관된 방식으로 정렬합니다. 속성(Property)에 대해서도 유사한 프로세스가 발생합니다. 결과적으로 프로그램에서 항목이 선언되는 순서가 선언 파일 생성(Declaration Emit)과 같은 부분에 예상치 못한 영향을 미칠 수 있습니다.
예를 들어, 다음 파일의 선언 생성을 고려해 보세요.
// 입력: some-file.ts
export function foo(condition: boolean) {
return condition ? 100 : 500;
}

// 출력: some-file.d.ts
export declare function foo(condition: boolean): 100 | 500;
// ^^^^^^^^^
// 이 유니언의 순서를 주목하세요: 100 다음 500.
만약 foo 위에 관련 없는 const를 추가하면 선언 생성이 변경됩니다.
// 입력: some-file.ts
const x = 500;
export function foo(condition: boolean) {
return condition ? 100 : 500;
}

// 출력: some-file.d.ts
export declare function foo(condition: boolean): 500 | 100;
// ^^^^^^^^^
// 여기서 순서가 바뀐 것을 주목하세요.
이런 현상이 발생하는 이유는 const x 선언을 분석할 때 리터럴 타입 500이 먼저 처리되어 100보다 낮은 타입 ID를 갖게 되기 때문입니다. 매우 드문 경우지만, 이러한 정렬 변경으로 인해 프로그램 처리 순서에 따라 오류가 나타나거나 사라질 수도 있습니다. 하지만 일반적으로 이 정렬을 가장 잘 느낄 수 있는 곳은 생성된 선언 파일이나 에디터에서 타입이 표시되는 방식입니다.
TypeScript 7의 주요 아키텍처 개선 사항 중 하나는 병렬 타입 검사이며, 이는 전체 검사 시간을 획기적으로 단축합니다. 그러나 병렬 처리는 과제를 안겨줍니다. 서로 다른 타입 검사기가 노드, 타입, 심볼을 서로 다른 순서로 방문할 때, 이러한 구조물에 할당된 내부 ID가 비결정적(Non-deterministic)이 됩니다. 이는 결과적으로 혼란스러운 비결정적 출력으로 이어져, 동일한 프로그램 내에서 내용이 같은 두 파일이 서로 다른 선언 파일을 생성하거나, 동일한 파일을 분석할 때 서로 다른 오류를 계산할 수도 있게 됩니다. 이를 해결하기 위해 TypeScript 7.0은 객체의 내용에 기반한 결정론적 알고리즘에 따라 내부 객체(예: 타입 및 심볼)를 정렬합니다. 이를 통해 모든 검사기가 객체가 생성된 방식이나 시점에 관계없이 동일한 객체 순서를 마주하도록 보장합니다. 결과적으로 위 예시에서 TypeScript 7은 항상 100 | 500을 출력하여 정렬 불안정성을 완전히 제거합니다.
이는 TypeScript 6와 7이 때때로 서로 다른 정렬을 표시할 수 있고 실제로 그렇다는 것을 의미합니다. 이러한 정렬 변경은 거의 항상 무해하지만, 실행 간의 컴파일러 출력을 비교하는 경우(예: 6.0과 7.0에서 생성된 선언 파일 확인), 이러한 정렬 차이는 정확성을 평가하기 어렵게 만드는 많은 노이즈를 발생시킬 수 있습니다. 드물게 정렬 변경으로 인해 타입 오류가 나타나거나 사라지는 현상을 목격할 수도 있는데, 이는 더욱 혼란스러울 수 있습니다.
이러한 상황을 돕기 위해 6.0에서는 새로운 --stableTypeOrdering 플래그를 지정할 수 있습니다. 이 플래그는 6.0의 타입 정렬 동작을 7.0과 일치시켜 두 코드베이스 간의 차이를 줄여줍니다. 이 플래그는 타입 검사 속도를 상당히 늦출 수 있으므로(코드베이스에 따라 최대 25%), 항상 사용하는 것을 권장하지는 않습니다.
--stableTypeOrdering을 사용할 때 타입 오류가 발생한다면, 이는 대개 추론 차이 때문입니다. --stableTypeOrdering이 없었을 때의 이전 추론은 프로그램의 현재 타입 정렬에 따라 우연히 작동했던 것입니다. 이를 해결하기 위해 어딘가에 명시적인 타입을 제공하는 것이 도움이 될 때가 많습니다. 보통은 타입 인수가 될 것입니다.
- someFunctionCall(/*...*/);
+ someFunctionCall<SomeExplicitType>(/*...*/);
또는 호출에 전달하려는 인수에 대한 변수 어노테이션일 수 있습니다.
- const someVariable = { /*... 복잡한 객체 ...*/ };
+ const someVariable: SomeExplicitType = { /*... 복잡한 객체 ...*/ };

someFunctionCall(someVariable);
이 플래그는 6.0과 7.0 사이의 차이점을 진단하는 데 도움을 주기 위한 용도일 뿐이며, 장기적인 기능으로 사용하기 위한 것이 아님에 유의하세요.

targetlib을 위한 es2025 옵션

TypeScript 6.0은 targetlib 모두에 대해 es2025 옵션 지원을 추가합니다. ES2025에는 새로운 JavaScript 언어 기능은 없지만, 이 새로운 타겟은 내장 API(예: RegExp.escape)를 위한 새로운 타입을 추가하고, esnext에서 es2025로 몇 가지 선언을 이동합니다(예: Promise.try, Iterator 메서드, Set 메서드). 새로운 타겟을 활성화하는 작업은 Kenta Moriuchi 덕분에 기여되었습니다.

Temporal을 위한 새로운 타입

오랫동안 기다려온 Temporal 제안이 3단계(Stage 3)에 도달했으며 가까운 시일 내에 JavaScript에 추가될 것으로 예상됩니다. TypeScript 6.0에는 이제 Temporal API를 위한 내장 타입이 포함되어 있으므로, --target esnext 또는 "lib": ["esnext"](또는 더 세분화된 temporal.esnext)를 통해 오늘 바로 TypeScript 코드에서 사용할 수 있습니다.
let yesterday = Temporal.Now.instant().subtract({
hours: 24,
});

let tomorrow = Temporal.Now.instant().add({
hours: 24,
});

console.log(`Yesterday: ${yesterday}`);
console.log(`Tomorrow: ${tomorrow}`);
Temporal은 이미 여러 런타임에서 사용할 수 있으므로 곧 실험을 시작할 수 있을 것입니다. Temporal API에 대한 문서는 MDN에서 확인 가능하지만, 아직 불완전할 수 있습니다.
이 작업은 GitHub 사용자 Renegade334 덕분에 기여되었습니다.

"upsert" 메서드(일명 getOrInsert)를 위한 새로운 타입

Map을 사용할 때 흔히 쓰이는 패턴은 키가 존재하는지 확인하고, 없으면 기본값을 설정하고 가져오는 것입니다.
function processOptions(compilerOptions: Map<string, unknown>) {
let strictValue: unknown;
if (compilerOptions.has("strict")) {
strictValue = compilerOptions.get("strict");
}
else {
strictValue = true;
compilerOptions.set("strict", strictValue);
}
// ...
}
이 패턴은 번거로울 수 있습니다. ECMAScript의 "upsert" 제안이 최근 4단계(Stage 4)에 도달했으며, MapWeakMap에 두 가지 새로운 메서드를 도입했습니다.
  • getOrInsert
  • getOrInsertComputed
이 메서드들은 esnext 라이브러리에 추가되어 TypeScript 6.0에서 즉시 사용할 수 있습니다.
getOrInsert를 사용하면 위의 코드를 다음과 같이 바꿀 수 있습니다.
function processOptions(compilerOptions: Map<string, unknown>) {
let strictValue = compilerOptions.getOrInsert("strict", true);
// ...
}
getOrInsertComputed도 비슷하게 작동하지만, 기본값을 계산하는 비용이 많이 드는 경우(예: 많은 계산, 할당이 필요하거나 오래 걸리는 동기 I/O를 수행하는 경우)를 위한 것입니다. 대신 키가 아직 존재하지 않을 때만 호출될 콜백을 인수로 받습니다.
someMap.getOrInsertComputed("someKey", () => {
return computeSomeExpensiveValue(/*...*/);
});
이 콜백은 키를 인수로 전달받으므로, 기본값이 키를 기반으로 하는 경우에도 유용합니다.
someMap.getOrInsertComputed(someKey, computeSomeExpensiveDefaultValue);

function computeSomeExpensiveValue(key: string) {
// ...
}
이 업데이트는 GitHub 사용자 Renegade334 덕분에 기여되었습니다.

RegExp.escape

정규 표현식 내에서 일치시킬 리터럴 문자열을 구성할 때 *, +, ?, (, ) 등과 같은 특수 정규 표현식 문자를 이스케이프(Escape)하는 것이 중요합니다. RegExp Escaping ECMAScript 제안이 4단계에 도달했으며, 이를 대신 처리해 주는 새로운 RegExp.escape 함수를 도입했습니다.
function matchWholeWord(word: string, text: string) {
const escapedWord = RegExp.escape(word);
const regex = new RegExp(`\\b${escapedWord}\\b`, "g");
return text.match(regex);
}
RegExp.escapees2025 라이브러리에서 사용할 수 있으므로, 오늘 바로 TypeScript 6.0에서 사용할 수 있습니다.
이 작업Kenta Moriuchi 덕분에 기여되었습니다.

dom 라이브러리에 dom.iterabledom.asynciterable 포함

TypeScript의 lib 옵션을 사용하면 대상 런타임이 가진 전역 선언을 지정할 수 있습니다. 한 가지 옵션은 웹 환경(즉, DOM API를 구현하는 브라우저)을 나타내는 dom입니다. 이전에는 IterableAsyncIterable을 지원하지 않는 환경을 위해 DOM API가 dom.iterabledom.asynciterable로 부분적으로 분리되어 있었습니다. 즉, NodeListHTMLCollection과 같은 DOM 컬렉션에서 반복 메서드를 사용하려면 dom.iterable을 명시적으로 추가해야 했습니다.
TypeScript 6.0에서는 lib.dom.iterable.d.tslib.dom.asynciterable.d.ts의 내용이 lib.dom.d.ts에 완전히 포함됩니다. 설정 파일의 "lib" 배열에서 여전히 dom.iterabledom.asynciterable을 참조할 수 있지만, 이제는 빈 파일일 뿐입니다.
// TypeScript 6.0 이전에는 "lib": ["dom", "dom.iterable"]이 필요했습니다.
// 이제는 "lib": ["dom"]만으로 작동합니다.
for (const element of document.querySelectorAll("div")) {
console.log(element.textContent);
}
이는 주요 현대 브라우저 중 이러한 기능을 지원하지 않는 브라우저가 없기 때문에, 흔한 혼란의 원인을 제거하는 삶의 질(QoL) 개선 사항입니다. 이미 domdom.iterable을 모두 포함하고 있었다면 이제 dom으로 단순화할 수 있습니다.
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

TypeScript 6.0의 주요 변경 사항 및 지원 중단

TypeScript 6.0은 TypeScript 컴파일러의 차기 네이티브 포트인 TypeScript 7.0을 위해 개발자를 준비시키도록 설계된 중요한 전환 릴리스입니다. TypeScript 6.0은 기존 TypeScript 지식과 완전한 호환성을 유지하고 TypeScript 5.9와 API 호환성을 계속 유지하지만, 이번 릴리스에서는 진화하는 JavaScript 생태계를 반영하고 TypeScript 7.0의 토대를 마련하는 여러 가지 주요 변경 사항과 지원 중단 사항을 도입합니다.
TypeScript 5.0 이후 2년 동안 개발자가 JavaScript를 작성하고 배포하는 방식에 지속적인 변화가 있었습니다.
  • 사실상 모든 런타임 환경이 이제 "에버그린(Evergreen)"입니다. 진정한 레거시 환경(ES5)은 매우 드뭅니다.
  • 번들러와 ESM은 새로운 프로젝트에서 가장 일반적인 모듈 타겟이 되었지만, CommonJS는 여전히 주요 타겟으로 남아 있습니다. AMD 및 기타 브라우저 내 사용자 영역 모듈 시스템은 2012년에 비해 훨씬 드뭅니다.
  • 거의 모든 패키지는 어떤 모듈 시스템을 통해서든 소비될 수 있습니다. UMD 패키지는 여전히 존재하지만, 사실상 새로운 코드가 전역 변수로만 제공되는 경우는 없습니다.
  • tsconfig.json은 설정 메커니즘으로서 거의 보편화되었습니다.
  • "더 엄격한" 타이핑에 대한 요구가 계속 커지고 있습니다.
  • TypeScript 빌드 성능이 최우선 과제입니다. TypeScript 7의 성능 향상에도 불구하고, 성능은 항상 핵심 목표로 유지되어야 하며, 성능 면에서 지원될 수 없는 옵션은 더 강력하게 정당화되어야 합니다.
따라서 TypeScript 6.0과 7.0은 이러한 현실을 염두에 두고 설계되었습니다. TypeScript 6.0의 경우, 이러한 지원 중단 사항은 tsconfig에서 "ignoreDeprecations": "6.0"을 설정하여 무시할 수 있습니다. 하지만 TypeScript 7.0은 이러한 지원 중단된 옵션을 지원하지 않을 것임을 유의하세요.
일부 필요한 조정은 코드모드(Codemod)나 도구를 사용하여 자동으로 수행할 수 있습니다. 예를 들어, 실험적인 ts5to6 도구는 코드베이스 전체에서 baseUrlrootDir을 자동으로 조정할 수 있습니다.

사전 조정 사항

아래에서 구체적인 조정 사항을 다루겠지만, 일부 지원 중단 및 동작 변경 사항은 근본적인 문제를 직접 가리키는 오류 메시지가 반드시 있는 것은 아니라는 점에 유의해야 합니다. 따라서 많은 프로젝트에서 다음 중 적어도 하나를 수행해야 할 것임을 미리 알려드립니다.
  • tsconfig에서 "types" 배열을 설정하세요. 일반적으로 "types": ["node"]입니다.
"types": ["*"]는 5.9의 동작을 복구하지만, 빌드 성능과 예측 가능성을 높이기 위해 명시적인 배열을 사용하는 것을 권장합니다.
누락된 식별자나 해결되지 않은 내장 모듈과 관련된 타입 오류가 많이 발생한다면 이것이 문제일 가능성이 높습니다.
  • 이전에 rootDir이 추론되는 것에 의존했다면 "rootDir": "./src"를 설정하세요.
파일이 ./dist/index.js 대신 ./dist/src/index.js로 작성되는 것을 본다면 이것이 문제일 가능성이 높습니다.

간단한 기본값 변경 사항

여러 컴파일러 옵션이 현대적인 개발 관행을 더 잘 반영하도록 업데이트된 기본값을 갖게 되었습니다.
  • strict가 이제 기본적으로 true입니다: 더 엄격한 타이핑에 대한 요구가 계속 커지고 있으며, 우리는 대부분의 새로운 프로젝트가 strict 모드를 활성화하기를 원한다는 것을 발견했습니다. 이미 "strict": true를 사용하고 있었다면 바뀌는 것은 없습니다. 이전 기본값인 false에 의존하고 있었다면 tsconfig.json에서 명시적으로 "strict": false를 설정해야 합니다.
  • module의 기본값이 esnext가 됩니다: 마찬가지로, ESM이 이제 지배적인 모듈 형식임을 인정하여 새로운 기본 moduleesnext입니다.
  • target의 기본값이 현재 연도의 ES 버전이 됩니다: 새로운 기본 target은 가장 최근에 지원되는 ECMAScript 스펙 버전입니다(사실상 유동적인 타겟). 현재 그 타겟은 es2025입니다. 이는 대부분의 개발자가 에버그린 런타임에 배포하며 이전 ECMAScript 버전으로 트랜스파일할 필요가 없다는 현실을 반영합니다.
  • noUncheckedSideEffectImports가 이제 기본적으로 true입니다: 이는 부수 효과 전용(Side-effect-only) 가져오기에서의 오타 문제를 잡는 데 도움이 됩니다.
  • libReplacement가 이제 기본적으로 false입니다: 이 플래그는 이전에 실행할 때마다 많은 수의 실패한 모듈 해결을 초래했으며, 이는 결과적으로 --watch 및 에디터 시나리오에서 감시해야 할 위치의 수를 증가시켰습니다. 새로운 프로젝트에서 libReplacement는 다른 명시적 설정이 이루어지기 전까지는 아무것도 하지 않으므로, 기본적으로 더 나은 성능을 위해 이를 끄는 것이 타당합니다.
이러한 새로운 기본값이 프로젝트를 깨뜨린다면, tsconfig.json에서 이전 값을 명시적으로 지정할 수 있습니다.

rootDir의 기본값이 이제 .입니다.

rootDir은 출력 디렉터리를 기준으로 출력 파일의 디렉터리 구조를 제어합니다. 이전에는 rootDir을 지정하지 않으면 선언 파일이 아닌 모든 입력 파일의 공통 디렉터리를 기반으로 추론되었습니다. 하지만 이는 해당 프로젝트를 로드하고 파싱해 보기 전까지는 파일이 프로젝트에 속하는지 알 수 없음을 의미하는 경우가 많았습니다. 또한 TypeScript가 프로그램의 모든 파일 경로를 분석하여 공통 소스 디렉터리를 추론하는 데 더 많은 시간을 소비해야 함을 의미했습니다.
TypeScript 6.0에서 기본 rootDir은 항상 tsconfig.json 파일이 포함된 디렉터리가 됩니다. rootDirtsconfig.json 파일 없이 커맨드 라인에서 tsc를 사용할 때만 추론됩니다.
tsconfig.json 디렉터리보다 깊은 단계에 소스 파일이 있고 TypeScript가 소스 파일의 공통 루트 디렉터리를 추론하는 것에 의존하고 있었다면, rootDir을 명시적으로 설정해야 합니다.
{
"compilerOptions": {
// ...
+ "rootDir": "./src"
},
"include": ["./src"]
}
마찬가지로, tsconfig.json이 포함된 디렉터리 외부의 파일을 참조하는 경우, 해당 파일을 포함하도록 rootDir을 조정해야 합니다.
{
"compilerOptions": {
// ...
+ "rootDir": "../src"
},
"include": ["../src/**/*.tests.ts"]
}
여기에서의 논의여기에서의 구현을 더 자세히 확인하세요.

types의 기본값이 이제 []입니다.

tsconfig.json에서 compilerOptionstypes 필드는 컴파일 중에 전역 스코프에 포함될 패키지 이름 목록을 지정합니다. 일반적으로 node_modules의 패키지는 소스 코드의 가져오기를 통해 자동으로 포함되지만, 편의를 위해 TypeScript는 기본적으로 node_modules/@types의 모든 패키지를 포함했습니다. 덕분에 직접 가져오지 않고도 @types/nodeprocess"fs" 모듈, 또는 @types/jestdescribeit 같은 전역 선언을 얻을 수 있었습니다.
어떤 의미에서 types 값은 이전에 " node_modules/@types에 있는 모든 것을 열거하라"는 것이 기본값이었습니다. 이는 매우 비용이 많이 들 수 있는데, 요즘 일반적인 저장소 설정은 특히 평탄화된 node_modules를 가진 멀티 프로젝트 워크스페이스에서 수백 개의 @types 패키지를 전이적으로 끌어올 수 있기 때문입니다. 현대적인 프로젝트는 거의 항상 @types/node, @types/jest 또는 전역에 영향을 미치는 소수의 다른 공통 패키지만 필요로 합니다.
TypeScript 6.0에서 기본 types 값은 [](빈 배열)이 됩니다. 이 변경은 프로젝트가 빌드 시 불필요한 수백 또는 수천 개의 선언 파일을 의도치 않게 끌어오는 것을 방지합니다. 우리가 살펴본 많은 프로젝트는 types를 적절하게 설정하는 것만으로도 빌드 시간을 20-50% 단축했습니다.
이것은 많은 프로젝트에 영향을 미칠 것입니다. "types": ["node"] 또는 몇 가지 다른 항목을 추가해야 할 가능성이 높습니다.
{
"compilerOptions": {
// 필요한 @types 패키지를 명시적으로 나열하세요.
+ "types": ["node", "jest"]
}
}
또한 이전의 열거 동작을 다시 활성화하기 위해 * 항목을 지정할 수도 있습니다.
{
"compilerOptions": {
// 모든 타입을 로드합니다 - TypeScript 5.9 이전의 기본값입니다.
+ "types": ["*"]
}
}
다음과 같은 새로운 오류 메시지가 나타난다면,
Cannot find module '...' or its corresponding type declarations.
Cannot find name 'fs'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'path'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'process'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.
Cannot find name 'Bun'. Do you need to install type definitions for Bun? Try `npm i --save-dev @types/bun` and then add 'bun' to the types field in your tsconfig.
Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha` and then add 'jest' or 'mocha' to the types field in your tsconfig.
types 필드에 일부 항목을 추가해야 할 가능성이 높습니다.
여기에서의 제안구현된 풀 리퀘스트를 더 자세히 확인하세요.

지원 중단: target: es5

ECMAScript 5 타겟은 레거시 브라우저를 지원하기 위해 오랫동안 중요했습니다. 하지만 그 후속인 ECMAScript 2015(ES6)는 10년 전에 출시되었으며, 모든 현대 브라우저는 수년 동안 이를 지원해 왔습니다. Internet Explorer의 은퇴와 에버그린 브라우저의 보편화로 인해 오늘날 ES5 출력에 대한 사용 사례는 거의 없습니다.
TypeScript의 최저 타겟은 이제 ES2015가 되며, target: es5 옵션은 지원 중단됩니다. target: es5를 사용하고 있었다면 더 새로운 타겟으로 마이그레이션하거나 외부 컴파일러를 사용해야 합니다. 여전히 ES5 출력이 필요하다면, 외부 컴파일러를 사용하여 TypeScript 소스를 직접 컴파일하거나 TypeScript의 출력을 후처리하는 것을 권장합니다.

지원 중단: --downlevelIteration

--downlevelIteration은 ES5 생성에만 영향을 미치며, --target es5가 지원 중단되었으므로 --downlevelIteration은 더 이상 목적이 없습니다.
미묘하게도, TypeScript 5.9 이하에서는 --target es2015와 함께 --downlevelIteration false를 사용해도 아무런 효과가 없음에도 오류가 발생하지 않았습니다. TypeScript 6.0에서는 --downlevelIteration을 설정하는 것 자체가 지원 중단 오류로 이어집니다.
구현 사항은 여기에서 확인하세요.

지원 중단: --moduleResolution node (일명 --moduleResolution node10)

--moduleResolution node는 Node.js 10의 동작을 가장 정확하게 반영하는 특정 버전의 Node.js 모듈 해결 알고리즘을 인코딩했습니다. 불행히도 이 타겟(및 그 이름)은 그 이후에 발생한 Node.js 해결 알고리즘의 많은 업데이트를 무시하며, 더 이상 현대 Node.js 버전의 동작을 잘 나타내지 못합니다.
TypeScript 6.0에서 --moduleResolution node(구체적으로 --moduleResolution node10)는 지원 중단됩니다. --moduleResolution node를 사용하던 사용자들은 Node.js를 직접 타겟팅할 계획이라면 --moduleResolution nodenext로, 번들러나 Bun을 사용할 계획이라면 --moduleResolution bundler로 마이그레이션해야 합니다.
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: moduleamd, umd, systemjs

다음 플래그 값들은 더 이상 지원되지 않습니다.
  • --module amd
  • --module umd
  • --module systemjs
  • --module none
AMD, UMD, SystemJS는 브라우저에 네이티브 모듈 지원이 부족했던 JavaScript 모듈 초기에 중요했습니다. "none"의 의미는 결코 잘 정의되지 않았으며 종종 혼란을 야기했습니다. 오늘날 ESM은 브라우저와 Node.js에서 보편적으로 지원되며, 가져오기 맵(Import Maps)과 번들러 모두 그 간극을 메우기 위해 선호되는 방식이 되었습니다. 여전히 이러한 모듈 시스템을 타겟팅하고 있다면, 적절한 ECMAScript 모듈 생성 타겟으로 마이그레이션하거나, 번들러 또는 다른 컴파일러를 도입하거나, 마이그레이션할 수 있을 때까지 TypeScript 5.x에 머무르는 것을 고려해 보세요.
이는 또한 amd-module 지시문에 대한 지원 중단을 의미하며, 더 이상 아무런 효과가 없습니다.
제안 이슈구현된 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: --baseUrl

baseUrl 옵션은 paths와 함께 가장 흔히 사용되며, 일반적으로 paths의 모든 값에 대한 접두사로 사용됩니다. 불행히도 baseUrl은 모듈 해결을 위한 조회 루트(Look-up root)로도 간주됩니다.
예를 들어, 다음과 같은 tsconfig.json이 있고
{
"compilerOptions": {
// ...
"baseUrl": "./src",
"paths": {
"@app/*": ["app/*"],
"@lib/*": ["lib/*"]
}
}
}
다음과 같은 가져오기가 있다면
import * as someModule from "someModule.js";
개발자가 @app/@lib/로 시작하는 모듈에 대해서만 매핑을 추가하려 했음에도 불구하고, TypeScript는 아마도 이를 src/someModule.js로 해결할 것입니다.
최선의 경우에도 이는 번들러가 무시할 "더 나빠 보이는" 경로로 이어지곤 했습니다. 하지만 이는 런타임에서 절대 작동하지 않았을 많은 가져오기 경로가 TypeScript에 의해 "괜찮은 것"으로 간주되었음을 의미하기도 했습니다.
path 매핑은 오랫동안 baseUrl 지정을 요구하지 않았으며, 실제로 baseUrl을 사용하는 대부분의 프로젝트는 이를 paths 항목의 접두사로만 사용합니다. TypeScript 6.0에서 baseUrl은 지원 중단되며 더 이상 모듈 해결을 위한 조회 루트로 간주되지 않습니다.
baseUrl을 경로 매핑 항목의 접두사로 사용했던 개발자들은 단순히 baseUrl을 제거하고 paths 항목에 접두사를 추가하면 됩니다.
{
"compilerOptions": {
// ...
- "baseUrl": "./src",
"paths": {
- "@app/*": ["app/*"],
- "@lib/*": ["lib/*"]
+ "@app/*": ["./src/app/*"],
+ "@lib/*": ["./src/lib/*"]
}
}
}
실제로 baseUrl을 조회 루트로 사용했던 개발자들은 이전 동작을 유지하기 위해 명시적인 경로 매핑을 추가할 수도 있습니다.
{
"compilerOptions": {
// ...
"paths": {
// baseUrl을 대체하는 새로운 포괄적 매핑:
"*": ["./src/*"],

// 이제 다른 모든 경로에는 명시적인 공통 접두사가 있습니다.
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"],
}
}
}
그러나 이는 매우 드문 경우입니다. 대부분의 개발자는 단순히 baseUrl을 제거하고 paths 항목에 적절한 접두사를 추가할 것을 권장합니다.
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: --moduleResolution classic

moduleResolution: classic 설정이 제거되었습니다. classic 해결 전략은 TypeScript의 원래 모듈 해결 알고리즘이었으며, Node.js의 해결 알고리즘이 사실상의 표준이 되기 전의 것입니다. 오늘날 모든 실질적인 사용 사례는 nodenext 또는 bundler에 의해 충족됩니다. classic을 사용하고 있었다면 이러한 현대적인 해결 전략 중 하나로 마이그레이션하세요.
이 이슈구현된 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: --esModuleInterop false--allowSyntheticDefaultImports false

다음 설정들은 더 이상 false로 설정할 수 없습니다.
  • esModuleInterop
  • allowSyntheticDefaultImports
esModuleInteropallowSyntheticDefaultImports는 원래 기존 프로젝트를 깨뜨리지 않기 위해 선택 사항(Opt-in)으로 도입되었습니다. 그러나 이들이 활성화하는 동작은 수년 동안 권장되는 기본값이었습니다. 이를 false로 설정하면 ESM에서 CommonJS 모듈을 소비할 때 미묘한 런타임 문제가 발생하는 경우가 많았습니다. TypeScript 6.0에서는 더 안전한 상호 운용성(Interop) 동작이 항상 활성화됩니다.
이전 동작에 의존하는 가져오기가 있다면 이를 조정해야 할 수도 있습니다.
// 이전 (esModuleInterop: false인 경우)
import * as express from "express";

// 이후 (esModuleInterop이 항상 활성화된 경우)
import express from "express";
이 이슈구현된 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: --alwaysStrict false

alwaysStrict 플래그는 "use strict"; 지시문의 추론 및 생성을 나타냅니다. TypeScript 6.0에서 모든 코드는 JavaScript 엄격 모드(Strict Mode)에 있는 것으로 간주됩니다. 이는 예약어를 둘러싼 구문적 코너 케이스에 가장 눈에 띄게 영향을 미치는 JS 시맨틱 세트입니다. await, static, private, public과 같은 예약어를 일반 식별자로 사용하는 "슬로피 모드(Sloppy mode)" 코드가 있다면 이름을 변경해야 합니다. 비엄격 코드에서 this의 의미와 관련된 미묘한 시맨틱에 의존했다면 코드 역시 조정해야 할 수 있습니다.
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: outFile

--outFile 옵션이 TypeScript 6.0에서 제거되었습니다. 이 옵션은 원래 여러 입력 파일을 단일 출력 파일로 결합하기 위해 설계되었습니다. 그러나 Webpack, Rollup, esbuild, Vite, Parcel 등과 같은 외부 번들러가 이제 이 작업을 더 빠르고 더 잘 수행하며 훨씬 더 많은 구성 가능성을 제공합니다. 이 옵션을 제거하면 구현이 단순해지고 TypeScript가 가장 잘하는 것, 즉 타입 검사와 선언 생성에 집중할 수 있습니다. 현재 --outFile을 사용하고 있다면 외부 번들러로 마이그레이션해야 합니다. 대부분의 현대적인 번들러는 기본적으로 뛰어난 TypeScript 지원을 제공합니다.

지원 중단: 네임스페이스를 위한 레거시 module 구문

초기 버전의 TypeScript는 네임스페이스를 선언하기 위해 module 키워드를 사용했습니다.
// ❌ 지원 중단된 구문 - 이제 오류 발생
module Foo {
export const bar = 10;
}
이 구문은 나중에 namespace 키워드를 사용하는 현대적인 선호 형태로 별칭이 지정되었습니다.
// ✅ 올바른 구문
namespace Foo {
export const bar = 10;
}
namespace가 도입되었을 때 module 구문은 단순히 권장되지 않는 수준이었습니다. 몇 년 전부터 TypeScript 언어 서비스는 이 키워드를 지원 중단된 것으로 표시하고 대신 namespace를 제안하기 시작했습니다.
TypeScript 6.0에서 namespace가 기대되는 곳에 module을 사용하는 것은 이제 강력한 지원 중단 대상입니다. 이 변경이 필요한 이유는 module 블록이 레거시 TypeScript 구문과 충돌할 수 있는 잠재적인 ECMAScript 제안이기 때문입니다.
앰비언트(Ambient) 모듈 선언 형태는 여전히 완전히 지원됩니다.
// ✅ 여전히 완벽하게 작동합니다.
declare module "some-module" {
export function doSomething(): void;
}
자세한 내용은 이 이슈해당 풀 리퀘스트를 참조하세요.

지원 중단: 가져오기에서의 asserts 키워드

asserts 키워드는 가져오기 단언(Import Assertions) 제안을 통해 JavaScript 언어에 제안되었습니다. 그러나 제안은 결국 asserts 대신 with 키워드를 사용하는 가져오기 속성(Import Attributes) 제안으로 변형되었습니다.
따라서 asserts 구문은 이제 TypeScript 6.0에서 지원 중단되며, 이를 사용하면 오류가 발생합니다.
// ❌ 지원 중단된 구문 - 이제 오류 발생.
import blob from "./blahb.json" asserts { type: "json" }
// ~~~~~~~
// 오류: 가져오기 단언은 가져오기 속성으로 대체되었습니다. 'asserts' 대신 'with'를 사용하세요.
대신 가져오기 속성을 위해 with 구문을 사용하세요.
// ✅ 새로운 가져오기 속성 구문으로 작동합니다.
import blob from "./blahb.json" with { type: "json" }
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

지원 중단: no-default-lib 지시문

/// <reference no-default-lib="true"/> 지시문은 대체로 오해되고 잘못 사용되어 왔습니다. TypeScript 6.0에서 이 지시문은 더 이상 지원되지 않습니다. 이를 사용하고 있었다면 대신 --noLib 또는 --libReplacement를 사용하는 것을 고려해 보세요.
여기해당 풀 리퀘스트에서 더 자세히 확인하세요.

tsconfig.json이 존재할 때 커맨드 라인 파일을 지정하는 것이 이제 오류로 처리됨

현재 tsconfig.json이 존재하는 폴더에서 tsc foo.ts를 실행하면 설정 파일이 완전히 무시됩니다. 이는 입력 파일에 검사 및 생성 옵션이 적용되기를 기대했을 때 매우 혼란스러운 경우가 많았습니다.
TypeScript 6.0에서 tsconfig.json이 포함된 디렉터리에서 파일 인수와 함께 tsc를 실행하면, 이 동작을 명시적으로 알리기 위해 오류가 발생합니다.
error TS5112: tsconfig.json is present but will not be loaded if files are specified on commandline. Use '--ignoreConfig' to skip this error.
만약 tsconfig.json을 무시하고 TypeScript의 기본값으로 foo.ts를 컴파일하고 싶은 경우라면, 새로운 --ignoreConfig 플래그를 사용할 수 있습니다.
tsc --ignoreConfig foo.ts
이 이슈해당 풀 리퀘스트에서 더 자세히 확인하세요.

TypeScript 7.0 준비하기

TypeScript 6.0은 전환 릴리스로 설계되었습니다. TypeScript 6.0에서 지원 중단된 옵션들은 "ignoreDeprecations": "6.0"이 설정되어 있으면 오류 없이 계속 작동하지만, 해당 옵션들은 TypeScript 7.0(네이티브 TypeScript 포트)에서 완전히 제거될 예정입니다. TypeScript 6.0으로 업그레이드한 후 지원 중단 경고가 표시된다면, 프로젝트에 TypeScript 7.0을 도입(또는 네이티브 프리뷰 시도)하기 전에 이를 해결할 것을 강력히 권장합니다.
일정에 관해서는 TypeScript 6.0 이후 곧바로 TypeScript 7.0이 출시될 것으로 예상합니다. 이는 연속성을 유지하는 동시에 도입 과정에서 발견된 마이그레이션 문제에 대해 더 빠른 피드백 루프를 제공하는 데 도움이 될 것입니다.

다음 단계는 무엇인가요?

현재 TypeScript 6.0은 기능적으로 완성되었으며, 컴파일러에 대한 치명적인 버그 수정을 제외하고는 변경 사항이 거의 없을 것으로 예상합니다. 앞으로 몇 주 동안 6.0 브랜치에서 보고된 이슈를 해결하는 데 집중할 예정이므로, RC를 사용해 보시고 의견을 공유해 주시기를 권장합니다.
또한 npmVisual Studio Code에서 나이틀리(Nightly) 빌드를 게시하고 있으며, 이를 통해 최근 수정된 이슈를 빠르게 확인할 수 있습니다.
우리는 또한 TypeScript 7.0 작업을 계속하고 있으며, 네이티브 프리뷰의 나이틀리 빌드와 VS Code 확장 프로그램도 게시하고 있습니다. 6.0과 7.0 모두에 대한 피드백을 매우 소중하게 생각하며, 가능하다면 두 버전 모두 시도해 보시기를 권장합니다.
그러니 프로젝트에서 TypeScript 6.0 RC를 사용해 보시고 여러분의 생각을 알려주세요!
즐거운 코딩 되세요!
– Daniel Rosenwasser와 TypeScript 팀 드림



저자

Daniel Rosenwasser

수석 제품 매니저
Daniel Rosenwasser는 TypeScript 팀의 제품 매니저입니다. 그는 프로그래밍 언어, 컴파일러, 그리고 훌륭한 개발자 도구에 대한 열정을 가지고 있습니다.
0
2

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!

Inkyu Oh님의 다른 글

더보기

유사한 내용의 글