iBetter Books
수정

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/awaitES2017 미만으로 변환하면 프로미스 체인과 스테이트 머신 코드가 생성됩니다.

// 입력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 nullundefined를 명시적으로 처리하도록 강제
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.tsresolve.alias
  • webpack: webpack.config.jsresolve.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은 프로젝트 전반의 동작을 결정하는 핵심 파일입니다. 처음부터 올바르게 설정하면 나중에 수정할 일이 훨씬 줄어듭니다.