보통 Vite + React + TypeScript로 프로젝트를 설정하면 tsconfig.json
, tsconfig.app.json
, tsconfig.node.json
같은 TypeScript 설정 파일들이 생성됩니다. 이 글에서는 각각의 파일이 어떤 역할을 하는지, 그리고 각 컴파일 옵션들이 어떤 의미를 가지는지에 대해 정리해보도록 하겠습니다.
보통 TypeScript를 사용하는 프로젝트의 루트 디렉토리에 위치하는 핵심 설정 파일 입니다. 이 파일의 주된 역할은 TypeScript 컴파일러(tsc)에게 프로젝트의 코드를 어떻게 변환할지 지시하는 역할을 합니다.
TypeScript 프로젝트에서 tsconfig.json
파일은 단순한 설정 파일 이상의 역할을 합니다. 프로젝트의 코드베이스가 어떻게 해석되고, 컴파일되며, 검증될지를 정의하는 심장같은 역할이라고 할 수 있습니다. IDE가 코드를 어떻게 이해하고, 빌드 도구가 어떻게 코드를 변환할지, 그리고 TypeScript 컴파일러가 어떤 규칙으로 타입 체킹을 수행할지 결정합니다.
특히 Vite와 같은 빌드 도구와 함께 사용할 때, 각 환경(브라우저, Node.js)에 맞는 최적의 설정을 분리 적용하는데 유용합니다.
다음과 같은 중요한 기능을 합니다.
app
과 node
로 나누어져 있나요? 👥Vite 기반 프로젝트에서는 tsconfig.json
이 여러 파일로 분리되어 있는데, 이는 다른 환경과 용도에 맞게 TypeScript 설정을 최적화하기 위함입니다.
이렇게 분리함으로써 각 환경에 맞는 최적의 TypeScript 설정을 적용할 수 있습니다.
tsconfig.json
은 TypeScript의 프로젝트 참조(Project References) 기능을 활용하는 일반적인 패턴입니다. 이는 하나의 큰 프로젝트 내에 여러 개의 하위 프로젝트 또는 빌드 컨텍스트를 정의하기 위해 사용됩니다.
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
이 파일은 vite.config.ts 파일을 include하여, Node.js 환경에서 실행되는 설정/빌드 관련 파일들에 대한 타입 검사 및 설정을 담당합니다.
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
이 파일은 src 디렉토리 전체를 include하여, 브라우저 환경에서 실행되는 애플리케이션 코드에 대한 컴파일 및 타입 검사 설정을 담당합니다.
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
},
"include": ["src"]
}
이렇게 설정을 나누면, 각기 다른 환경(Node.js vs 브라우저)이나 목적(빌드 스크립트 vs 앱 코드)에 맞는 별도의 컴파일 옵션을 적용할 수 있습니다. 예를 들어, tsconfig.app.json
에는 브라우저 환경에서 필요한 DOM 라이브러리를 포함시키고, JSX 구문을 처리하기 위한 jsx: "react-jsx"
옵션을 설정하지만, tsconfig.node.json
에서는 이러한 옵션이 필요하지 않습니다.
또한, 프로젝트 참조를 통해 의존성이 있는 하위 프로젝트 간의 빌드를 효율적으로 관리할 수 있습니다.
{
"compilerOptions": { ... },
"include": { ... },
"exclude": { ... },
"files": { ... },
"references": { ... },
"watchOptions": { ... },
"typeAcquisition": { ... },
"extends": "..."
}
include
"include": ["src/**/*"]
일반적으로 “어떤 디렉토리/파일 패턴을 볼 것인가”를 지정합니다. 기본적으로 .ts
, .tsx
, .d.ts
파일만 포함됩니다.
exclude
"exclude": ["node_modules", "build", "dist", "**/*.test.ts"]
include로 포함된 것들 중 “무엇을 제외할 것인가”를 지정합니다. 예시로 보자면, 빌드 결과물 폴더와 테스트 파일을 컴파일 대상에서 제외합니다.
files
"files": ["src/main.ts", "src/app.tsx"]
컴파일할 특정 파일 목록을 명시적으로 지정합니다. 특정 진입점 파일만 명시적으로 지정할 때 유용합니다.
references
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
다른 TypeScript 프로젝트에 대한 참조를 정의합니다. 프로젝트를 논리적인 부분으로 분리하여 각 부분별로 다른 컴파일 설정을 적용합니다.
이는 증분 빌드 성능 향상에 기여하고, 프로젝트 간의 의존성을 명확하게 관리하는 데 도움이 됩니다.
watchOptions
"watchOptions": {
"watchFile": "useFsEvents",
"watchDirectory": "useFsEvents",
"fallbackPolling": "dynamicPriority",
"excludeDirectories": ["node_modules", "dist"]
}
감시 모드(watch mode) 관련 설정을 정의합니다.
watchFile
: 파일 변경 감지 방법(fixedPollingInterval
, priorityPollingInterval
, dynamicPriorityPolling
, useFsEvents
, useFsEventsOnParentDirectory
)watchDirectory
: 디렉토리 변경 감지 방법(useFsEvents
, fixedPollingInterval
, dynamicPriorityPolling
)fallbackPolling
: 파일 시스템 이벤트를 사용할 수 없을 때 폴링 전략 설정synchronousWatchDirectory
: 디렉토리 변경 감지를 동기식으로 수행할지 여부typeAcquisition
"typeAcquisition": {
"enable": true,
"include": ["jquery"],
"exclude": ["node"]
}
자동 타입 정의 파일 획득 관련 설정을 정의합니다. 특정 라이브러리의 타입 정의를 자동으로 획득하도록 할 때 사용됩니다.
enable
: 자동 타입 획득 활성화/비활성화include
: 명시적으로 타입을 획득할 라이브러리exclude
: 타입 획득에서 제외할 라이브러리extends
"extends": "./tsconfig.base.json"
다른 tsconfig 파일의 설정을 상속합니다. 기본 설정을 상속하고 일부만 덮어쓸 때 유용합니다.
compilerOptions
"compilerOptions": { ... }
compilerOptions
객체는 TypeScript 컴파일러의 동작을 세밀하게 제어하는 다양한 옵션을 포함합니다. compilerOptions
은 따로 빼서 조금 더 딥하게 톺아보겠습니다.
TypeScript의 compilerOptions는 컴파일 방식, 모듈 해석, 타입 검사 수준, 출력 방식 등을 정밀하게 제어할 수 있도록 도와주는 설정입니다.
아래는 옵션들을 기능별로 분류하고, 중요한 옵션 3~4개에 한해 부연 설명을 덧붙였습니다.
"compilerOptions": {
"target": "ES2022", // 컴파일된 JavaScript의 ECMAScript 버전
"lib": ["ES2023", "DOM"], // 포함할 라이브러리 파일
"module": "ESNext", // 모듈 코드 생성 방식
"skipLibCheck": true, // 선언 파일(.d.ts)의 타입 검사 건너뛰기
"experimentalDecorators": true, // 데코레이터 지원
"emitDecoratorMetadata": true, // 데코레이터 메타데이터 생성
"allowJs": true, // JavaScript 파일 컴파일 허용
"checkJs": true, // JavaScript 파일 타입 검사
"declaration": true, // .d.ts 파일 생성
"declarationMap": true, // 선언 파일에 대한 소스맵 생성
"sourceMap": true, // 소스맵 파일 생성
"removeComments": true, // 주석 제거
"downlevelIteration": true // 이터레이션 프로토콜 지원
}
target
target
설정은 어떤 표준 라이브러리 정의 파일(lib 옵션과 관련)이 기본적으로 포함될지에도 영향을 줍니다.lib
"lib": ["ES2023", "DOM"]
과 같이 설정하면, ES2023 표준 기능과 웹 브라우저의 DOM API에 대한 타입 정의가 포함됩니다.declaration
.d.ts
타입 선언 파일을 생성할지 여부를 결정합니다.true
로 설정하면 각 TypeScript 파일에 대응하는 선언 파일이 생성됩니다.sourceMap
"compilerOptions": {
"moduleResolution": "bundler", // 모듈 해석 전략
"baseUrl": "./", // 모듈 해석의 기본 디렉토리
"paths": { // 모듈 이름 매핑
"@/*": ["src/*"]
},
"rootDir": "./src", // 입력 파일의 루트 디렉토리
"outDir": "./dist", // 출력 디렉토리
"preserveSymlinks": true, // 심볼릭 링크 해석 방식
"allowImportingTsExtensions": true, // .ts 확장자 임포트 허용
"resolveJsonModule": true, // JSON 모듈 임포트 허용
"isolatedModules": true, // 각 파일을 별도 모듈로 처리
"moduleDetection": "force", // 모듈 감지 방식
"typeRoots": ["./node_modules/@types", "./src/types"], // 타입 정의 파일 위치
"types": ["node", "jest"] // 포함할 타입 패키지
}
rootDir
rootDir: "./src"
이고 outDir: "./dist"
이면, src/components/button.ts
파일은 dist/components/button.js
또는 dist/components/button.d.ts
등으로 출력됩니다.moduleResolution
node
: Node.js의 모듈 해석 알고리즘을 사용합니다.classic
: TypeScript 1.6 이전의 해석 방식을 사용합니다. (옛날 방식이라 거의 사용하지 않습니다.)bundler
: Webpack, Vite와 같은 번들러 환경에 맞춰 모듈을 해석합니다. ESM(EcmaScript Module)을 기본 전제로 하며 .ts, .d.ts, .json 등 일부 확장자 자동 해석이 비활성화되어 있을 수 있습니다.nodenext
: Node.js ESM과 CJS의 해석 규칙을 따릅니다.bundler
또는 node
를 사용합니다.isolatedModules
types
@types/*
패키지 이름 목록을 지정합니다.["node", "jest"]
는 Node.js와 Jest 관련 타입만 포함합니다.node_modules/@types
아래의 모든 패키지가 자동으로 포함됩니다."compilerOptions": {
"strict": true, // 모든 엄격한 타입 체킹 옵션 활성화
"noImplicitAny": true, // 암시적 any 타입 오류 표시
"strictNullChecks": true, // null과 undefined 타입 엄격 체크
"strictFunctionTypes": true, // 함수 매개변수 타입 체크 엄격화
"strictBindCallApply": true, // bind, call, apply 메서드 엄격 체크
"strictPropertyInitialization": true, // 클래스 속성 초기화 체크
"noImplicitThis": true, // this에 암묵적 any 타입 오류 표시
"alwaysStrict": true, // 'use strict' 모드로 파일 생성
"noUnusedLocals": true, // 사용하지 않는 지역 변수 오류 표시
"noUnusedParameters": true, // 사용하지 않는 매개변수 오류 표시
"noImplicitReturns": true, // 함수 내 모든 코드 경로에서 반환 체크
"noFallthroughCasesInSwitch": true, // switch문 fallthrough 오류 표시
"noUncheckedIndexedAccess": true, // 인덱스 시그니처 반환 타입에 undefined 포함
"allowUnreachableCode": false, // 도달할 수 없는 코드 허용 여부
"allowUnusedLabels": false, // 사용되지 않는 레이블 허용 여부
"exactOptionalPropertyTypes": true // 선택적 속성 타입 정확히 해석
}
strict
true
로 설정하면 noImplicitAny
, strictNullChecks
, strictFunctionTypes
등 모든 엄격 옵션이 활성화됩니다.noUnusedLocals
noUnusedParameters
"compilerOptions": {
"noEmit": true, // 출력 파일 생성 안 함
"outFile": "./dist/bundle.js", // 모든 출력을 단일 파일로 병합
"importHelpers": true, // 헬퍼 함수 임포트 (tslib)
"importsNotUsedAsValues": "remove", // 타입 임포트 처리 방식
"preserveConstEnums": true, // const enum 보존
"stripInternal": true, // @internal JSDoc 주석이 있는 선언 제외
"noEmitOnError": true, // 오류 시 출력 파일 생성 안 함
"noEmitHelpers": false, // 헬퍼 함수 생성 안 함
"inlineSourceMap": false, // 소스맵을 출력 파일에 인라인으로 포함
"inlineSources": false, // 소스 코드를 소스맵에 인라인으로 포함
"emitBOM": false, // BOM 출력
"newLine": "lf", // 줄바꿈 문자 지정
"esModuleInterop": true, // CommonJS와 ES 모듈 간 상호 운용성
"preserveValueImports": true // 사용되지 않는 값 임포트 보존
}
noEmit
esModuleInterop
true
로 설정하면 CommonJS 모듈을 ES 모듈처럼 가져올 수 있습니다.import fs from 'fs'
처럼 사용할 수 있게 됩니다. (기존에는import * as fs from 'fs’
필요)noEmitOnError
"compilerOptions": {
"jsx": "react-jsx", // JSX 코드 생성 방식
"jsxFactory": "React.createElement", // JSX 팩토리 함수
"jsxFragmentFactory": "React.Fragment", // JSX 프래그먼트 팩토리
"jsxImportSource": "react" // JSX 임포트 소스
}
jsx
.jsx
파일로 변환합니다. (React.createElement 호출).js
로 변경합니다.jsxFactory
React.createElement
입니다.h
), Vue(h
), 또는 다른 JSX 호환 라이브러리와 함께 사용할 때 변경할 수 있습니다.jsx: "react"
또는 jsx: "react-native"
일 때만 적용됩니다.jsxImportSource
"react"
이지만 @emotion/react
또는 theme-ui
와 같은 다른 라이브러리로 변경할 수 있습니다.jsx: "react-jsx"
또는 jsx: "react-jsxdev"
일 때만 적용됩니다.import { jsx as _jsx } from 'react/jsx-runtime'
와 같은 형태의 임포트 구문을 생성하도록 지시하여, 별도의 임포트 없이도 JSX를 사용할 수 있게 합니다.{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
},
"include": ["src"]
}
target: "ES2020"
: 모던 브라우저를 대상으로 합니다.lib: ["ES2020", "DOM", "DOM.Iterable"]
: 웹 환경에서 실행될 코드를 위한 DOM API와 최신 자바스크립트 기능을 사용합니다.useDefineForClassFields: true
: 클래스 필드를 ECMAScript 표준 방식으로 처리합니다.moduleResolution: "bundler"
: Vite나 Webpack과 같은 번들러와 효율적으로 작동하도록 설정합니다.allowImportingTsExtensions: true
: .ts
나 .tsx
확장자를 가진 파일을 직접 임포트할 수 있습니다.isolatedModules: true
: 각 파일이 독립적으로 트랜스파일될 수 있도록 보장합니다.moduleDetection: "force"
: 모든 파일을 모듈로 취급합니다.noEmit: true
: 타입 체크만 수행하고 JavaScript 파일을 생성하지 않습니다. Vite가 실제 트랜스파일을 담당합니다.jsx: "react-jsx"
: React 17 이상의 자동 JSX 변환을 사용합니다.strict: true
: 모든 엄격한 타입 체크를 활성화합니다.noUnusedLocals: true
: 사용하지 않는 지역 변수에 오류를 발생시킵니다.noUnusedParameters: true
: 사용하지 않는 함수 매개변수에 오류를 발생시킵니다.noFallthroughCasesInSwitch: true
: switch 문에서 불필요한 fallthrough를 방지합니다.{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
target: "ES2022"
: 최신 Node.js 버전을 대상으로 합니다.lib: ["ES2023"]
: 최신 자바스크립트 기능을 사용하지만 브라우저 API는 포함하지 않습니다.moduleResolution: "bundler"
, allowImportingTsExtensions: true
, isolatedModules: true
, moduleDetection: "force"
, noEmit: true
: tsconfig.app.json과 동일한 번들러 모드 설정을 사용합니다.strict: true
, noUnusedLocals: true
, noUnusedParameters: true
, noFallthroughCasesInSwitch: true
: tsconfig.app.json과 동일한 엄격한 타입 체크 및 린팅 설정을 사용합니다.개발을 진행하고, 배포를 진행하면서 “개발자로 살아남으려면 tsconfig에 대해 어느 정도까지 알아야 할까?” 하는 궁금증이 생겼습니다. 처음에는 가볍게 훑어보려던 마음이었는데, 쓰다 보니 생각보다 길어졌네요. 하지만 이렇게 한 번 정리하고 나니, 이전까지 막연하게 느껴졌던 tsconfig에 대한 두려움이 많이 사라진 것 같습니다!
이 글이 저처럼 tsconfig를 어려워하던 분들에게 작은 도움이 되었으면 좋겠고, 이어서 npm 패키지를 배포하며 겪었던 트러블슈팅 경험도 곧 정리해 공유해보겠습니다. 🏃🏻♀️
(++ npm 배포 중 만난 트러블 슈팅 (feat.타입 빌드) 글 작성했습니다!)
tsconfig 해체하기 좋은데요? 👍