라이브러리를 만드는 과정에서 타입스크립트 빌드를 하게 되었는데, 이상한 상황을 겪었다.
src 폴더 안에 .d.ts 타입 선언 파일을 직접 만들어뒀는데, 빌드 후 dist 폴더에는 이 파일이 나타나지 않았다.
분명히 타입 선언 파일인데, 왜 결과물에 안 들어가는 걸까?
해당 문제가 발생하니, 빌드 결과물의 d.ts에서 import한 타입이 없어 깨지는 문제가 발생하여 곤란했다 💧
문제 상황 재현 예제 ⁉️
폴더 구조
my-lib/
├── src/
│ ├── index.ts ← .d.ts의 type을 import 하여 사용함
│ └── index.d.ts ← 여기에 타입 선언이 있다
├── package.json
└── tsconfig.json
src/index.d.ts
export type GreetingProps = { name: string };
위와 같이 타입을 선언하고,
src/index.ts
import type { GreetingProps } from "./index.d";
export function greet({ name }: GreetingProps): string {
return `Hello, ${name}!`;
}
이렇게 .ts 코드 옆에 .d.ts 파일을 만들어서 import하여 타입을 사용하고 있었는데...
빌드 스크립트 및 설정
// package.json
"scripts": {
"build": "tsc"
}
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"outDir": "dist",
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "Node",
"strict": true
},
"include": ["src"]
}
위와 같이 타입스크립트 설정을 하고, 실제 빌드를 하게 되면
빌드 결과
$ npm run build
결과는?
dist/
└── index.js
index.d.ts ❌ 없음!
.ts 파일에서 생성된 타입 선언은 잘 나왔지만, 내가 직접 만든 src/index.d.ts는 복사되지 않았다 🫥
원인 🫤
이 문제의 원인은 의외로 간단하다:
tsc는 직접 만든 .d.ts 파일을 자동으로 outDir로 복사하지 않는다.
즉, src/index.d.ts는 TypeScript가 생성하는 파일이 아니기 때문에 타입 컴파일러는 무시하고 복사조차 하지 않는 것이다.
이건 StackOverflow의 이 답변이 해결의 실마리가 되어주었다.
♥️ 해결 방법
방법은 여러 가지가 있다. 상황에 맞게 선택하면 된다
✅ 방법 1 >> tsc 후 수동 복사
"scripts": {
"build": "tsc && cp src/index.d.ts dist/index.d.ts"
}
혹은 cross-platform 방식으로 cpx나 cpy-cli 같은 패키지를 써도 된다
npm install --save-dev cpx
"scripts": {
"build": "tsc && cpx \"src/**/*.d.ts\" dist"
}
근데 사실, tsc에서 직접 빌드 후 결과물을 만들어주지 않는다는 것은 빌드 관점에서 좀 불안함이 있다.
빌더의 일관성있는 결과물이 아니라고 볼 수 있기 때문이다.
✅ 방법 2 >> declaration이 아닌 files나 include를 이용해 별도 빌드 없이 배포 대상에 포함
만약 타입만 따로 배포하고 싶을 땐, files 필드로 명시하거나 npm 배포 설정을 따로 잡는 방법도 있다.
.d.ts 파일을 tsc 빌드 결과물로 복사하는 대신, npm publish 시점에 그대로 포함되도록 만드는 방법이다.
즉, 타입 선언 파일을 컴파일 과정 없이 배포에 포함시키는 전략이다.
package.json
{
"name": "my-lib",
"version": "1.0.0",
"main": "dist/index.js",
"types": "src/index.d.ts",
"files": [
"dist",
"src/index.d.ts"
],
"scripts": {
"build": "tsc"
}
}
- types: 타입 진입점을 src/index.d.ts로 지정
- files: npm publish 시 포함할 파일 또는 폴더를 명시
→ src/index.d.ts를 직접 지정했기 때문에 npm publish 시 패키지에 포함됨
🌽 files를 쓰지 않고 모든 파일을 배포에 포함시킨 뒤, .npmignore를 사용해 일부 파일만 제거하는 방법도 있긴 하다.
# .npmignore
src/**/*.ts
!src/index.d.ts
✅ 방법 3 >> .d.ts 파일 이름 변경
가장 간단하고 어쩌면 효과적인 방법으로, 그냥 타입 파일의 이름을 index.ts로 변경하거나 타입을 컴포넌트 파일 내부에 같이 선언해 사용하는 것이다.
tsc는 .d.ts만 무시하므로, 이름을 바꿔주면 잘 작동한다.
my-lib/
├── src/
│ ├── index.tsx ← 컴포넌트 파일
│ └── index.ts ← 여기에 타입 선언이 있다
├── package.json
└── tsconfig.json
요약 💘
- TypeScript는 .d.ts 파일을 직접 만들었더라도 자동으로 dist로 복사하지 않는다
- .ts → .d.ts 변환은 tsc가 해주지만, 이미 있는 .d.ts는 별도 취급
- 따라서 필요하다면 cp, cpx, cpy-cli 등으로 명시적 복사를 해주거나 .d.ts 파일 이름을 변경한다
사실 이거, 처음엔 굉장히 당황스러운 문제였다.
타입 파일 결과물이 갑자기 사라져서 다시 빌드 해보기도 하고, tsconfig의 declaration 설정을 바꿔보기도 했지만 소용이 없었다.
알고 보면 TypeScript는 이런 부분까지도 굉장히 ‘엄격’하게 동작한다.
이런 시행착오도 결국 나중에 다른 프로젝트 세팅할 때 큰 도움이 되니까, 기록해두기로 했다.
🍋 참고 자료
https://www.npmjs.com/package/cpx
cpx
Copy file globs, watching for changes.. Latest version: 1.5.0, last published: 9 years ago. Start using cpx in your project by running `npm i cpx`. There are 405 other projects in the npm registry using cpx.
www.npmjs.com
https://stackoverflow.com/questions/56018167/typescript-does-not-copy-d-ts-files-to-build
https://www.typescriptlang.org/tsconfig/#declaration
TSConfig Reference - Docs on every TSConfig option
From allowJs to useDefineForClassFields the TSConfig reference includes information about all of the active compiler flags setting up a TypeScript project.
www.typescriptlang.org
'TypeScript' 카테고리의 다른 글
TypeScript에서 (string & {}) 을 제외한 리터럴 유니언 타입만 남기기 🫥 (0) | 2025.05.31 |
---|---|
TypeScript에서 __brand 패턴 알아보기 ✈️ (타입 안정성 높이기) (0) | 2025.03.30 |
TypeScript에서 객체 타입 비교 및 extends 활용하기 🏘️ (0) | 2025.03.21 |
[typescript] Record를 사용하여 객체 Key 타입 설정하기 🍎 (0) | 2024.11.01 |
타입스크립트에서 기존 코드로부터 타입을 가져오는 법 ReturnType, ComponentProps (1) | 2024.09.18 |