본문 바로가기
Development/JS & TS

[Typescript] 상황별 tsconfig.json 설정하기

by 대범하게 2025. 3. 2.
반응형

들어가면서

Typescript를 사용하는 프로젝트에서 가장 중요한 부분 중 하나는 tsconfig.json 설정이다. 새로운 프로젝트를 0부터 시작하거나, 기존 템플릿(Scaffolded Project)에서 시작하는 경우에 tsconfig.json을 세세히 다루지 않는 경우가 많다. 하지만 프로젝트가 커질수록, 프로젝트 성격에 맞게 tsconfig.json을 구성하는 것이 중요하다. 

 

이번 글에서는 각 패키지나 프로젝트의 특성에 맞게 tsconfig.json을 작성 예시와 경험에 따라 설정을 정리해보고자 한다.

1. 상황별 tsconfig.json 구분하기

먼저, 상황별 tsconfig.json부터 살펴보자.

 

Typescript 프로젝트를 설정할 때, 어떤 환경에서 실행될 것인지에 따라 tsconfig.json이 달라질 수 있다. 예를 들어, 백엔드, 프론트엔드, 패키지 개발, 브라우저 API 활용 유무 등 각 환경에 따라 최적의 설정이 존재한다. 아래 그림은 상황별 tsconfig.json을 잘 정리한 라이브러리(https://github.com/total-typescript/tsconfig)를 도식화한 것이다.

 

tsconfig.json from https://github.com/total-typescript/tsconfig

 

위 그림에 살펴보면 특정 상황들을 제외한 대부분의 설정은 비슷한 것으로 보인다. 

특정 상황은 다음과 같이 구분할 수 있을 것 같다. 백엔드(Node.js)에서의 tsc를 사용한 컴파일, 프론트엔드(React, Vue 등)에서 번들러를 이용한 빌드, 라이브러리나 패키지 개발, 브라우저 API 활용, 그리고 모노레포 구조에서의 개발 등이다. 

 

위의 https://github.com/total-typescript/tsconfig 는 배포된 npm 패키지를 설치하고 다음과 같이 간편하게 설정할 수 있다. 

// install
npm install --save-dev @total-typescript/tsconfig

// tsconfig.json
{
  "extends": "@total-typescript/tsconfig/bundler/dom/app"
}​

 

비록 tsc vs 번들러 혹은 dom vs no-dom 으로 구분하는 것이 해당 패키지를 사용하는 입장에서는 편하지만, 직접 설정해보는 입장에서 와닿지 않는 지점이 있어 tsconfig.json를 설정하는 맥락을 훑으며 각 상황별 설정에 따라 분류되도록 글을 구성해보려고 한다. 

 

2. tsconfig.json 설정 방식 결정하기

tsconfig.json 설정 방식을 고려하기 앞서, 설정 방식은 여러 오픈소스와 업무에서 채택되고 있는 모노레포를 바탕으로 설명하고자 한다.

설정 방식을 결정한다는 것 자체가 단일 레포에서의 고려 사항이 아니라고 생각한다. 단일 레포에서의 tsconfig.json은 위에 언급한 상황에 맞는 설정을 하면 큰 문제 없이 설정이 가능하기 때문이다.  

 

모노레포는 여러 프로젝트가 함께 존재하며, 각 프로젝트의 특성에 따라 tsconfig.json 설정 방식이 조금씩 달라진다. 그 결과, 불필요하게 방대한 설정을 포함한 뚱뚱한 tsconfig.json 파일을 마주하는 경우가 많다. tsconfig.json 설정 방식은 정답이랄게 없기에 여러 오픈소스를 살펴본 결과 다음 두 가지 방식으로 나눠 볼 수 있었다. 

 

1) 레포지토리의 최상위 tsconfig.json을 상속받아(extends) 구성하는 방식

├── A프로젝트/
│   └── tsconfig.json
├── B프로젝트/
│   └── tsconfig.json
└── tsconfig.json
	레포지토리의 최상위 TypeScript 설정 파일
	공통 설정을 정의하며, 개별 프로젝트(`A프로젝트`, `B프로젝트`)에서 이를 `extends`하여 사용할 수 있음

 

위 방식을 사용한 오픈소스로는 turborepo가 있다.

turborepo 레포지토리를 보면 최상단의 tsconfig.json을 각 apps 혹은 example의 프로젝트에서 extends하여 사용 중이다.  https://github.com/vercel/turborepo

 

GitHub - vercel/turborepo: Build system optimized for JavaScript and TypeScript, written in Rust

Build system optimized for JavaScript and TypeScript, written in Rust - vercel/turborepo

github.com

 

2) tsconfig.json 구성을 패키지로 만들어 활용한 방식

├── apps/
│   └── A프로젝트/
│       ├── package.json 
│       │   `dependencies`에 `@workspace/ts-config` 패키지를 포함하여 TypeScript 설정을 공유함  
│       └── tsconfig.json 
│           `extends`를 사용하여 `@workspace/ts-config` 패키지 내의 설정을 가져옴  
│           프로젝트에 맞게 필요한 옵션을 추가하거나 오버라이드할 수 있음  
└── packages/
    └── ts-config 
        ├── package.json 
        │   이 패키지는 TypeScript 설정을 모듈화하여 여러 프로젝트에서 공통적으로 사용할 수 있도록 제공함
        ├── base.json 
        │   기본 TypeScript 설정을 정의하며, 다른 설정 파일들이 이를 확장하여 사용함
        ├── library.json
        │   라이브러리 개발에 적합한 TypeScript 설정을 포함하며, `base.json`을 확장함
        ├── node.json 
        │   Node.js 환경에서 사용할 TypeScript 설정을 포함하며, `base.json`을 확장함
        └── react.json  /
            React 프로젝트에서 사용할 TypeScript 설정을 포함하며, `base.json`을 확장함

 

(*참고: 위 트리에서 @workspace는 모노레포 구조를 표시하기 위한 임의의 네임스페이스이다. 실제 프로젝트에서는 조직이나 패키지 네임스페이스에 맞게 변경하면 된다.)

 

위 방식을 사용한 오픈소스로는 pnpm이 있다. pnpm 레포지토리를 살펴보면 __utils__ > tsconfig 패키지를 구성하여 사용되고 있다. https://github.com/pnpm/pnpm/tree/main/__utils__/tsconfig

 

pnpm/__utils__/tsconfig at main · pnpm/pnpm

Fast, disk space efficient package manager. Contribute to pnpm/pnpm development by creating an account on GitHub.

github.com

 

tsconfig.json 설정이 많기도 하며 가독성이 꽤나 좋지 못하여 카테고리화 하는게 더 유리하다고 생각한다. 개인적으로 tsconfig.json 구성을 패키지로 만들어 활용한 방식이 패키지로써 가독성도 좋고, 유지보수에도 좋다고 생각하여 위 트리 구조로 설정 방식을 결정하였다. 

 

3. 상황별로 json을 분리하여 tsconfig 패키지 구성하기

최상단의 사진보다 조금 간결해진 모습이다..! 특정 상황과 관계없는 옵션을 base.json으로 분리하면서 json 자체의 크기가 작아진 것을 볼 수 있다.

 

위에서 택한 tsconfig를 패키지로 만들어 활용한 방식을 바탕으로 구성하였다.

다음과 같이 기본 설정을 구성하는 base.json, 라이브러리를 개발할 수 있도록 구성하는 library.json, node.js 환경용 node.json, 브라우저 API 개발 및 react를 사용하는 react.json으로 이루어져있다. packages/ts-config에서 공통 설정을 관리하면 각 프로젝트마다 일일이 설정을 작성할 필요 없이 일관된 환경을 유지할 수 있다. 특정 설정을 변경해야 할 경우, ts-config 패키지만 수정하면 모든 프로젝트에 자동으로 반영된다.

 

먼저, 공식 typescript 문서가 훨씬 정확하다는 것을 말하고자 한다. https://www.typescriptlang.org/tsconfig/

하지만, 미래의 나에게 선물하는 tsconfig.json 설정 설명이다 ..!

 

1) base.json : 기본 설정 (모든 프로젝트 공통)

tsconfig.json의 기본 설정을 구성하는 base.json 파일이다. library.json, node.json, react.json이 base.json을 상속받도록(extends) 구성되어있다. 아래 설정은 조금 빡세게^ 구성해보았다.

// base.json
{
    "$schema": "https://json.schemastore.org/tsconfig",
    "compilerOptions": {
        "baseUrl": ".",
        "isolatedModules": true,
        "resolveJsonModule": true,
        "allowImportingTsExtensions": true,
        "noEmit": true,
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noUnusedLocals": true,
        "noImplicitOverride": true,
        "noUnusedParameters": true,
        "noUncheckedIndexedAccess": true,
        "noFallthroughCasesInSwitch": true,
        "allowJs": false,
        "skipLibCheck": true
    }
}

 

baseUrl: "."

모듈 이름의 기준 디렉토리를 지정한다. 프로젝트의 루트 디렉토리를 기준으로 상대 경로 임포트를 해석한다.

상대경로 없이 모듈을 임포트할 때(예: import { Button } from 'components/Button') 기준이 되는 경로이다.

 

isolatedModules: true

각 파일을 별도의 모듈로 취급하도록 설정한다.

TypeScript가 아닌 다른 트랜스파일러(Babel 등)와 함께 사용할 때 유용하다.

타입 정보만 포함된 파일 생성을 방지하고, 모든 파일이 독립적으로 컴파일될 수 있도록 한다.

 

resolveJsonModule: true

.json 파일을 모듈로 임포트할 수 있게 한다.

예: import data from './data.json'와 같이 JSON 파일을 직접 임포트하여 타입 안전성을 확보할 수 있다.

 

allowImportingTsExtensions: true

.ts, .tsx 확장자를 가진 파일을 직접 임포트할 수 있게 한다.

주로 noEmit: true와 함께 설정하여 사용한다.

 

noEmit: true

TypeScript 컴파일러가 JavaScript 파일을 생성하지 않고 타입 검사만 수행하도록 한다.

Vite, Webpack 같은 번들러를 사용할 때 유용하다. 번들러가 트랜스파일을 담당하고 TypeScript는 타입 체킹만 수행한다.

 

esModuleInterop: true

CommonJS 모듈을 ES 모듈처럼 임포트할 수 있게 해주는 설정이다.

예: import React from 'react'와 같은 임포트 방식을 가능하게 한다.

 

allowSyntheticDefaultImports: true

기본 내보내기(default export)가 없는 모듈에서도 import x from 'y' 구문을 사용할 수 있게 한다.

타입 검사 수준에서만 작동하며, 런타임 동작을 변경하지 않는다.

 

forceConsistentCasingInFileNames: true

파일 이름의 대소문자를 일관되게 사용하도록 강제한다.

대소문자를 구분하는 파일 시스템과 구분하지 않는 파일 시스템 간의 문제를 방지한다.

 

strict: true

모든 엄격한 타입 검사 옵션을 활성화한다.

이 설정 하나로 noImplicitAny, strictNullChecks, strictFunctionTypes 등 여러 엄격한 검사 옵션을 켤 수 있다.

 

noUnusedLocals: true

사용되지 않는 지역 변수에 대해 오류를 보고한다.

코드 품질을 향상시키고 불필요한 변수 선언을 줄일 수 있다.

 

noImplicitOverride: true

상속 받은 메서드를 오버라이딩할 때 명시적으로 override 키워드를 사용하도록 강제한다.

부모 클래스의 메서드 시그니처가 변경될 때 자식 클래스의 오버라이딩 메서드가 적절히 업데이트되도록 보장한다.

 

noUnusedParameters: true

함수에서 사용되지 않는 매개변수에 대해 오류를 보고한다.

코드의 명확성을 높이고 불필요한 매개변수를 제거하도록 유도한다.

 

noUncheckedIndexedAccess: true

인덱스 접근 시 해당 요소가 존재하지 않을 수 있다고 가정하고 undefined를 유니온 타입으로 추가한다.

배열이나 객체에 접근할 때 경계를 벗어난 접근을 방지할 수 있다.

예: arr[0]의 타입은 T가 아닌 T | undefined가 된다.

 

noFallthroughCasesInSwitch: true

switch 문에서 case 간 폴스루(fall-through)가 발생할 경우 오류를 보고한다.

각 case 문이 break, return, 또는 throw로 끝나도록 강제한다.

 

allowJs: false

JavaScript 파일(.js)의 컴파일을 허용하지 않는다.

TypeScript 파일만 프로젝트에 포함되도록 한다.

 

skipLibCheck: true

선언 파일(*.d.ts)의 타입 검사를 건너뛴다.

빌드 시간을 단축시킬 수 있지만, 서드파티 라이브러리의 타입 오류를 놓칠 수 있다.

 

2) library.json : 라이브러리 개발용 설정

// library.json
{
    "$schema": "https://json.schemastore.org/tsconfig",
    "extends": "./base.json",
    "compilerOptions": {
        "declaration": true,
        "declarationMap": true,
        "declarationDir": "dist/types",
        "composite": true,
    },
}

 

declaration: true

컴파일 시 .d.ts 타입 선언 파일을 생성한다.

라이브러리를 개발할 때 필수적인 설정으로, 사용자가 라이브러리의 타입을 활용할 수 있게 한다.

 

declarationMap: true

.d.ts.map 소스맵 파일을 생성한다.

타입 선언 파일의 원본 소스 위치로 이동할 수 있어 디버깅과 개발 경험을 향상시킨다.

 

declarationDir: "dist/types"

생성된 타입 선언 파일(.d.ts)을 저장할 디렉토리를 지정한다.

빌드 결과물을 구조화하여 타입 선언 파일만 별도의 디렉토리에 모을 수 있다.

 

composite: true

프로젝트가 다른 프로젝트에 의해 참조될 수 있도록 한다.

TypeScript의 프로젝트 참조 기능을 사용할 때 필요한 설정으로, 증분 빌드를 가능하게 한다.

 

declaration: true를 자동으로 요구하며, 빌드 성능을 최적화한다.

 

3) node.json : Node.js 환경용 설정

// node.json
{
    "$schema": "https://json.schemastore.org/tsconfig",
    "extends": "./base.json",
    "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "NodeNext",
    }
}

 

target: "ESNext"

컴파일된 JavaScript 코드의 ECMAScript 버전을 지정한다.

"ESNext"는 항상 최신 ECMAScript 표준을 사용하도록 설정한다. Node.js의 최신 버전은 최신 ECMAScript 기능을 지원하므로 적합한 설정이다.

 

module: "ESNext"

모듈 코드 생성 방식을 지정한다.

"ESNext"는 최신 ECMAScript 모듈 시스템(ESM)을 사용한다. package.json에 "type": "module"을 설정하면 Node.js에서 ESM을 사용할 수 있다.

 

moduleResolution: "NodeNext"

모듈 해석 전략을 지정한다. "NodeNext"는 Node.js의 ESM 모듈 해석 알고리즘을 사용한다.

package.json의 "exports" 필드 지원 등 최신 Node.js의 모듈 해석 방식을 적용한다.

 

4) react.json : React 프로젝트용 설정

// react.json
{
    "$schema": "https://json.schemastore.org/tsconfig",
    "extends": "./base.json",
    "compilerOptions": {
        "target": "ESNext",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "lib": [
            "DOM",
            "DOM.Iterable",
            "ESNext"
        ],
        "jsx": "react-jsx"
    }
}

 

target: "ESNext" & module: "ESNext"

앞서 설명한 내용과 동일하게 최신 ECMAScript 표준을 사용한다. 모던 브라우저와 번들러를 타겟으로 할 때 적합하다.

 

moduleResolution: "bundler"

Webpack, Vite 같은 번들러 환경에 최적화된 모듈 해석 전략을 사용한다.

TypeScript 4.7부터 도입된 설정으로, ESM과 CJS 간의 상호 운용성을 개선한다.

 

lib: ["DOM", "DOM.Iterable", "ESNext"]

컴파일에 포함될 라이브러리 파일을 지정한다. "DOM"과 "DOM.Iterable"은 브라우저 API에 접근할 수 있게 한다. "ESNext"는 최신 ECMAScript 기능의 타입 정의를 포함한다. React 웹 애플리케이션에 필요한 기본 라이브러리 세트이다.

 

jsx: "react-jsx"

JSX 코드 생성 방식을 지정한다. "react-jsx"는 React 17 이상에서 도입된 새로운 JSX 변환을 사용한다. 이 설정으로 import React from 'react'를 명시적으로 작성하지 않아도 된다.

 

방대한 양의 옵션들을 살펴보았고, 위 구조에 따라 구성해놓은 레포를 참고해보면 될 것 같다. 

위의 base.json, library.json, node.json, react.json으로 구성되어있다. https://github.com/choidabom/devom/tree/master/packages/ts-config

 

4. 모노레포에서의 설정 적용 방법: 분리된 json 용도에 맞게 tsconfig.json 설정하기

이제 분리한 tsconfig 패키지를 제대로 적용하는 것이 중요하다. 아래 트리는 '설정 방식'을 정할 때 작성한 트리에서 B프로젝트가 추가되었다. B프로젝트는 A프로젝트에서 참조하여 사용되는 컴포넌트이자, 라이브러리로서 동작해야한다. 

├── apps/
│   └── A프로젝트/
│       ├── package.json 
│       └── tsconfig.json 
│   └── B프로젝트/ -> A 프로젝트에서 import 될 패키지
│       ├── package.json 
│       └── tsconfig.json 
└── packages/
    └── ts-config 
        ├── package.json 
        ├── base.json 
        ├── library.json
        ├── node.json 
        └── react.json

 

1) 단일 프로젝트에서의 tsconfig.json 설정

모노레포 내의 개별 프로젝트는 tsconfig.json을 사용하여 공통 설정을 상속받아 적용할 수 있다.

예를 들어, A프로젝트는 React 기반 애플리케이션이므로 react.json 설정을 확장하여 사용한다. 

// apps/A프로젝트/tsconfig.json
{
  "extends": "@workspace/ts-config/react.json",
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

 

1. extends

"@workspace/ts-config/react.json": react.json의 공통 설정을 확장하여 적용한다. 

2. include

["src/**/*"]: src 디렉토리 내의 모든 TypeScript 파일을 포함한다.

3. exclude

["node_modules", "dist"]: node_modules 및 dist 디렉토리는 컴파일 대상에서 제외하여 불필요한 파일 처리를 방지한다.

 

2) 다중 프로젝트 의존성이 있는 경우의 설정

B프로젝트는 A프로젝트에서 import하여 사용하는 라이브러리이므로, React 기반 컴포넌트 라이브러리로서 동작해야한다. 따라서 react.json과 library.json 두 가지 설정을 모두 상속받는다. 이 설정은 컴포넌트이자 라이브러리로 사용되어야 하는 프로젝트에 적합한 tsconfig.json 구성이다.

// apps/B프로젝트/tsconfig.json
{
  "extends": ["@workspace/ts-config/react.json", "@workspace/ts-config/library.json"],
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

 

1. extends

@workspace/ts-config/react.json과 @workspace/ts-config/library.json 두 가지 기본 설정을 확장하여 React 컴포넌트와 라이브러리 둘 다의 특성을 지원한다. B프로젝트처럼 라이브러리로 사용되는 프로젝트는 library.json을 추가로 상속받아 라이브러리 특화 설정을 적용할 수 있다. 반면, A프로젝트처럼 애플리케이션 역할을 하는 프로젝트는 react.json만 상속하여 불필요한 설정을 줄일 수 있다.

2. include

["src/**/*"]: src 디렉토리 내의 모든 TypeScript 파일을 포함한다.

3. exclude

["node_modules", "dist"]: node_modules 및 dist 디렉토리는 컴파일 대상에서 제외하여 불필요한 파일 처리를 방지한다.

 

마무리

이렇게 상황별로 tsconfig.json 설정하는 방식과 옵션을 정리하였고, 옵션의 설명이 조금 길어졌지만 하나씩 파볼 수 있었던 유의미한 시간이었다. 모노레포를 모르는 읽는 이에겐 조금 불친절한 글이지 않을까 싶지만, 겪었던 경험을 공유하고자한 목적이 컸다. tsconfig 패키지로 분리하면서 사내 프로젝트와 개인 프로젝트의 뚱뚱한 tsconfig.json들의 다이어트를 성공적으로 이끄는 경험을 하였다. (속이 시원!) 

 

참고

https://github.com/total-typescript/tsconfig

https://www.typescriptlang.org/tsconfig/

반응형