개요
이 글을 읽기 전에 타입스크립트에 대한 기본적인 개념이 잡혀있어야 합니다. 타입스크립트에 대한 필요성, 기능, 문법 등은 다른 글을 참조해주세요.
트랜스파일링이란 코드를 코드로 변환한다는 뜻입니다. 실제로 node.js 에서 실행할 때에는 js 파일만 해석할 수 있는데, 그래서 ts 파일을 모두 js 파일로 변경해야 합니다. 타입스크립트 컴파일러인 tsc 가 그 역할을 합니다. typescript 를 설치하면 함께 설치되는 컴파일러인 tsc
는 문법 검사 및 코드 변환에 대한 책임만 있고 추가적인 작업을 하지 않습니다. 예를 들어 pug 파일이나 graphql 파일이 소스 폴더에 포함되어 있어도, 이것들을 전혀 건드리지 않기 때문에 copyfiles
등을 이용해 빌드가 완료된 폴더에 따로 복사를 해주어야 합니다.
ts-node
는 node 명령어를 사용하는 것처럼 typescript 를 쓸 수 있도록 하는 모듈입니다. 기존에 node
명령어로 실행하던 것을 그대로 ts-node
로 바꿔주기만 하면 됩니다. 아주 편리합니다! 그런데, 아까 타입스크립트는 파일을 변환한 뒤에 실행해야 한다고 하였죠? 이 ts-node
는 변환되는 js
파일의 디렉터리 경로를 그냥 임시 폴더 경로로 해둡니다. 그러므로 어디에 변환되는지는 사용자 입장에서 전혀 신경쓸 필요가 없어집니다. 이 ts-node
는 tsconfig.json
의 설정을 읽어서 수행합니다. 다만, node
와 동작 방법이 유사하기 때문에 (index.js
로부터 require
로 필요한 소스 파일들을 검색해나감) tsconfig.json
파일 내의 include
, exclude
, files
옵션을 무시하는 게 기본 세팅입니다.
어쨌거나 저쨌거나 생각보다 타입스크립트 세팅이 쉽지 않았습니다. 네… 차라리 NestJs 와 같이 처음부터 typescript 를 지원하는 프레임워크를 쓰시기 바랍니다.
조건
- Incremental Build (점진적인 빌드), 혹은 watch 기능, 혹은 둘 다 지원되어야 한다 : Incremental Build 기능이란 컴파일과 관련한 기록파일을 남겨둠으로써
tsc
가 필요한 것만 효율적으로 컴파일할 수 있도록 하는 기능입니다. watch 기능이란 파일의 변경을 감지하여 자동으로 새로고침 해주는 기능입니다. 타입스크립트 문법 체크는 변경사항이 생길 때마다 필요합니다. 하지만 프로젝트의 규모가 작지 않기 때문에 변경사항이 생길 때마다 모든 파일의 문법 검사를 한다면 한 글자를 고쳐도 몇 초를 기다려야 할 수 있습니다. 이는 다소 생산성이 떨어지게 되겠지요. - js, ts 파일 외의 파일도 복사되어야 한다 : 소스코드 내에는
pug
템플릿 파일이나.graphql
정의 파일 등이 존재했습니다. 이 파일을 개발/프로덕션 환경에서 잘 읽어와야 했습니다. - 절대경로 축약(Path Alias)이 제대로 동작해야 한다 : 마찬가지로 생산성을 위해
../../
와 같은 경로 설정을 줄여보고자 절대 경로 Alias 를 만들어서 코딩을 진행했습니다. (예:import abc from '../../loader';
–>import abc from '@/loader';
) - Babel 을 사용하지 않는다 : 왜냐하면 필자가 잘 모르기 때문이지요. 경로 등을 변환하기 위해서 사용해볼 수도 있으나 너무 중간 과정이 많아지는 걸 바라지는 않았습니다.
조건을 충족시키기
1번의 조건에 의해서 nodemon
은 후보에서 제외됩니다. nodemon 은 변경사항이 생길 때마다 프로그램을 재시작시킵니다. 정확하게 테스트를 해본 건 아니지만 tsc
혹은 ts-node
의 incremental build
기능과 협업하리라 기대하기 힘들었습니다. tsc
자체에는 --watch
옵션이 있고, ts-node
는 watch 기능과 관련하여 더 잘되어있을 것 같은 ts-node-dev
가 이미 존재하기에 그것을 사용하기로 했습니다. ts-node-dev
는 ts-node
와 node-dev
를 합친 거라고 합니다. node-dev
는 nodemon
과 같이 watch 와 관련된 모듈입니다.
2번, 3번 조건에 의해서 tsc 는 오로지 문법 체크 용도로 사용하고자 합니다. 왜냐하면 tsc 에는 절대경로를 상대경로로 바꿔주는 기능도 없고 소스파일이 아닌 다른 파일을 복사해주는 기능도 없기 때문입니다. 프로덕션 환경에서도 실행은 ts-node
로 하되, 조금이라도 실행 속도를 보장하기 위해 --transpile-only
플래그를 붙여줄 예정입니다. 이 플래그를 붙이면 문법 검사 등은 하지 않고 변환만 척척 수행합니다.
실제로 해보기
Typescript Path Alias 를 지원하기 위해 tsconfig-paths
를 미리 해둡니다. 물론 typescript
, ts-node
, ts-node-dev
도 미리 설치해주세요.
npm install typescript ts-node ts-node-dev -g
npm init
tsc --init
npm install --save-dev tsconfig-paths
tsconfig.json
파일은 아래와 같이 설정했습니다.
{
"compilerOptions": {
"incremental": true,
"checkJs": true,
"baseUrl": ".",
"module": "CommonJS",
"target": "esnext",
"moduleResolution": "node",
"sourceMap": true,
"paths": {
"@/*": ["src/*"],
"@config/*": ["config/*"]
},
"outDir": "ts-build",
"diagnostics": true,
"esModuleInterop": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"listEmittedFiles": true
},
"include": ["src/**/*", "config/**/*"],
"exclude": ["node_modules", "**/*.spec.ts", "**/*.spec.js"]
}
tsconfig.json
파일의 세부 설정은 레퍼런스를 참조해주시기 바랍니다.
아래는 package.json
파일입니다. 여기에 3개의 스크립트를 만들었습니다.
{
...
"scripts": {
"start-dev": "NODE_ENV=development ts-node-dev --transpile-only --respawn -r tsconfig-paths/register --watch ./src/**/*.pug,./src/**/*.graphql --trace-warnings --trace-uncaught -- ./src/index.js",
"start": "NODE_ENV=production ts-node --transpile-only -r tsconfig-paths/register src/index.js",
"watch": "tsc --watch",
},
...
}
개발할 때에는 2개의 터미널을 켜놓고 npm run start-dev
와 npm run watch
를 따로 수행합니다. start-dev
는 개발 모드 서버를 실행시키는 용도이고 watch
는 문법 검사 용도입니다. 프로덕션은 npm run start
를 실행시킵니다. 속도는 꽤 괜찮게 나옵니다.
NODE_ENV=production
혹은 NODE_ENV=development
로 환경 변수를 설정해두면 코드 내에서 process.env.NODE_ENV
에 접근해서 개발/프로덕션 환경에 대한 코드를 분리시킬 수 있습니다. 윈도우 사용자는 cross-env 등을 사용하여 환경 변수를 설정하면 됩니다.
남은 미션
아직 test 관련되어 한번도 실행해보지 않았다는 게 함정… mocha.. 너는 정령 테스트를 제대로 수행해낼 수 있을 것인가!