Ch 03. tsconfig.json 완전 정복
tsconfig.json은 TypeScript 컴파일러의 동작을 결정하는 설정 파일입니다. 옵션이 100개가 넘지만, 실무에서 자주 건드리는 것은 20개 이내입니다. 핵심 옵션을 항목별로 정리합니다.
전체 구조
// 새 파일: tsconfig.json{ "compilerOptions": { // 컴파일러 동작 설정 }, "include": ["src"], // 컴파일 대상 파일/폴더 "exclude": ["node_modules"], // 제외할 파일/폴더 "files": [] // 개별 파일 명시 (선택)}
include에 지정하지 않으면 tsconfig.json이 있는 디렉토리 하위의 모든 .ts 파일이 대상입니다. 보통 src만 명시합니다.
target — 출력 JavaScript 버전
{ "compilerOptions": { "target": "ES2022" }}
TypeScript 코드를 어느 버전의 JavaScript로 변환할지 결정합니다.
| 값 | 설명 |
|---|---|
ES5 |
Internet Explorer 호환. 화살표 함수를 function으로 변환 |
ES2015 (ES6) |
대부분의 모던 브라우저 지원 |
ES2020 |
옵셔널 체이닝(?.), nullish 합산(??) 네이티브 지원 |
ES2022 |
class 필드, at() 등 지원. 권장값 |
ESNext |
최신 초안 기능 포함. 주의해서 사용 |
Node.js 22라면 ES2022 또는 ES2023을 사용합니다. 브라우저 대상이라면 번들러(Vite, webpack)가 추가로 변환을 담당하므로 ES2022로 충분합니다.
target이 낮으면 컴파일 결과물이 커집니다. async/await를 ES2017 미만으로 변환하면 프로미스 체인과 스테이트 머신 코드가 생성됩니다.
// 입력async function fetchData() { const result = await getData(); return result;}
// ES5 target 출력 — 코드가 늘어남var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { // ... 생략 });};function fetchData() { return __awaiter(this, void 0, void 0, function* () { const result = yield getData(); return result; });}
// ES2022 target 출력 — 원본과 거의 동일async function fetchData() { const result = await getData(); return result;}
module — 모듈 시스템
{ "compilerOptions": { "module": "NodeNext" }}
생성되는 JavaScript의 모듈 방식을 결정합니다.
| 값 | 설명 |
|---|---|
CommonJS |
require() / module.exports. Node.js 전통 방식 |
ESNext |
import / export. 브라우저 네이티브 |
NodeNext |
Node.js의 ESM + CJS 혼용 지원. Node.js 프로젝트 권장 |
Preserve |
입력 파일의 모듈 방식 유지 |
Node.js 프로젝트에서는 NodeNext를 권장합니다. package.json의 "type": "module" 설정과 함께 사용해야 합니다.
// package.json{ "type": "module"}
NodeNext를 사용할 때 import 경로에 .js 확장자를 명시해야 합니다.
// module: NodeNext 일 때import { greet } from "./utils.js"; // .js 확장자 필수 (실제 파일은 .ts)
처음에는 헷갈리지만, 이것이 ESM의 올바른 방식입니다. TypeScript는 컴파일 시 .ts를 .js로 바꾸므로, 소스에서 .js를 쓰면 컴파일 결과물에서 올바른 경로가 됩니다.
프론트엔드(Vite/webpack 번들러 사용)라면 "module": "ESNext"를 사용합니다. 번들러가 임포트 경로를 처리해줍니다.
moduleResolution — 모듈 탐색 방식
{ "compilerOptions": { "moduleResolution": "NodeNext" }}
TypeScript가 import 경로를 어떻게 찾는지 결정합니다. module 설정과 일치시키는 것이 원칙입니다.
| module | moduleResolution |
|---|---|
CommonJS |
Node |
ESNext (번들러용) |
Bundler |
NodeNext |
NodeNext |
Bundler는 TypeScript 5.0에 추가된 옵션입니다. Vite, webpack, Rollup 사용 시 이 옵션을 씁니다. 확장자 없이 임포트해도 됩니다.
// Vite 프로젝트 권장 설정{ "compilerOptions": { "module": "ESNext", "moduleResolution": "Bundler" }}
strict — 엄격 모드 그룹
{ "compilerOptions": { "strict": true }}
strict: true는 다음 옵션을 모두 켜는 단축키입니다.
| 옵션 | 역할 |
|---|---|
strictNullChecks |
null과 undefined를 명시적으로 처리하도록 강제 |
strictFunctionTypes |
함수 파라미터 타입 공변성/반공변성 검사 |
strictBindCallApply |
bind, call, apply 인자 타입 검사 |
strictPropertyInitialization |
클래스 프로퍼티 초기화 강제 |
noImplicitAny |
any 타입 암묵적 추론 금지 |
noImplicitThis |
this 타입 명시 강제 |
useUnknownInCatchVariables |
catch 절의 변수를 unknown으로 처리 (4.4+) |
alwaysStrict |
모든 파일에 "use strict" 추가 |
반드시 strict: true로 시작하세요. 나중에 켜면 수십, 수백 개의 오류를 한꺼번에 마주합니다.
strictNullChecks 상세
// strictNullChecks: false (기본값)function getLength(str: string) { return str.length;}getLength(null); // 런타임 오류지만 컴파일 통과// strictNullChecks: truefunction getLength(str: string) { return str.length;}getLength(null); // 컴파일 오류: Argument of type 'null' is not assignable to parameter of type 'string'
noImplicitAny 상세
// noImplicitAny: truefunction process(data) { // 오류: Parameter 'data' implicitly has an 'any' type. return data;}// 해결: 타입을 명시function process(data: unknown) { return data;}
outDir과 rootDir
{ "compilerOptions": { "outDir": "./dist", "rootDir": "./src" }}
rootDir은 소스 파일의 최상위 디렉토리, outDir은 컴파일 결과물이 들어갈 디렉토리입니다.
src/
index.ts → dist/index.js
utils/
helper.ts → dist/utils/helper.js
디렉토리 구조를 그대로 유지하며 변환합니다.
declaration — 타입 선언 파일 생성
{ "compilerOptions": { "declaration": true, "declarationDir": "./types" }}
라이브러리를 만들 때 필요합니다. 컴파일 시 .d.ts 파일을 함께 생성합니다.
// src/utils.tsexport function add(a: number, b: number): number { return a + b;}
// types/utils.d.ts (생성됨)export declare function add(a: number, b: number): number;
.d.ts 파일에는 타입 정보만 있고 JavaScript 코드는 없습니다. npm에 패키지를 배포할 때 포함합니다.
애플리케이션(라이브러리가 아닌 경우)에서는 필요 없습니다.
sourceMap — 소스맵 생성
{ "compilerOptions": { "sourceMap": true }}
.js.map 파일을 생성합니다. 소스맵이 있으면 브라우저 개발자 도구나 Node.js 디버거에서 컴파일된 JavaScript 대신 원본 TypeScript 파일 기준으로 오류 위치와 중단점을 볼 수 있습니다.
개발 환경에서는 항상 켜두는 것이 좋습니다.
lib — 빌트인 타입 라이브러리
{ "compilerOptions": { "lib": ["ES2022", "DOM", "DOM.Iterable"] }}
TypeScript에 어떤 전역 API가 존재하는지 알려주는 타입 정의 목록입니다.
| 값 | 포함하는 것 |
|---|---|
ES2022 |
Array.at(), Object.hasOwn() 등 ES2022 표준 |
DOM |
document, window, fetch 등 브라우저 API |
DOM.Iterable |
NodeList 등에 for...of 사용 허용 |
target을 설정하면 lib도 자동으로 설정됩니다. 명시적으로 지정하면 자동 설정을 덮어씁니다.
Node.js 전용 프로젝트라면 DOM이 필요 없습니다.
// Node.js 전용{ "compilerOptions": { "target": "ES2022", "lib": ["ES2022"] }}
paths — 경로 별칭
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils/*": ["src/utils/*"] } }}
../../../utils/helper처럼 긴 상대 경로 대신 @utils/helper처럼 짧은 경로를 쓸 수 있습니다.
// 상대 경로 (불편)import { formatDate } from "../../../utils/date";// 경로 별칭 (깔끔)import { formatDate } from "@utils/date";
주의할 점은 paths는 TypeScript 컴파일러에게만 알려주는 것입니다. 실제 모듈 탐색은 Node.js나 번들러가 담당하므로, 런타임에서도 경로를 해석할 수 있도록 추가 설정이 필요합니다.
- Node.js:
tsconfig-paths패키지 또는--experimental-specifier-resolution - Vite:
vite.config.ts의resolve.alias - webpack:
webpack.config.js의resolve.alias
resolveJsonModule — JSON 파일 임포트
{ "compilerOptions": { "resolveJsonModule": true }}
import config from "./config.json";console.log(config.port); // 타입 추론 포함
JSON 파일을 import할 수 있고, 내용에 맞는 타입이 자동으로 추론됩니다.
esModuleInterop — CommonJS 호환성
{ "compilerOptions": { "esModuleInterop": true }}
CommonJS 방식으로 작성된 패키지(module.exports = ...)를 import 문법으로 쓸 수 있게 합니다.
// esModuleInterop: falseimport * as express from "express"; // 이렇게 써야 함// esModuleInterop: trueimport express from "express"; // 더 자연스럽게
대부분의 프로젝트에서 true로 설정합니다. module: NodeNext와 함께 사용할 때는 이미 포함된 설정입니다.
skipLibCheck — 라이브러리 타입 검사 스킵
{ "compilerOptions": { "skipLibCheck": true }}
node_modules 내의 .d.ts 파일의 타입 검사를 생략합니다. 서드파티 라이브러리의 타입 정의 충돌로 인한 오류를 무시할 때 유용합니다.
빌드 속도를 높여주는 효과도 있습니다. 대부분의 프로젝트 템플릿이 이 옵션을 기본으로 포함합니다.
환경별 권장 설정
Node.js 백엔드 프로젝트
// 새 파일: tsconfig.json{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "rootDir": "./src", "outDir": "./dist", "strict": true, "sourceMap": true, "declaration": false, "resolveJsonModule": true, "skipLibCheck": true }, "include": ["src"], "exclude": ["node_modules", "dist"]}
React + Vite 프론트엔드 프로젝트
// 새 파일: tsconfig.json{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "jsx": "react-jsx", "lib": ["ES2022", "DOM", "DOM.Iterable"], "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src"], "exclude": ["node_modules"]}
React 프로젝트에서는 jsx 옵션이 추가됩니다.
| jsx 값 | 설명 |
|---|---|
react |
React.createElement 호출로 변환 |
react-jsx |
React 17+ 자동 JSX 변환 (React import 불필요) |
preserve |
JSX를 그대로 두고 번들러가 처리 |
라이브러리 배포 프로젝트
// 새 파일: tsconfig.json{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "Bundler", "rootDir": "./src", "outDir": "./dist", "declaration": true, "declarationMap": true, "sourceMap": true, "strict": true, "skipLibCheck": true }, "include": ["src"], "exclude": ["node_modules", "dist", "**/*.test.ts"]}
라이브러리는 declaration: true로 타입 정보를 포함시켜 배포합니다.
추가 엄격 옵션
strict: true 외에 코드 품질을 높이는 옵션들입니다.
{ "compilerOptions": { "strict": true, "noUnusedLocals": true, // 선언했지만 안 쓰는 변수 오류 "noUnusedParameters": true, // 선언했지만 안 쓰는 파라미터 오류 "noImplicitReturns": true, // 함수의 모든 경로에서 return 강제 "noFallthroughCasesInSwitch": true // switch 케이스 fallthrough 금지 }}
// noUnusedLocalsfunction calculate() { const temp = 42; // 오류: 'temp' is declared but its value is never read. return 100;}// noImplicitReturnsfunction getGrade(score: number): string { if (score >= 90) return "A"; if (score >= 80) return "B"; // 오류: Not all code paths return a value.}
이 옵션들을 켜두면 불필요한 코드가 쌓이는 것을 막을 수 있습니다.
tsconfig 상속
대규모 프로젝트에서는 기본 설정을 상속해 사용합니다.
// tsconfig.base.json{ "compilerOptions": { "strict": true, "skipLibCheck": true, "resolveJsonModule": true }}
// tsconfig.json (개발용){ "extends": "./tsconfig.base.json", "compilerOptions": { "sourceMap": true, "noUnusedLocals": false }}
// tsconfig.build.json (빌드용){ "extends": "./tsconfig.base.json", "compilerOptions": { "outDir": "./dist", "declaration": true }, "exclude": ["**/*.test.ts", "**/*.spec.ts"]}
# 개발npx tsc -p tsconfig.json# 빌드npx tsc -p tsconfig.build.json
정리
실무에서 가장 자주 설정하는 옵션 목록입니다.
| 옵션 | 권장값 | 이유 |
|---|---|---|
target |
ES2022 |
모던 Node.js/브라우저 대응 |
module |
NodeNext / ESNext |
환경에 맞게 |
moduleResolution |
NodeNext / Bundler |
module과 일치 |
strict |
true |
타입 안전성 최대화 |
outDir |
./dist |
빌드 결과물 분리 |
rootDir |
./src |
소스 경로 명시 |
sourceMap |
true |
디버깅 편의 |
skipLibCheck |
true |
빌드 속도 향상 |
resolveJsonModule |
true |
JSON 임포트 허용 |
esModuleInterop |
true |
CJS 패키지 호환 |
tsconfig.json은 프로젝트 전반의 동작을 결정하는 핵심 파일입니다. 처음부터 올바르게 설정하면 나중에 수정할 일이 훨씬 줄어듭니다.