본문 바로가기
programming_kr/typescript

tsconfig.json이 하는 역할

by JSsunday 2023. 2. 25.
728x90

typescript로 프로젝트를 진행하면서 '도대체 내가 뭘 하고 있는지 모르겠다'는 생각이 들기 시작했습니다.

여러가지 설정이 있는데 공식 사이트에서 해주는 가이드대로 하기 바빴고 실제로 tsconfig.json 파일이 하는 역할을 제대로 알지 못했습니다.

그래서 tsconfig.json 파일이 어떤 역할을 하는지 알아보게 되었습니다.

일단 tsconfig.json을 만들지 않아도 tsc를 그냥 사용할 수 있습니다.
tsc를 통해서 원하는 .ts파일을 .js로 컴파일 할 수 있습니다.

$ tsc hello.ts

위와 같이 실행 시 동일한 위치에 hello.js 파일이 생성된 것을 확인 할 수 있고 tsc는 tsconfig.json 파일 없이도 바로 사용할 수 있다.

그러면 왜 tsconfig.json을 설정하는 것일까?
그 이유는 매번 명령어에 옵션을 주기가 힘들고 프로젝트에서 일정한 설정을 유지시키기 위해서입니다.

 

tsconfig.json

vscode는 기본적으로 typescript에 대한 intellisense를 지원합니다.

intellisense : 코드 문법 자동완성 기능을 의미


이 intellisense가 .ts 파일을 인식하는 방법을 제어하기 위해서 우리는 tsconfig.json을 작성해야 합니다.

여기서 주의해야 하는 점은 vscode의 typescript intellisense와 tsc는 전혀 상관이 없습니다.
vscode는 intellisense가 .ts 파일을 인식하는 방법을 제어하기 위해서 tsconfig.json을 사용하는 것이고 tsc 또한 typescript를 javascript로 컴파일하는 과정에서 tsconfig.json을 사용하는 것일 뿐입니다.

 

vscode tsc는 분명하게 분리되어 있습니다.

vscode의 경우 typescript project directory의 root에 반드시 tsconfig.json을 넣고 이것을 별도의 세팅을 통해서 위치를 설정하는 방법을 제공하지 않고 있지만 tsc의 경우는 --build 옵션을 통해서 원하는 설정 파일을 지정하게 할 수 있습니다.

즉, vscode 에디터 상에는 에러가 나오지 않지만 정작 tsc를 통해서 컴파일을 시도하면 에러가 날 수도 있고 그 반대의 상황이 나올 수도 있습니다.
이 사실을 반드시 인지하고 있어야만 합니다.

 

tsconfig.json 설정하기

tsconfig.json을 설정하는 이유는 크게 2가지가 있습니다.

  1. vscode의 intellisense가 typescript 처리하는 방법을 제어하기 위해서
  2. tsc가 typescript를 컴파일하는 방법을 제어하기 위해서

tsc를 사용하는 이유는 결론적으로 .ts 파일을 .js로 변환하기 위해서입니다.
그렇다면 tsconfig.json을 설정하는 이유는 전체 typescript 프로젝트 최종 결과물이 어떻데 변환되어 출력되는지를 결정하기 위해서가 되는데 우리가 원하는 것이 tsc를 통해서 실제 결과물을 원하는 것인지 아니면 단순히 vscode에서 가이드라인을 제시하는 것인지를 잘 생각하면서 설정을 작성해야 합니다.

 

최상위 속성

- files

{
  "files": [
    "core.ts",
    "types.ts",
    "scanner.ts",
    "checker.ts"
  ]
}

files를 통해서 우리가 원하는 파일만 tsc가 처리하도록 만들 수 있습니다. 아래와 같이 실행하면 target.ts 파일에 대해서 컴파일이 진행됩니다.

$ tsc target.ts

위 같이 cli에서 특정 파일을 지목해서 사용했다면

$ tsc

위와 같이 파일 목록을 넘겨주지 않고 바로 tsc 만 실행시킬 수 있게 되는데 경로는 tsconfig.json의 위치에서 상대 경로로 작성하면 됩니다.
이 옵션은 타겟 파일이 적을 때 사용하는 게 좋습니다.

※ 우리가 tsconfig.json을 설정하는 이유는 일단 vscode와 tsc를 위한 것인데 그 외의 도구에서 tsconfig.json을 읽어서 동작을 처리하지 않는다면 앞으로 설정하는 것들이 기대한 것과 다르게 동작할 것이라는 것을 유의해야 합니다.

 

- includes

{
  "include": ["src/**/*", "tests/**/*"]
}

include를 통해서 pattern 형태로 원하는 파일 목록을 지정할 수 있습니다.
include와 exclude 모두 glob 패턴을 지원합니다.

  • * 없거나 하나 이상의 문자열과 일치 (디렉터리 구분자 제외)
  • ? 하나의 문자와 일치 (디렉터리 구분자 제외)
  • **/ 단계에 관계없이 아무 디렉터리와 일치

만약 glob 패턴에 파일 확장자를 선언하지 않으면 typescript가 지원하는 확장자만을 포함합니다. 예시로 .ts, .tsx, .d.ts가 있으며 allowJs를 활성화 시키면 .js와 .jsx도 포함됩니다.

include를 지정해도 files에 지정한 파일들은 제외되지 않습니다.

 

- exclude

exclude로 include에 지정한 파일이나 패턴을 제외시킬 수 있습니다.
주의할 점은 include에 지정하지 않은 파일은 적용되지 않습니다.
또한 exclude에 지정하였더라고 import나 /// <reference를 통해서 코드 베이스에 추가될 수 있습니다.

exclude를 지정하지 않으면 ["node_modules", "bower_components", "jspm_packages"]와 outDir에 지정한 경로가 기본값이 됩니다.

 

compilerOptions 속성

{
  "compilerOptions": {
    
  }
}

위의 files, include, exclude는 우리가 원하는 파일을 선택하고 제외하는 설정이라면 compilerOptions는 선택된 파일들을 처리하는 설정이 된다.
굉장히 많은 옵션들이 존재하나 대표적인 것들만 확인해보겠습니다.

 

 

- target

{
  "compilerOptions": {
    "target": "ES2018"
  }
}

target을 통해서 tsc가 최종적으로 컴파일하는 결과물의 문법 형태를 결정할 수 있습니다.
만약 "ES5"를 선택했다면 코드상에 작성한 () => this와 같은 화살표 함수는 모두 function 표현법으로 변환될 것입니다.

기본 값은 "ES3"입니다. 그렇기에 만약 "ES3" 자체에 없는 기능을 코드에 작성하면 컴파일러는 에러를 출력하게 됩니다.
target에 따라서 사용할 수 있는 기능이 제한될 수 있는데 예를 들어서 ES6부터 지원하는 Number.isInteger를 사용하는데 target이 ES6 보다 낮게 설정돼 있다면 에러가 발생합니다.

만약 tsc로 결과물을 출력하지 않는다면 현재 코드에서 사용하는 문법을 기준으로 target을 선택하면 됩니다.

 

- lib

{
  "compilerOptions": {
    "target": "ES2018"
  }
}


lib는 현재 프로젝트에서 사용할 수 있는 특정 기능에 대한 문법(타입)을 추가해주는데 설정하는 target에 따라서 기본으로 설정되는 lib가 달라집니다.

만약 프로젝트가 web browser서 실행돼야 하기에 DOM관련 API를 호출해야 한다고 해봅시다.
그러나 typescript 기본으로 DOM관련 API를 문법에 추가해주지 않습니다.
그렇게에 document.querySelector 같이 코드를 작성하면 "document는 존재하지 않는다."라고나오게 됩니다.
이때 lib를 ["DOM"]과 같이 지정하면 DOM관련 API의 타입을 사용할 수 있게 됩니다.

주의할 점은 lib는 typescript가 그런 문법과 기능이 있다는 것을 알게 해주는 것이지 runtime에 해당 기능을 추가해주는 것이 아니라는 것입니다.
만약 target이 "ES5"인데 Number.isInteger와 같은 기능을 사용하게 되면 에러가 나게 되는데 이때 lib에 "ES6"를 추가하면 더 이상 에러가 발생하지 않고 컴파일도 정상적으로 진행됩니다. 하지만 실제 runtime이 ES5 만 지원한다면 런타임 에러가 발생하게 됩니다.

typescript는 타입 검사와 변환만을 할 뿐이지 polyfill 같은 것은 알아서 해야합니다.

 

- outDir

files와 include를 통해서 선택된 파일들의 결과문이 저장되는 디렉터리를 outDir을 통해서 지정할 수 있습니다.

만약 타입 체크용으로 사용한다면 필요가 없게 됩니다. (vscode에서 intellsense만 사용할 경우)

outFile이라는 속성도 있는데 이는 모든 파일을 하나의 파일로 합쳐서 출력할 경우 지정하는 파일명입니다.
module이 none, system 또는 amd가 아닌 경우 사용할 수 없습니다.
es6 방식의 module을 사용한다고 가정하는 이 글에서는 고려할 대상이 아닙니다.

 

- noEmit

noEmit을 true로 설정하면 최종결과물이 나오지 않게 됩니다.
이를 통해서 단순 타임 체크용으로 사용할 것인지 아니면 tsc를 컴파일용으로 사용할 것인지 지정할 수 있게 됩니다.

 

- declaration

declaration을 true로 설정하게 되면 해당 .ts 파일의 .d.ts 파일 또한 같이 출력물에 포함됩니다. declaration 파일들만 따로 출력하게 하고 싶다면 declarationDir로 별도 지정해 줄 수 있습니다.

 

- emitDeclarationOnly

emitDeclarationOnly가 true라면 출력물에 declaration 파일만 나오게 되며 noEmit과 같이 사용할 수 없습니다.

 

- sourceMap

sourceMap을 true로 지정하면 출력물에 .js.map 이나 .jsx.map 파일을 포함시킵니다.
inlineSourceMap을 true을 지정하면 .js 파일 내부에 source map이 포함됩니다.
두 속성은 같이 사용할 수 없습니다.

 

- typeRoots

typeRoots는 배열로 설정하며 기본값은 ["node_modules/@types"] 입니다.
typescript가 정의돼 있는 type을 찾는 공간이 됩니다.
경로는 tsconfig.json이 있는곳에서 부터 상대 경로로 작성하면 됩니다.

만약 추가적인 type들을 정의한다면 별도의 type 디렉터리를 만들고 그 안에 .d.ts 파일을 만든 뒤 디렉터리를 typeRoots에 추가해 주면 됩니다.
예를 들어서 프로젝트의 루트 디렉터리에 @types/ 디렉터리를 만들었다면 ["node_modules/@types", "@types"]와 같이 설정해 주면 됩니다.

include에 이미 포함된 곳이라면 굳이 추가해줄 필요는 없습니다.
대부분이 잘못 알고 있는 것 중 하나가 프로젝트 내에서 type 파일을 만들게 되면 typeRoots에 추가해야 한다고 알고 있는데 include에 포함돼 있는 .d.ts 파일은 자동으로 typescript가 인식하므로 넣어줄 필요가 없습니다.
include 외에 있는 경우만 추가해주면 됩니다.

또한 추가적인 typeRoots를 설정했는데 "node_modules/@types"을 빼놓게 되면 기본으로 추가가 되지 않으니 문제가 될 수 있음으로 반드시 추가해야 합니다.

typeRoots를 통해서 지정한 type 경로는 일반적으로 외부 라이브러리가 제공하는 모듈의 타입을 정의하기 위해서 사용합니다.
만약 외부 모듈을 가져오려고 할 때 "Could not find a declaration file for module 'xxxx'."와 같은 에러 메세지가 나게 된다면 @types/xxxx 패키지를 설치하거나 직접 정의를 해줘야만 하는데 직접 정의하게 될 경우 이 경로에 작성하면 됩니다.
프로젝트 내의 타입을 정의하기 위해서 사용하지 않기를 바랍니다.

- strict

strict를 true로 지정하면 typescript의 type 검사 옵션 중 strict* 관련된 모든 것을 true로 만들게 됩니다.
strictFunctionTypes, strictNullChecks 등 이와 같은 속성들이 모두 true가 되고 필요에 따라서 선택하여 false로 지정하면 됩니다.

기본값은 false이며 true로 설정하기를 권장합니다.

- module

컴파일된 결과물이 사용하게 될 module 방식입니다.

- moduleResolution

모듈 해결 전략을 설정하는 것인데 여기서 "node"로 설정하는 것이 node.js의 node_modules에서 모듈을 가지고 오는 것이라고 node.js가 사용하는 방식으로 모듈을 찾는다는 의미입니다.

 

- baseUrl

외부 모듈이 아닌 이상 상대 경로로 모듈을 참조해야 합니다.
baseUrl은 외부 모듈이 아닌 모듈들을 절대 경로 참조할 수 있게 해 줍니다.
만약 baseUrl을 "src"로 설정하게 되면 src/를 기준으로 절대 경로로 모듈 참조가 가능해집니다.

 

project/
|-- tsconfig.json
|-- src/
|-- |-- index.ts
|-- |-- lib/
|-- |-- |-- newLib.ts

위와 같이 파일들이 존재한다면 index.ts에서 newLib.ts를 절대 경로로 참조할 수 있게 됩니다.

// import { newLibFunc } from "./lib/newLib";
import { newLibFunc } from "lib/newLib";

newLibFunc();

그러나 vscode에서 intellisense가 제대로 "lib/some"을 찾아주었고 vsc에서도 정상적으로 타입 검사를 통과했더라도 webpack과 같은 외부 bundler를 사용한다면 별도의 설정을 해줘야합니다.
별도의 설정을 하지 않는다면 외부 bundler는 "lib/some"을 node_modules에서 일차적으로 찾을 것이고 찾기를 실패해 에러를 출력할 수밖게 없습니다.

 

- paths

모듈 참조를 baseUrl를 기준으로 다시 매핑시킬 수 있습니다.

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
        "app/*": ["app/*"],
        "config/*": ["app/_config/*"],
        "lib/*": ["lib/*"],
        "tests/*": ["tests/*"]
    },
}

 

baseUrl을 지정하는 것만으로 절대 경로로 모듈 참조를 할 수 있지만 더 상세하게 일을 지어줄 수 있게 됩니다.
이를 통해서 "../../../lib/newLib"과 같이 길고 알아보기 힘든 경로를 "lib/newLib"과 같이 단순하게 만들 수 있게 됩니다.

물론 이 또한 위에서 언급한 것과 같이 외부 bundler를 사용한다면 별도의 설정을 해줘야만 합니다.

 

- isolatedModules

isolatedModules을 true로 설정하면 프로젝트 내에 모든 각각의 소스코드 파일을 모듈로 만들기를 강제합니다.
소스코드 파일에서 import 또는export를 사용하면 그 파일은 모듈이 됩니다.
만약 import / export를 하지 않으면 그 파일은 전역 공간으로 정의됩니다.

isolatedModules을 true로 돼 있다면 모듈로 소스코드를 작성하지 않을 경우 에러를 출력합니다.
만약 babel과 같은 외부 도구를 사용한다면 isolatedModules를 true로 설정하는 것이 좋습니ㅏㄷ.

- esModuleInterop

ES6가 아닌 CommonJS의 경우 module의 export 방법이 다릅니다.
ES6의 경우 export를 할 때 이름을 지정하나 default로 내보내게 되는데 CommonJs의 경우 module.exports = xxx를 통해서 객체를 내보낼 수 있습니다.
이때 import가 호환이 안되게 됩니다.

// import moment from "moment";
import * as moment from "moment";
moment();

CommonJs로 작성된 moment는 import moment from "moment"로 import를 할 수 없어 위와 같이 작성해야만 합니다.
이러한 문제의 불편함을 해소하기 위해서 esModuleInterop를 true를 설정합니다.
typescript가 두 방식의 차이를 자동으로 해소할 것입니다.

- skipLibCheck

외부 라이브러리의 모듈을 참조할 경우 .d.ts 파일에 타입 정의가 잘못돼 있어서 오류가 나는 경우가 가끔씩 있는데 프로젝트 내부에는 문제가 없는데도 불구하고 외부 라이브러리의 타입 정의가 잘못돼서 오류가 나는 경우입니다.
이럴 경우 skipLibCheck를 true로 지정하면 tsc에게 .d.ts 파일의 타입 검사를 생략시킬 수 있습니다.

이렇게 되면 내부 프로젝트에서 정의된 .d.ts 파일까지 검사가 생략돼서 문제가 발생하지 않냐고 할 수도 있는데 내부 프로젝트에서는 .d.ts 파일 정의를 하지 않으면 됩니다.
.d.ts은 내부용이 아니라 외부용으로 사용되는 게 일반적입니다.
일반적인 typescript 프로젝트에서는 type 정의를 .ts 파일에서 하고 export 하고 import 해서 사용하기를 권장합니다.

단순 타입 체크용으로 사용하려면

위의 내용을 보고 우리가 실제 파일을 출력할 것인지 단순 타입체크만 할 것인지에 따라서 필요한 속성이 될 수도 있고 굳이 쓸 필요가 없는 속성이 될 수도 있습니다.
만약 단순 타입 체크만 하는 경우라면 rootDir이나 sourceMap 같은 속성들은 설정할 필요가 아예 없습니다.
그리고 대부분은 rootDir의 경우는 정확히 무엇을 하는지도 모르고 사용하는 경우도 많이 보았는데 noEmit을 true로 설정하거나 tsc --noEmit 옵션을 활용하면 tsc를 빌드 전 단순 타입 체크를 위한 도구로 만들 수도 있습니다.
package.json에서 type check용 script를 만들고 build와 연결해서 사용할 수도 있습니다.

{
  "scripts": {
    "type-check": "tsc --noEmit",
    "build:no-check": "webpack",
    "build": "npm run type-check && npm run build:no-check"
  }
}

 

 

참조
{ tsconfig.json } 제대로 알고 사용하기

 

728x90

댓글