본문 바로가기
개발자 도전기/[STUDY] JS || TS

ts-node | NodeBird | 초기 세팅

by 답수 2022. 4. 18.
728x90
SMALL

 

 

※ 인프런 - Node.js에 TypeScript 적용하기(feat. NodeBire) by 조현영 강의를 기반으로 정리한 내용입니다.

 

 

타입스크립트에 대한 기본 지식을 배웠고, 이제 조금은 더 심화 학습을 위해 이번 강의를 수강하기로 했다. 나는 주로 node.js로 개발을 하고 있기 때문에 여기에 ts를 적용하는 것을 배워보려고 한다. 강의를 들으면서 TS 활용법을 더 체득시켜가자!

 

 

프로젝트 초기 세팅

일단 프로젝트 레포를 먼저 생성한 후, 초기 환경 설정부터 다시 시작!

 

npm init -y 명령어로 package.json 생성, 그후 npm install typescript로 타입스크립트 설치.

 

나는 npx tsc --init 명령어로 tsconfig.json를 생성했지만, 강사님은 다음과 같이 설정을 직접 작성하셨다.

// back/tsconfig.json

{
  "compilerOptions": {
      "lib": ["es2015", "es2016", "es2017", "es2018", "es2019", "es2020"],
      "module": "commonjs", 
      "moduleResolution": "node",
      "strict": true,
  }
}

 

 

나는 --init 명령어를 통해 기본으로 옵션들이 설정되어 있다. 차이점을 보면, 우선 나는 "target" 옵션을 사용했지만 강사님은 "lib"옵션을 사용했다. config파일에 적혀있는 코멘트를 보자.

"target" : Set the JavaScript language version for emitted JavaScript and include compatible library declarations.

"lib" : Specify a set of bundled library declaration files that describe the target runtime environment.

즉 target은 파일을 컴파일했을 때 빌드 디렉토리에 생성되는 자바스크립트의 버전을 의미이고(즉 빌드할 때의 버전), lib은 타입스크립트를 자바스크립트로 컴파일할 때 포함될 라이브러리 목록이다.

* ref: https://www.typescriptlang.org/tsconfig#lib

 

강사님은 lib을 저렇게 설정한 이유가 최신 문법들을 최대한 다 사용하기 위해서 저렇게 버전들을 lib 배열에 다 넣었다고 한다. 근데 꼭 저렇게 모든 버전을 집어 넣어야 하나...?

 

 

다음으로 npm install @types/node 를 설치하여 node.js의 타입들을 추가해준다. 

그리고 프레임워크는 express를 사용할 것이기 때문에 npm install express @types/express 명령어를 입력한다.

 

 

이제 index.ts 파일을 생성한다. 이제 express를 임포트할 것인데 여기서 주의할 점이 있다.

// 1
import express from "express";

// 2
import * as express from "express";

 

패키지를 import할 때, 저 두 방법이 어떻게 다른건지 원리를 알아야 한다. 현재는 1번 처럼 * as 를 붙이지 않으면 

이 모듈은 'export ='를 사용하여 선언되었으며 'esModuleInterop' 플래그를 사용하는 경우에만 기본 가져오기와 함께 사용할 수 있습니다.

이런 에러 문구가 나온다. 왜 * as가 필요한지에 대해서 강사님이 설명해주신다.

 

먼저 express 타입 정의 파일을 들어가보자. 경로: node_modules/@types/express/index.d.ts

이 파일에서 export default 키워드를 찾아보면 해당되는 코드가 없다고 나온다. 즉 저 코드가 없으면 import 할 때 * as를 붙여야 한다.

 

만약 export default가 있고, default를 가져오고 싶다면 * as 없이 import ... from ... 만 입력하면 된다.

 

그런데 가끔 * as 가 있어야 하는 코드인데 없는 경우가 있다. 이런 경우는 tsconfig에 "esModuleInterop": true, 옵션이 설정되어 있기 때문이다. 이 옵션이 true로 설정되어 있으면 * as를 사용하지 않아도 된다. 즉 esModuleInterop은 편의를 위해서 사용하는거지만, 개발하는 입장에서 저런 옵션들의 원리에 대한 이해가 기본적으로 선행되어야 한다!! 고 강사님이 강조하셨다.

 

하지만 저 설명만으로 이해하기에는 아직 내가 많이 부족함... 일단 export default 키워드부터 차근차근 찾아보자. 

 

export vs export default

export default는 하나의 파일에서 단 하나의 변수나 클래스 등의 모듈만 export할 수 있도록 한다. 파일 하나에 한 개의 모듈만 존재하기 때문에 import할 때 임의의 이름으로 정의하는 것도 가능하다.

 

반면에 export는 한 파일 안에서 여러 개의 변수/ 클래스 등을 export하는 것이 가능하고, import할 때 {} 안에 export할 때 정의한 이름과 같은 값을 넣는다.

 

하지만 export로 해도 import할 때 이름을 바꿀 수 있는 방법이 있는데 바로 as 를 이용하는 것이다. 예를 들어 

import { MyClass as MyClassAlias } from "./MyClass";

 이렇게 코드를 작성한다면, MyClassAlias라는 이름으로 이 모듈을 사용할 수 있다. 

 

그렇다면 *은 뭘까? 파일에 있는 모든 모듈들을 한 번에 import한다는 의미이다. 즉

import * as express from "express";

이 코드는 express 내의 모든 모듈들을 import한다는 뜻이고, express.(모듈 이름) 이렇게 사용하면 된다. 

 

 

그럼 이제 tsconfig의 "esModuleInterop": true 옵션이 무엇이길래 저런 개념들에 대한 구분도 없이 모듈을 가져올 수 있게 해주는 걸까?

 

우선 tsconfig.json 파일 내 기본적으로 적혀 있는 주석을 보자.

/* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */

CommonJs모듈을 임포팅하는 것에 대한 지원을 더 쉽게 해주는 옵션이고, 타입 호환을 위해 `allowSyntheticDefaultImports`가 가능하게 해준다고 한다. 그러니까 위에서 예시로 봤던 것처럼

// 1
import express from "express";

// 2
import * as express from "express";

원래 2번 처럼 작성해야 되는 코드를 1번 같이 작성해도 임포트를 허용한다는 것이다. 

* ref: https://www.typescriptlang.org/tsconfig#allowSyntheticDefaultImports

 

우선 나는 이 옵션을 끄고 프로젝트를 진행해봐야겠다. 개념에 대해 더 잘 알고 직접 코드를 작성하면서 체감해보는 것이 좋을 듯?

 

index.ts 생성

다시 본론으로 돌아와서, index.ts를 생성하자.

import * as express from "express"; 

const app = express();
// 환경변수 설정
const prod: boolean = process.env.NODE_ENV === 'production'; // 배포용
/**
 * 강사님 원칙: 기존에 만들어진 라이브러리 사용할 때는 타입 정의 따로 안함(타입추론되게 둠)
 * 내가 직접 만든 변수에는 타입 정의함
 */

app.set('port', prod ? process.env.PORT : 3065);    // express에 변수 설정하는 방법! 실무에서 이렇게 많이 사용 
app.get('/', (req, res) => {
    res.send('nodebird 백엔드 정상 동작!');
});

app.listen(app.get('port'), () => {  // 배포용이면 포트 자유자재로 바꿀 수 있도록, 개발용이면 3065로 고정
    console.log(`server is running on ${app.get('port')}`);
});

 

 

한 번 실행해보자. 근데 노드는 TS를 실행시키지 못하기 때문에 js파일로 변환해야 하는 작업을 거쳐야 한다. 하지만 개발자 환경에서 바로 실행할 수 있는 방법이 있다.

 

npm i -D ts-node

 

이 ts-node 라이브러리를 설치하면 되는데, 엄밀히 말하면 내부적으로 이 모듈이 타입스크립트를 자바스크립트로 변환 후 실행하는 것이고, 노드가 타입스크립트 자체를 실행시킬 수 없다는 사실은 변하지 않는다. 그리고 노드몬도 같이 설치하자(두 라이브러리에 대해서는 이전 강의 때도 공부했었다!!).

 

npm i -D nodemon

 

참고로 npm 명령어에 -D 를 붙이면 개발용 디펜던시가 따로 생성된다. 요런 식으로

 

파일을 실행할 때는 npx ts-node index.ts 명령어를 치면 된다.

 

여기서 npx를 사용하는 이유? 글로벌 설치를 막기 위해서! 글로벌 패키지는 package.json에 남겨져 있지 않기 때문에 다른 개발자에게 프로젝트를 인수인계할 때 매우 복잡해진다. 그래서 요즘은 글로벌 설치를 최대한 피하는 추세라고 한다. 그래서 npx를 입력하면 글로벌 명령어를 글로벌 설치 없이 실행할 수 있다고 한다. 그래서 npx ts-node index.ts 이 명령어도 ts-node가 devDependencies에 적혀 있고 앞에 npx만 붙여주면 글로벌 명령어처럼 실행이 된다.

 

ts-node는 배포용으로 사용하기에는 성능에 무리가 있다. 배포용에서는 npx tsc로 모든 파일을 자바스크립트로 컴파일하여 사용한다. 수많은 요청이 올때마다 ts를 js로 변환하면서 사용하면 매우 비효율적이기 때문!

 

암튼 다시다시 본론으로 돌아와서 서버를 실행시키면,

 

3065 포트로 서버가 열렸다는 로그가 뜨고

 

 

res.send로 보낸 저 텍스트가 브라우저에 뜨게 된다.

 

 

아 그리고 저번 강의에서 배웠던 것처럼 npm start 명령어로 ts-node가 실행될 수 있도록 package.json의 scripts 부분에 코드를 추가했다.

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon --watch '*.ts' --exec ts-node index.ts",
    "build": "tsc"
  },
  ...

 

728x90
LIST

댓글