728x90
1. 배경
약 1년 전에 작성했던 JavaScript 코드를 리뉴얼 하는 과정에서 새롭게 TypeScript를 도입하기로 하였다.
TypeScript를 도입할 경우, 변수 및 함수에 타입추론을 할 수 있기 때문에 타입에 맞는 메서드를 에디터에 의해 제안받을 수 있어 편리하며, 잘못된 타입을 사용했을 경우 경고가 뜨기 때문에 실수에서 비롯한 버그를 미연에 방지할 수 있기 때문이다.
지금 생각해 보면 타입을 헷갈려 올바르지 않은 내장 메서드를 사용했거나, 혹은 변수명을 잘못 지어 발생한 버그를 해결하는데 많은 시간을 보낸 것을 생각하면 충분히 도입할만한 가치가 있다고 생각하였다.
2. 방법
아래의 명령을 입력하여 기본적인 환경을 도입한다.
npm i -D typescript
npx tsc --init
tsc --init을 수행할 경우 프로젝트 루트 폴더에 tsconfig.json이 생성되는데, 이곳에서 타입스크립트 컴파일에 관한 설정을 할 수 있다.
다양한 옵션이 있는데, 주로 사용하는 옵션들 위주로 기억하면 좋을 것 같다.
- TypeScript 컴파일
일반적으로 컴파일이라 하면, 인간이 이해할 수 있는 고수준의 프로그래밍 언어를 기계가 이해할 수 있는 저수준의 언어로 변환하는 과정을 생각하게 되는데, TypeScript의 컴파일은 TypeScript 코드를 JavaScript로 변환하는 과정이다. (즉 고수준 언어를 또 다른 고수준 언어로 바꾼다고 보면 된다)
우선 TypeScript 공식문서에서도 사용하는 것을 권장하는 옵션이 있는데 바로 'noImplicitAny'와 'strictNullChecks'이다.
- noImplicitAny
암묵적으로 any로 간주되는 타입이 발견되었을 때 오류를 발생시키는 옵션
사실상 TypeScript를 도입하는 이유가 타입을 명확히 지정하기 위함이라, any 타입을 줄이는 것이 목적이라 할 수 있다.
명시적으로 타입이 지정되지 않는다면 암묵적으로 any 타입이 지정되는데, 이 옵션이 활성화되었을 경우 암묵적 any 타입에 오류가 발생하기 때문에 개발자는 어떻게든 타입을 지정하게 된다.
이 옵션을 끌 경우 사실상 TypeScript를 사용하는 의미가 없다고 봐도 될 것 같다.
- strictNullChecks
(null이나 undefine를 제외한) 타입이 지정되었을 경우, 그 값에는 undefined나 null가 할당될 가능성을 제거하는 옵션.
예를 들어 변수 a의 타입이 number라고 지정되었다면 a는 number만 올 수 있으며, undefined나 null을 대입할 수 없다.
또한 특정 함수의 반환 값이 string으로 지정되었다면, 그 함수의 반환 값은 반드시 undefined나 null이 아닌 string이어야 한다.
자꾸만 출력결과에 undefined가 나와 원인을 찾기까지 계속해서 디버깅을 해야 하는 수고를 덜게 만들어주는 옵션이라 볼 수 있다.
2.1. 주요 옵션
본인의 백엔드 코드에서 가져온 tsconfig.json이다.
각각의 항목이 무엇을 의미하는지 살펴보자.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"rootDir": ".",
"moduleResolution": "Node",
"moduleDetection": "force",
"outDir": "./dist",
"allowJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
- target
TypeScript를 JavaScript로 변환할 때, JavaScript의 어떤 버전으로 변환할 것인지 지정하는 옵션이다.
공식문서에 의하면 기본적으로 ES3으로 변환된다고 하는데, 작금의 모든 브라우저는 웬만해서 ES5는 기본적으로 지원하는 편이며, Promise나 async await 등 비교적 최신 JavaScript 문법은 옛날 문법으로 변환할 수 없기 때문에, 작성한 코드가 최신 JavaScript 문법을 사용하는 경우 target을 적어도 ES6로 맞춰야 한다. (개인적으로는 ES2020으로 설정해 놓는다)
만약 최신 문법을 적용했는데 target을 지정하지 않을 경우, 기본 설정이 ES3으로 적용된 상태이기 때문에 에러가 발생할 수 있다.
- module
컴파일된 결과물이 사용할 module 방식을 의미한다.
ES module 방식이나 commonJS 방식을 지정하고자 할 때 사용한다.
만약 ES module 방식을 사용할 경우, package.json에서 type: "module" 설정을 추가적으로 할 필요성이 있다.
- outDir
컴파일한 결과물이 어디에 저장되는지 지정하는 옵션이다.
이 옵션을 사용하지 않는다면, 컴파일된 js 파일은 각각의 ts 파일이 위치한 지점에 생성되는데, 결과적으로 폴더 내부에 ts 파일과 js 파일이 혼재되기 때문에 폴더 내부가 매우 복잡해지는 결과를 초래한다.
따라서 컴파일 결과물을 저장할 수 있는 폴더를 새롭게 지정하는 것이 바람직하다.
보통 프로젝트 root 디렉토리에 위치를 지정하는 편이며, 폴더 이름으로는 build, out, dist 등등을 주로 사용한다.
- rootDir
컴파일할 때, 폴더 구조의 기준점을 정할 때 사용한다.
기본값은 컴파일될 수 있는 모든 파일들(tsconfig.json 및 .d.ts 제외)을 포함할 수 있는 최소 위치이다.
예를 들어 폴더구조가 다음과 같다고 가정해보자.
MyProj
├─ src
│ ├─ a.ts
│ ├─ b.ts
├── tsconfig.json
rootDir이 명시적으로 지정되지 않았다면, 컴파일될 수 있는 모든 파일들을 포함할 수 있는 최소 위치는 src 폴더 내부이기 때문에, 컴파일 이후의 컴파일 결과물에 대한 폴더구조는 src 폴더 내부의 구조와 동일해진다.
(이때 outDir는 ./dist로 지정되었다고 가정함)
MyProj
├─ dist
│ ├─ a.js
│ ├─ b.js
├─ src
│ ├─ a.ts
│ ├─ b.ts
├── tsconfig.json
만약 rootDir을 .로 지정했다면, 컴파일될 수 있는 최소 위치는 root 디렉토리가 되기 때문에 컴파일 결과물에 대한 폴더구조가 아래와 같이 바뀐다.
(컴파일될 수 있는 최소 위치가 root 디렉토리로 변경되었기 때문에, ts 파일이 위치해 있는 src 폴더까지도 컴파일 결과물에 포함된 것을 볼 수 있다)
MyProj
├─ dist
│ ├─ src
│ │ ├─ a.js
│ │ ├─ b.js
├─ src
│ ├─ a.ts
│ ├─ b.ts
├── tsconfig.json
- moduleResolution
https://it-eldorado.tistory.com/124 를 참조
- moduleDetection
TypeScript는 기본적으로 모든 파일을 한 개의 전역 모듈로 인식한다.
즉 개발자가 분리해서 작성한 a.ts와 b.ts가 한 개의 전역 모듈 일부로 취급되기 때문에, a.ts에서 선언한 변수명과 b.ts에서 선언한 변수명이 서로 겹칠 경우 오류가 발생한다. (한 개의 파일에서 변수를 중복해서 사용한 것으로 인식하기 때문)
moduleDetection 옵션을 force로 지정할 경우, 각각의 a.ts 및 b.ts는 독립된 모듈로 취급하게 되어 위와 같은 문제를 해결할 수 있다.
- allowJs
ts 파일 내부에서 js 파일을 import 할 수 있는지 설정하는 옵션이다.
기본값은 false로 되어있다.
프로젝트의 맨 처음 단계부터 TypeScript를 사용할 경우 js파일을 작성할 일이 좀처럼 없기 때문에 사용할 일이 없는 옵션이지만, 만약 프로젝트 중간에 TypeScript로 마이그레이션 할 경우, ts 파일 내부에서 js 파일을 import 하는 경우가 생길 수 있기 때문에 무수한 오류가 발생할 수 있다.
이때 이 옵션을 true로 설정하면 오류를 피할 수 있다.
점진적인 TypeScript 마이그레이션 과정에서 사용하면 좋은 옵션이라 생각한다.
- esModuleInterop
commonJS 방식으로 작성된 모듈을 ES module 방식으로 불러올 필요성이 있을 때(혹은 그 반대의 경우), 따로 변환 문법을 작성할 필요 없이 컴파일 과정에서 자동으로 변환시켜 주는 옵션이다.
- forceConsistentCasingInFileNames
파일 이름의 대소문자를 판별한다.
true로 설정하는 것을 권장한다.
- strict
모든 타입 검사 규칙을 활성화 한다. (앞서 언급한 noImplicitAny 및 strictNullChecks 포함)
strict를 false로 두고, 일부 타입검사 규칙만 활성화 하는 경우도 있겠으나, 그냥 strict를 true로 두는 것을 권장한다.
- skipLibCheck
외부 라이브러리 모듈에 대한 .d.ts 파일 정의가 잘못되어 내가 작성한 프로그램에 오류가 발생하는 일이 생길 수 있는데, 해당 옵션을 true로 설정할 경우 d.ts 파일의 타입 검사를 생략할 수 있다.
(따라서 d.ts 파일은 외부 라이브러리에 대한 타입 정의에만 사용하고, 내부 프로젝트에서 사용하는 타입은 d.ts 파일이 아닌 일반 .ts 파일에서 정의하는 것이 좋다)
- noEmit
컴파일 결과물을 생성할 것인지에 대한 옵션이다.
단순히 TypeScript를 타입 체킹용으로만 사용할 경우 true로 두며, 빌드를 위해 js 코드로 변환된 결과물이 필요할 경우 false로 둔다.
- lib
현재 프로젝트에서 사용할 수 있는 타입 정보를 추가할 때 사용한다.
예를 들어 프론트엔드 개발을 위해 DOM과 관련된 메서드 및 이벤트를 사용하고자 할 경우, lib에 DOM을 추가하면 TypeScript는 DOM에 대한 타입 정보를 제공한다.
단 lib에서 설정하는 옵션은 단순히 타입정보만 제공할 뿐, polyfill 정보는 제공하지 않는다는 점을 기억하자.
따라서 target를 ES5로 맞춘 뒤, lib에는 ES2020를 지정하여 최신 자바스크립트 코드를 작성한다면, 코드 작성시점이나 컴파일 시점에서는 아무런 에러도 발생하지 않으나, ES5까지만 지원하는 환경에서 코드를 실행시킬 경우 최신 문법을 실행시킬 수 없기 때문에 런타임 시점에서 에러가 발생하게 된다.
- include
컴파일할 대상(파일, 폴더)을 지정한다.
glob 패턴을 지원한다.
만약 include를 따로 지정하지 않는다면, tsc 명령어 뒤에 컴파일할 대상을 하나하나 지정해야 되기 때문에 매우 불편하므로, include를 작성하는 것이 권장된다.
- exclude
include에 포함된 것들 중 제외하고 싶은 것을 지정할 때 사용한다.
'TypeScript' 카테고리의 다른 글
enum과 tree shaking (1) | 2023.12.21 |
---|---|
extends vs implements (1) | 2023.10.07 |