https://aka.ms/ts6은 마이그레이션 가이드 이슈(#62508)로 리다이렉트되는데, 이 글을 쓰는 시점엔 본문이 아직 Placeholder만 들어 있는 빈 페이지다. 에러는 친절하게 안내하지만 정작 안내처는 공사 중인 셈.
-
----
-
-## 2. types 기본값이 []가 된 이유 (그리고 왜 우리 빌드는 멀쩡했나)
-
-### 의심: 타입이 다 빠졌어야 하는 거 아닌가?
-
-작업 중간에 의문이 들었다. "TS6에선 `types: []`가 기본이라던데, 그럼 전역 타입이 다 빠졌을 텐데 왜 빌드가 다 되지?" 이건 에러가 아니라 **에러가 안 난 게 더 수상한** 경우였다.
-
-### 무엇이, 왜 바뀌었나
+`TS5101`은 "Option '{0}' is deprecated..."라는 **범용 메시지**의 코드고, `{0}`에 `baseUrl`이, 안내 링크(
+`Visit https://aka.ms/ts6 ...`)에 신규 코드 `5111`이 related로 붙은 것이다. 그러니 `ignoreDeprecations: "6.0"`으로
+한시적으로 끌 수는 있지만, 그건 그저 문제를 7.0으로 미루는 일이다.
-[릴리스 노트](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-6-0.html)와 제안 이슈 [#62195](https://github.com/microsoft/TypeScript/issues/62195)이 명확하다. 기존 `types`의 기본값은 사실상 "`node_modules/@types`를 전부 열거"였다.
+참고로 에러가 가리키는 https://aka.ms/ts6은 마이그레이션 가이드
+이슈(#62508)로 리다이렉트되는데, 이 글을 쓰는 시점엔
+본문이 아직 Placeholder만 들어 있는 빈 페이지다. 에러는 친절하게 안내하지만 정작 안내처는 공사 중인 셈.
-> "for convenience, TypeScript would also include all packages in `node_modules/@types` by default... This can be _very_ expensive, as a normal repository setup these days might transitively pull in hundreds of `@types` packages, especially in multi-project workspaces with flattened `node_modules`." — 릴리스 노트
-
-flattened `node_modules`를 쓰는 모노레포에서는 이게 특히 치명적이다. 수백 개의 불필요한 `.d.ts`가 전이적으로 프로그램에 끌려와 파싱/체크 비용을 잡아먹는다. 그래서 기본값을 `[]`로 바꿨고, 효과는 수치로 제시돼 있다.
-
-> "Many projects we've looked at have improved their build time anywhere from 20-50% just by setting `types` appropriately." — 릴리스 노트 ([원 출처는 #54500](https://github.com/microsoft/TypeScript/issues/54500))
-
-### 구현 레벨: "기본값 = []" 대입문은 없다
-
-PR [#63054 "Set default `types` array to `[]`; support `\"*\"` wildcard"](https://github.com/microsoft/TypeScript/pull/63054)을 까보면, 흥미롭게도 `options.types = []` 같은 **명시적 기본값 대입은 어디에도 없다.** 자동 열거를 담당하던 `getAutomaticTypeDirectiveNames`의 early-return 조건이 뒤집힌 게 전부다.
-
-```diff
-// src/compiler/moduleNameResolver.ts
- export function getAutomaticTypeDirectiveNames(options, host): string[] {
-- // Use explicit type list from tsconfig.json
-- if (options.types) {
-- return options.types;
-+ if (!usesWildcardTypes(options)) {
-+ return options.types ?? [];
- }
- // ... 여기 아래(typeRoots 열거)는 이제 "*"가 있을 때만 실행된다
-- return result;
-+ return deduplicate(flatten(options.types.map(t => t === "*" ? wildcardMatches : t)), equateValues);
- }
-```
+### `paths`도 끝이 아니다: 진짜 대안은 package.json `imports`
-핵심은 `if (options.types)` → `if (!usesWildcardTypes(options))`로 바뀐 한 줄이다. 예전엔 "`types`를 명시 안 하면(undefined) typeRoots를 뒤져 모든 `@types`를 자동 포함"했는데, 이제는 **`types` 배열에 `"*"`가 있을 때만** 열거하고, 그 외에는 `options.types ?? []`를 그대로 돌려준다. `[]` 기본값은 별도 코드가 아니라 이 early-return의 자연스러운 결과다.
+`baseUrl`을 지우고 `paths`로 갈아탔지만, `paths`도 완전한 답은 아니다. **`paths`는 타입체커 전용**이라 `tsc`가 내보내는 JS엔 반영되지 않아,
+`baseUrl`이 6.0에서 막힌 그 이유(런타임에 안 맞는 import를 타입체커만 통과)와 정도만 다를 뿐 같은 괴리를 안는다. 그 괴리가 구조적으로 없는 대안이 *
+*Node가 런타임에 직접 읽는 package.json `imports`** (`#` subpath)다 — 타입과 런타임이 같은 매핑을 보니 어긋날 수 없고, TS 핸드북도
+이를 _"a standard replacement for convenience `paths` aliases"_ 로 부른다.
-판정 헬퍼도 새로 추가됐다.
+ignoreDeprecations는 baseUrl 같은 deprecated 옵션용이지, types 기본값 변경을 되돌리는 옵션이 아니다. 옛 동작이 필요하면 "types": ["*"]를 쓰면 된다 — 이건 glob이 아니라 "전부 열거"를 뜻하는 special token이다.
+ignoreDeprecations는 baseUrl 같은 deprecated 옵션용이지,
+types 기본값 변경을 되돌리는 옵션이 아니다. 옛 동작이 필요하면 "types": ["*"]를 쓰면 된다 — 이건 glob이 아니라 "전부
+열거"를 뜻하는 special token이다.
-선택지는 둘이었다. (a) `ignoreDeprecations: "6.0"`으로 경고를 한시적으로 끄거나, (b) 권고대로 도구를 갈아치우거나. baseUrl 주입은 내 코드가 아니라 도구의 문제고, 그 도구가 더는 고쳐지지 않는다면 답은 정해져 있었다.
+---
-### tsdown으로
+## 4. 1차 검증: 여기까지 check-types는 통과
-[tsdown](https://tsdown.dev)은 [Rolldown](https://rolldown.rs)(Rust 기반 번들러) 위에서 도는 tsup의 후속 격 도구다. 공식 문서가 관계를 이렇게 정리한다.
+세 가지 규칙을 다 맞추고 나니 타입 체크는 깨끗했다.
-> "tsdown is the spiritual successor to tsup, powered by Rolldown instead of esbuild." — [tsdown FAQ](https://tsdown.dev/guide/faq)
+- `pnpm check-types` → **5/5 통과**
-결정적으로, dts를 [`rolldown-plugin-dts`](https://github.com/sxzz/rolldown-plugin-dts)로 생성하기 때문에 **tsup처럼 baseUrl을 주입하지 않는다.** 그리고 peer dependency로 `typescript: "^5.0.0 || ^6.0.0"`을 선언해 **TS6를 공식 지원**한다(tsup은 아직 open 이슈). tsup을 쓰던 패키지는 둘(`@design-system/ui`, `@package/bundler`)뿐이라 둘 다 전환했다. 공식 마이그레이션 도구도 있다.
+자, TypeScript 자체가 바꾼 규칙들은 다 막아냈다. 그런데 전체 빌드(`pnpm build`)를 돌리자, **정작 내가 손대지도 않은 곳**에서 다시 터졌다. 그것도 방금
+작별했다고 생각한 그 이름, `baseUrl`로.
-```bash
-npx tsdown-migrate # 단일 디렉터리
-npx tsdown-migrate packages/* # 모노레포 glob
-npx tsdown-migrate --dry-run # 변경 미리보기 (-d)
-```
+---
-config는 `import`만 바꾸면 거의 그대로다.
+## 5. 보너스: 다시 튀어나온 baseUrl — 이번엔 내 코드가 아니었다
+
+`pnpm build`가 디자인 시스템 패키지의 dts 빌드에 다다르자 **또 `TS5101`**이 떴다. 그런데 `@design-system/ui`의 tsconfig엔
+`baseUrl`이 **없다.** #1에서 봤듯 TS5101은 범용 진단이니, 누군가 내 빌드에 baseUrl을 **주입**하고 있다는 뜻이다.
+
+범인은 dts 번들러로 쓰던 **tsup**이었다. [소스](https://github.com/egoist/tsup/blob/main/src/rollup.ts)의 dts 빌드 옵션
+구성부에 이 한 줄이 있다.
```ts
-// packages/@package/bundler/tsdown.config.ts
-import { defineConfig } from 'tsdown';
-
-export default defineConfig({
- entry: ['src/index.ts', 'src/cli.ts'],
- format: ['esm', 'cjs'],
- platform: 'node',
- target: 'node24', // ← platform과 다른 축 (함정 ② 참고)
- clean: true,
- dts: true,
- sourcemap: true,
-});
+baseUrl: compilerOptions.baseUrl || '.', // ← 내 tsconfig에 없어도 '.'를 강제 주입
```
-전환 직후, `TS5101`은 **사라졌다.** 근본 원인(baseUrl 주입)이 없어졌기 때문이다.
+TS 5.x에선 무해했지만 TS6에선 이 주입이 곧장 하드 에러다([tsup #1388](https://github.com/egoist/tsup/issues/1388), 재현 환경
+tsup 8.5.1 + TS 6.0.2). 게다가 tsup은 **이미 유지보수 중단** — README 최상단에 박혀 있다.
-### 함정 ①: 출력 확장자가 다르다
+> "This project is not actively maintained anymore. Please consider using tsdown
+> instead." — [egoist/tsup README](https://github.com/egoist/tsup/blob/main/README.md)
-다만 공짜는 아니었다. tsdown은 `platform: 'node'`에서 기본적으로 **`.mjs` / `.cjs` / `.d.mts` / `.d.cts`** 확장자로 출력한다(`fixedExtension`). tsup의 `.js` / `.d.ts`와 달라서, `package.json`의 `exports`·`bin`·`main`·`types`가 존재하지 않는 파일을 가리키게 됐다. 실제 산출물에 맞춰 전부 정정해야 했다.
+baseUrl 주입은 내 코드가 아니라 도구의 문제이고, 그 도구가 더는 고쳐지지 않으니 답은 정해져 있었다 — 후속 도구 [tsdown](https://tsdown.dev)으로
+갈아탔다. [Rolldown](https://rolldown.rs) 기반이라 **baseUrl을 주입하지 않고**, peer로
+`typescript: "^5.0.0 || ^6.0.0"`을 선언해 **TS6를 공식 지원**한다. 마이그레이션 도구(`npx tsdown-migrate`)로 config도 거의
+그대로 옮겨졌고, 전환 직후 `TS5101`은 사라졌다.
-```jsonc
-// @package/bundler/package.json — 실제 산출물에 맞게 수정
-{
- "exports": {
- ".": {
- "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
- "require": { "types": "./dist/index.d.cts", "default": "./dist/index.cjs" }
- }
- },
- "bin": { "minibundler": "./dist/cli.mjs" } // ← cli.js 가 아니라 cli.mjs
-}
-```
-
-`@design-system/ui`도 마찬가지로 `main`/`types`를 `./dist/index.mjs`·`./dist/index.d.mts`로 맞췄다(이 정정은 별도 커밋으로 떨어졌다 — 산출물 확장자가 바뀌면 이런 메타데이터가 줄줄이 따라온다는 걸 잊지 말 것).
+다만 공짜는 아니었다. 전환하며 밟은 함정 셋:
-### 함정 ②: `platform: 'node'`은 `target`이 아니다
+- **출력 확장자.** tsdown은 `platform: 'node'`에서 `.mjs`/`.cjs`/`.d.mts`/`.d.cts`로 낸다(tsup은 `.js`/`.d.ts`).
+ `package.json`의 `exports`·`bin`·`main`·`types`를 산출물에 맞춰 전부 정정해야 했다.
+- **`platform` ≠ `target`.** `platform: 'node'`는 의존성 외부화·출력 확장자 힌트일 뿐, ES 문법 타겟은 `target`이 따로 통제한다.
+ Node 24 문법까지 내리려면 `target: 'node24'`를 함께 명시.
+- **빌드 도구는 `devDependencies`에.** `tsdown`·`typescript`는 런타임 의존성이 아니다. `@design-system/ui`가 `tsup`을
+ `dependencies`에 두고 있어 뒤늦게 옮겼다.
-tsup의 `target: 'node24'`를 옮기며 무심코 `platform: 'node'`로 바꿔 적었는데, 둘은 **다른 축**이다. `platform`은 "이 번들은 Node에서 돈다"는 힌트로 의존성 외부화·조건 해석·기본 출력 확장자(`fixedExtension`) 등에 영향을 주고, **ES 문법 트랜스파일 타겟**은 `target`이 따로 통제한다. `target`을 생략하면 tsdown 기본값이 쓰이므로, Node 24 문법까지 의도대로 내리려면 `target: 'node24'`를 **함께** 명시해야 한다.
+- `pnpm build` (전체) → **9/9 통과** (blog 정적 빌드 포함)
-### 함정 ③: 빌드 도구는 `devDependencies`에
+---
-tsup 시절 `tsup`이 `dependencies`에 들어가 있었고, tsdown으로 바꾸며 그 자리를 그대로 물려줬다. 하지만 빌드 도구(`tsdown`)와 `typescript`는 런타임 의존성이 아니라 **`devDependencies`**에 있어야 한다 — 안 그러면 패키지를 퍼블리시할 때 불필요한 의존성이 딸려간다. `@package/bundler`는 처음부터 devDependencies였는데 `@design-system/ui`만 `dependencies`에 남아 있어 뒤늦게 맞춰 옮겼다.
+## 6. 정리: TypeScript 6 마이그레이션 체크리스트
-```jsonc
-// @design-system/ui/package.json
-{
- "dependencies": {
- "@design-system/ui-lib": "workspace:^" // 런타임 의존성만
- },
- "devDependencies": {
- "tsdown": "0.22.2", // ← 빌드 도구
- "typescript": "catalog:" // ← 빌드 타임
- // ...
- }
-}
-```
+이번 삽질을 한 줄짜리 체크리스트로 압축하면(1~4는 앞에서 다룬 것, 5는 버전을 일괄 갱신할 때 함께 챙기는 곁가지다):
-- `pnpm build` (전체) → **9/9 통과** (blog 정적 빌드 포함)
+1. **`baseUrl`을 쓰는가?** → 직접 쓰면 제거(`paths`는 4.1부터 baseUrl 불필요). 빌드 도구가 주입한다면 도구를 점검하라. `TS5101`은
+ baseUrl 전용이 아니라 **범용 deprecated-option 진단**임을 기억할 것.
+2. **`emitDeclarationOnly`/`outDir`로 emit하는데 소스가 tsconfig보다 깊은가?** → `rootDir`을 명시. 에러 메시지의 `'{1}'`이
+ 곧 넣어야 할 값이다.
+3. **`@types/node` 같은 전역에 의존하는가?** → `types: ["node", ...]`로 명시. TS6 기본은 `[]`이고, 이는 **ambient 전역에만**
+ 영향을 준다(import 타입은 무관). 옛 동작은 `["*"]`.
---
-## 6. 정리: TypeScript 6 마이그레이션 체크리스트
+## 7. 정작 가장 큰 수확: 6을 올리지 않아도 됐다
-이번 삽질을 한 줄짜리 체크리스트로 압축하면:
+표의 #1~#3, 그리고 보너스까지 따라오며 깨달은 게 있다. 이 변화들의 공통점은 결국 '비싸거나 모호한 기본값을 걷어내는 청소'였다 — baseUrl·rootDir은 TS7(Go)을 향한 정리, types는 순수 성능 개선. 그리고 **그 청소는 6을 올려야만 할 수 있는 게 아니다.**
+baseUrl·rootDir·types 셋 다 5에서도 오늘 당장 적용할 수 있는 모범 설정이다. 그러니 정작 올릴 첫 커밋은 "6 업그레이드"가 아니라 이 정리다.
-1. **`baseUrl`을 쓰는가?** → 직접 쓰면 제거(`paths`는 4.1부터 baseUrl 불필요). 빌드 도구가 주입한다면 도구를 점검하라. `TS5101`은 baseUrl 전용이 아니라 **범용 deprecated-option 진단**임을 기억할 것.
-2. **`@types/node` 같은 전역에 의존하는가?** → `types: ["node", ...]`로 명시. TS6 기본은 `[]`이고, 이는 **ambient 전역에만** 영향을 준다(import 타입은 무관). 옛 동작은 `["*"]`.
-3. **`emitDeclarationOnly`/`outDir`로 emit하는데 소스가 tsconfig보다 깊은가?** → `rootDir`을 명시. 에러 메시지의 `'{1}'`이 곧 넣어야 할 값이다.
-4. **dts 번들러가 tsup인가?** → tsup은 유지보수가 멈췄고 dts에 baseUrl을 주입한다. tsdown으로 전환하고(`npx tsdown-migrate`), ⓐ 출력 확장자(`.mjs`/`.d.mts`)에 맞춰 `exports`/`bin`/`main`/`types`를 고치고, ⓑ `platform`과 `target`은 다른 축이니 ES 타겟이 필요하면 `target`을 따로 명시하고, ⓒ 빌드 도구(`tsdown`)·`typescript`는 `devDependencies`에 두라.
-5. **버전을 일괄 갱신했는가?** → 하위 패키지에 박힌 `packageManager`·`engines` 필드가 루트와 어긋나 있지 않은지 확인하라. (루트는 `pnpm@11.6.0`인데 한 패키지에 `pnpm@10.4.1`이 남아 있었다.)
+**6을 안 올려도, 5에서 지금 할 일:**
-곁가지 — 작업 중 node_modules와 빌드 산출물을 수없이 지웠던 터라, 외부 의존성 없이 macOS find로 도는 clean 스크립트를 루트에 넣어뒀다. 핵심은 -name node_modules -prune으로 node_modules 내부를 가지치기하는 것 — 안 그러면 의존성 안의 수많은 dist까지 매칭돼 느리고 위험하다.
+- **`baseUrl` 제거** → `paths`만 남기거나, 런타임까지 일치하는 package.json `imports`로.
+- **`rootDir` 고정** → 명시해 출력 레이아웃을 결정적으로 묶는다.
+- **`types` 좁히기** → `types: ["node", ...]`로 명시해 자동 `@types` 열거 비용을 미리 던다.
-```jsonc
-"clean:dist": "find . -name node_modules -prune -o -type d '(' -name dist -o -name .next -o -name out -o -name .turbo ')' -prune -exec rm -rf {} +",
-"clean:modules": "find . -name node_modules -type d -prune -exec rm -rf {} +"
-```
+세 줄 다 5.x tsconfig에서 오늘 커밋할 수 있다. 6은 이걸 '강제'했을 뿐 '발명'한 게 아니다.
-> 메이저 업그레이드는 버전 숫자 하나 바꾸는 일처럼 보여도, 그 숫자가 건드리는 **기본값과 빌드 파이프라인의 가정들**을 전부 다시 확인하게 만든다. 이번엔 그 가정이 `baseUrl`, `types`, `rootDir` 세 군데에 숨어 있었고, 마지막 하나는 TypeScript가 아니라 **유지보수가 멈춘 내 빌드 도구** 안에 있었다.
+> 시작은 "이걸 왜 굳이 바꾸지?"라는 호기심 한 줄이었다. 세 옵션의 '왜'를 PR diff까지 따라가 보니, 답은 늘 같은 곳을 가리켰다 — Go로 다시 쓰인 7.0.
+`baseUrl`·`types`·`rootDir`도, 마지막에 튀어나온 `tsup`도, 전부 그 길목을 미리 쓸어두는 일이었다. 그리고 가장 김빠지면서도 든든한 깨달음은 따로
+> 있었다. 이 길, 6을 올려야만 걸을 수 있는 게 아니다. 5에서 그대로, 그것도 빌드가 빨라지는 채로 갈 수 있다. 버전 숫자를 올리는 일과 더 나은 설정으로 가는 일은, 생각보다 자주 별개다.
---
@@ -416,6 +493,7 @@ tsup 시절 `tsup`이 `dependencies`에 들어가 있었고, tsdown으로 바꾸
- TypeScript 6.0 릴리스 노트 —