TypeScript로 개발하는 express app setting하기 (1)

최근 회사 동료들끼리 진행했던 side project에서 TypeScript를 사용해 보고 그 매력에 푹 빠졌습니다(저번에 약속했던 flow와 TypeScript의 차이점을 포스팅 할 수도 있겠네요). 그러던 차에, 마침 잊고있었던 backend를 공부하기위해 시작한 express를 TypeScript로 개발하기로 마음 먹었습니다.

개인적으로 JS 혹은 node project는 개발 전 설정이 30%를 차지한다고 생각합니다. 그렇기 때문에 react app의 경우에도 create-react-app을 선호하는 편입니다. 하지만 이렇게 project builder를 이용하다 보니 시작은 편하지만, 정작 제 입맛에 맞게 설정을 변경하기 까다로웠습니다. 그래서 TypeScript-node-starter를 이용하지않고, 참고만 하여 제 project를 setting해 보기로 하였습니다.

사실 document만 잘 찾아보면 쉽게 할 수 있는 일이지만 저의 삽질과 고민, 잘못된 부분을 공유해 보고자 setting을 공유하기로 하였습니다.


1. tsconfig.json을 이용한 TypeScript 설정

먼저 TypeScript를 global install합니다.

npm install -g typescript

설치가 완료되었다면 다음 명령어를 통해 제대로 설치되었는지 확인합니다.

tsc -v
// Version 3.0.1

확인이 되었다면, 프로젝트 directory를 만들어 해당 directory로 이동하여 다음 명령어를 수행합니다.

tsc --init

해당 directory에 생성된 tsconfig.json 파일을 열어보면 수 많은 option들이 있습니다. 제가 전부 다 설명할 수는 없고, 설정한 option들 위주로만 설명을 하겠습니다. option들의 모든 설명을 참고 하시려면 이 곳이 곳을 참고하시면 됩니다.

{
"compilerOptions": {
/* Basic Options */
"target": "es2015",
"module": "commonjs",
"outDir": "./dist",

/* Strict Type-Checking Options */
"strict": true
    /* Module Resolution Options */
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"utils/*": ["src/utils/*"],
"controller/*": ["src/controller/*"]
},
"esModuleInterop": true
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
  1. target을 통해 TypeScript가 complie되었을 때의 ECMAScript version을 지정해 줍니다.
  2. module option은 complie하는 동안 지정된 모듈 타겟에 따라 Node.js (CommonJS), require.js (AMD), UMD, SystemJS 또는 ECMAScript 2015 네이티브 모듈 (ES6) 모듈로드 시스템에 적합한 코드를 생성해 줍니다.
  3. outDir 은 complie된 결과물이 생성될 경로를 지정해 줍니다.
  4. moduleResolution 은 module을 import하거나 export할 때, 어떤 방식으로 처리할 것인가에 대한 설정입니다.
  5. baseUrl 은 Non-relative module을 가져올 때 기준이 되는 경로를 설정해 줍니다. 아래쪽의 paths option과도 관계가 있습니다. 생성된 tsconfig.json 파일의 주석에는 Base directory to resolve non-absolute module names. 라고 되어있는데, module resolution에 관한 공식문서에는 A non-relative import can be resolved relative to baseUrl. 이라고 나와있습니다.
  6. paths 는 제 개인적인 취향과 관계가 있는 option입니다. 위의 설정을 기준으로 특정 module을 import할 시 다음과 같은 순서를 따르게 됩니다.
import express from 'express'
import { login } from 'controller/userController'
// 1. express
//'baseUrl' option is set to '<project root>', using this value to resolve non-relative module name 'express'.
//'paths' option is specified, looking for a pattern to match module name 'express'.
// <project root>/express.ts
// <project root>/express.tsx
// <project root>/express.d.ts
// <project root>/express
// <project root>/src/node_modules << not exist, skipping search
// <project root>/node_modules/express.ts
// <project root>/node_modules/express.tsx
// <project root>/node_modules/express.d.ts
// <project root>/node_modules/express/package.json << look up [typings, types, main] field.
// <project root>/node_modules/express/index.ts
// <project root>/node_modules/express/index.tsx
// <project root>/node_modules/express/index.d.ts
// <project root>/node_modules/express/package.json << look up [typings, types, main] field.
// main field "" => reference <project root>/node_modules/@types/express
// <project root>/node_modules/@types/express.d.ts
// <project root>/node_modules/@types/index.d.ts << get!!
// 2. userController
//'baseUrl' option is set to '<project root>', using this value to resolve non-relative module name 'controller/userController'.
//'paths' option is specified, looking for a pattern to match module name 'controller/userController'.
// Module name 'controller/userController', matched pattern 'controller/*'.
// Trying substitution 'src/controller/*', candidate module location: 'src/controller/userController'.
// <project root>/src/controller/userController.ts << get!!

7. exModuleInterop option은 이렇게 설명되어 있습니다.

Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.

모든 import에 대해 namespace를 생성하여 CommonJS와 ES Module간의 상호운용성을 제공한다는데, 정말 알 수 없는 표현입니다. 여기서 주목해야 할 것은 allowSyntheticDefaultImports 옵션을 암시적으로 승인한다는 부분인데요, 해당 옵션에 대한 설명은 다음과 같습니다.

Allow default imports from modules with no default export. This does not affect code emit, just typechecking.

export default 가 없는 module을 import 할 시 default import 가 가능해 진다는 설명입니다. 코드 실행에는 영향이 없고 type check에만 사용된다는 데요, react를 예로 들면 다음과 같게 됩니다.

// exModuleInterop: false || allowSyntheticDefaultImports: false
import * as React from 'react'
// exModuleInterop: true || allowSyntheticDefaultImports: true
import React from 'react'

8. includeexclude 는 여러분이 익히 아시는 그 option입니다. typescript로 complie할 시 포함할 파일과 아닌 파일을 구분할 수 있습니다.


기본적인 TypeScript에 대한 설정을 해 보았습니다. 저도 아직 공부하는 중이고, tsconfig에 대한 설정이 다양한 만큼, 모든 option을 다 알려드리지 못했습니다. 그렇기 때문에, 이 문서를 참고하셔서 프로젝트를 setting하시면 도움이 될 것 같습니다. 다음 번에는 project개발을 위한 setting을 해보겠습니다.