ESLint + Prettier + TypeScript + Husky + VSCode (+ Node)

곽태욱·2020년 12월 26일
4

https://github.com/rmfpdlxmtidl/node-template (완성본)

TypeScript, Node 환경에서 Visual Studio Code를 통해 작성한 코드를 저장할 때마다 자동으로 포맷하고, ESLint를 통해 문법 오류를 확인하고, Husky를 통해 커밋하기 전에 스크립트를 실행하는 기능을 사용해보도록 하자.

Node, Yarn

Node는 버전 14부터 ES2020 기능을 모두 지원하고, ES 모듈도 지원하기 시작했고 한다. 따라서 ES2020 기능을 사용하기 위해 별도의 babel 컴파일러를 설치하지 않아도 된다.

Yarn은 페이스북에서 제작한 패키지 관리자인데 현재는 기본 패키지 관리자인 NPM과 기능적으로 별로 차이가 없다(..고 하지만 yarn이 체감상 더 빠른 듯하다). 익숙한 패키지 관리자로 설치하면 된다.

> node --version
> yarn --version

위 명령어를 통해 설치된 버전을 확인할 수 있다.

Node.js LTS 버전 설치 (공식)
Yarn 설치 (공식)

Yarn add 명령어 옵션 (공식)
Typescript configuration closest to node v14 (StackOverflow)

프로젝트 생성

> mkdir node-project
> cd node-project
> yarn init --yes

프로젝트 폴더와 그 폴더 안에 package.json 파일을 생성한다. --yes 옵션으로 질문을 건너뛸 수 있다.

TypeScript

> yarn add typescript @types/node ts-node --dev
  • @types/node : Node의 type을 정의한 패키지로서 Node를 TypeScript 환경에서 개발할 때 필요하다.
  • ts-node : ts 파일을 그대로 실행할 수 있다. 내부적으로 ts 파일을 js 파일로 트랜스파일하면서 실행된다.
> yarn tsc --init
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "sourceMap": false,
    "outDir": "dist",
    "strict": true,
    "baseUrl": "./",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
  },
  "include": ["src"]
}

그리고 위 명령어로 TypeScript 설정 파일(tsconfig.json)을 생성할 수 있다. 기본적으로 target, module, strict, esModuleInterop, skipLibCheck, forceConsistentCasingInFileNames가 설정되어 있다. 우리는 Node v14를 사용할 예정이기 때문에 위와 같은 옵션을 추가한다.

  • target : 트랜스파일된 js 파일에 적용할 문법
  • module : 트랜스파일된 js 파일에 적용할 모듈화 방식
  • lib : 기본적으로 내장된 JS API 타입 외에 브라우저 환경에서 필요한 타입이나 새로운 JS API 타입을 추가할 수 있다.
  • sourceMap : ts가 어떤 과정을 거쳐 js로 변환됐는지 보여주는 파일을 추가로 생성한다. (디버그용)
  • outDir : 트랜스파일된 js 파일이 해당 폴더에 생성된다. 따로 설정하지 않으면 js파일이 해당 ts 파일과 동일한 경로에 생성된다.
  • strict : 엄격한 타입 검사 수행
  • baseUrl : 해당 경로를 기준으로 절대 경로로 모듈을 불러올 수 있다.
  • include : 해당 폴더에 있는 ts 파일만 js 파일로 트랜스파일한다.
> yarn add rimraf --dev

여러 OS 간 원활한 파일 삭제를 위해 위 패키지를 개발 종속성으로 설치한다.

// package.json
{
  "engines": {
    "node": ">=14.5.0"
  },
  "scripts": {
    "build": "rimraf dist && tsc",
    "type": "tsc --pretty --noEmit"
  },
}

package.json에 Node 최소 버전을 명시해주고, 편의를 위해 위 스크립트를 등록한다.

  • yarn build : 이전에 트랜스파일된 파일은 모두 삭제한 후 TypeScript 트랜스파일러가 tsconfig.jsoninclude로 지정한 경로의 ts 파일을 js 파일로 트랜스파일 해준다.
  • yarn type : ts 파일의 타입 유효성만 검사해준다.

TypeScript 설치 (공식)
tsconfig.json 옵션

Prettier

Prettier는 주로 JavaScript의 문법과 관련없는 코드 입력 스타일을 잡아준다. 띄어쓰기를 몇 칸으로 할지, Space/Tab 중 무엇을 사용할지, 개행은 언제 어떤 문자로 할지 등을 설정할 수 있다. 프로젝트 전체에 코딩 스타일 통일감을 줄 수 있기 때문에 필수로 사용하는 것이 좋다.

> yarn add prettier --dev

--exact는 패키지의 버전을 고정하는 옵션인데 추후 Prettier 업데이트로 인한 포맷 방식 변경을 방지할 수 있다. 이 옵션은 프로젝트 환경에 따라 있어도 되고 없어도 된다.

// .prettierrc.json
{
  "printWidth": 100,
  "semi": false,
  "singleQuote": true
}

그리고 프로젝트 폴더에 .prettierrc.json 파일을 생성하고 Prettier 옵션을 설정해준다. 옵션을 명시하지 않으면 Prettier 권장 기본값으로 설정된다. 옵션 설정에 정답은 없으니 팀원과 합의한 방법으로 설정하면 된다. 개인적으로 세미콜론 없음과 작은 따옴표 사용은 StandardJS에서 권장하는 사항이라서 좋아한다.

Prettier 설치 (공식)
Prettier 옵션 목록 (공식)
Prettier vs Linter (공식)

ESLint

> yarn add eslint --dev
> yarn run eslint --init

> To check syntax, find problems, and enforce code style
> JavaScript modules (import/export)
> 프로젝트에 맞게 (React, Vue, None of these)
> Does your project use TypeScript? › Yes
> 프로젝트에 맞게 (프론트엔드면 Browser, 백엔드면 Node)
> Use a popular style guide
> Airbnb, Google, Standard 중 하나 선택
> JSON
> 필요한 플러그인을 yarn으로 수동 설치할꺼면 No, npm으로 자동 설치할 거면 Yes

첫번째 명령어로 ESLint를 설치하고, 두번째 명령어로 프로젝트에 적합한 Lint 방식을 선택할 수 있다. 보통 ESLint 예제가 JSON 형식으로 되어 있어서 ESLint 설정 파일 형식을 JSON으로 선택했지만, 설정 파일에 동적인 값을 넣어야 한다면 JSON 대신 JavaScript 형식으로 생성할 수도 있다. 깔끔한 걸 좋아하면 YAML 형식도 괜찮다. 그리고 Airbnb, Google, Standard 스타일을 선택하는 대신에 직접 스타일 질문에 답해도 된다.

// .eslintrc.json

{
  "extends": [
    "eslint:recommended", "plugin:@typescript-eslint/recommended"
  ]
}

그리고 자동 생성된 ESLint 설정 파일에 위 플러그인을 추가해 TypeScript 권장 설정을 활성화할 수 있다.

// .eslintrc.json

{
  "extends": [
    "plugin:@typescript-eslint/recommended-requiring-type-checking"
  ],
  "parserOptions": {
    "project": "./tsconfig.json"
  } 
}

그리고 위와 같이 설정하면 ESLint에서 TypeScript의 타입 유효성 검사를 같이 실행할 수 있다. 하지만 해당 플러그인 깃허브 설명에도 나와 있듯이 타입 유효성 검사를 위해선 전체 코드를 빌드하는 과정이 필요하기 때문에 ESLint 수행 시간에 빌드 시간이 추가된다. 따라서 Linting과 타입 유효성 검사를 항상 같이 할 때만 추가해주자.

ESLint 설치 (공식)
eslint-config-standard (GitHub)
typescript-eslint (GitHub)

ESLint + Prettier

Prettier와 ESLint는 코드를 자동으로 바꿔준다는 점에서 지향하는 점이 비슷하기 때문에 아무런 설정없이 같이 사용하게 되면 설정 충돌이 일어날 수 있다. 그래서 Prettier와 충돌할 수 있는 ESLint 옵션을 끄는 플러그인을 설치한다.

> yarn add eslint-config-prettier eslint-plugin-prettier --dev
  • eslint-config-prettier : Prettier와 충돌할 수 있는 ESLint 옵션을 꺼준다.
  • eslint-plugin-prettier : ESLint에서 Prettier를 일종의 규칙으로 취급하도록 설정해준다.
// .eslintrc.json
{
  "extends": [
    "plugin:prettier/recommended", "prettier/@typescript-eslint"
  ]
}

그리고 위 두 항목을 extends 배열 제일 마지막에 넣는다. extends 배열 항목은 뒤로 갈 수록 우선순위가 높아지기 때문에 Prettier 관련 설정은 extends 배열 제일 뒤에 넣어줘야 한다.

ESLint + Prettier (Prettier 공식)
typescript-eslint (GitHub)
eslint-config-prettier (GitHub)
eslint-plugin-prettier (GitHub)

스크립트 등록

// package.json
{
  "scripts": {
    "format": "prettier . --write --ignore-path .gitignore",
    "lint": "eslint . --fix --ignore-path .gitignore"
  }
}

지금까지 설치한 ESLint와 Prettier를 코드에 적용하기 위해선 명령어를 입력해야 한다. 그래서 package.json ESLint와 Prettier를 실행할 수 있는 명령어를 스크립트로 등록한다.

모든 파일에 대해 ESLint와 Prettier를 적용하면 실행 시간이 오래 걸릴 수 있기 때문에 .eslintrc.json, .prettierrc.json 파일과 동일한 위치에 .prettierignore, .eslintignore 파일을 생성해 적용을 제외시킬 파일을 명시할 수 있다. 주로 외부 파일이나 굳이 적용할 필요가 없는 파일(node_modules, dist 등)을 제외시켜준다. 이 파일을 따로 관리하기 귀찮다면 --ignore-path 옵션으로 .gitignore 파일을 지정하면 좋다.

> yarn format
> yarn lint

그러면 위처럼 짧은 스크립트를 통해 ESLint와 Prettier를 실행할 수 있다.

.eslintignore (ESLint 공식)
.prettierignore (Prettier 공식)

VSCode

하지만 ESLint와 Prettier를 실행하기 위해 코드를 작성하면서 계속 스크립트를 실행하기는 귀찮을 수 있다. 우리는 작성한 코드가 저장할 때마다 바로 바로 정렬되는 것을 원할 것이다. 그렇다면 VSCode를 이용해보자.

우선 VSCode의 Prettier와 ESLint 확장 프로그램을 설치한다. 그리고 VSCode 프로젝트(workspace) 설정 파일에 아래 항목을 추가해준다.

// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.formatOnPaste": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "typescript.tsdk": "node_modules/typescript/lib",
  "eslint.packageManager": "yarn",
  "prettier.packageManager": "yarn"
}

프로젝트를 나 혼자 진행한다면 VSCode 전역 설정 파일(단축키 Ctrl + ,)에 추가해도 되지만, 이 프로젝트를 사용하는 모든 사람에게 이러한 설정을 적용하고 싶으면 프로젝트 설정 파일에 추가하는 것이 좋다.

// .vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode"
  ]
}

그리고 위 파일을 통해 다른 사람이 사용하는 VSCode에도 Prettier와 ESLint 확장 프로그램을 설치하게끔 알려줄 수 있다.

Husky

프로젝트를 진행하면 보통 git을 사용해서 프로젝트 버전을 관리하는데, 프로젝트 버전마다 코드 포맷 형식이 다르면 코드를 읽는데 불편할 수 있다. 이렇게 되면 나중에 코드 포맷만을 위한 커밋이 추가될 수 있는데, 이는 의미 없는 커밋이기 때문에 프로젝트 단계에서 커밋(또는 푸시)하기 전에 코드 포맷을 자동으로 검사해주는 것이 좋다. 이를 위해 Husky가 등장했다.

> yarn add husky --dev
// package.json
{
  "scripts": {
    "pre-push": "yarn format && yarn lint && yarn type"
  },
  "husky": {
    "hooks": {
      "pre-push": "yarn pre-push"
    }
  }
}

위와 같이 husky를 개발 종속성으로 설치하고 package.json에 husky 설정을 추가해준다. 위 설정은 외부 저장소로 커밋을 push하기 전에 yarn format && yarn lint && yarn type 스크립트를 실행해, 성공하면 커밋을 push하고 실패하면 push하지 않는다는 뜻이다.

husky hooks 중에 pre-commit도 있는데 이를 통해 커밋 전에 실행될 스크립트를 지정할 수 있다. 근데 그러면 매 커밋마다 스크립트를 실행하기 때문에 속도 저하가 있을 수 있어, 이번에 커밋될 파일에만 스크립트를 적용하는 lint-staged와 같이 사용하는 것이 좋다. 개인적으론 매 커밋마다 스크립트를 실행하면 커밋하는데 몇 초 정도 지연이 발생해서 pre-push를 선호하는 편이다.

Node + TypeScript

> yarn add nodemon --dev
// nodemon.json
{
  "verbose": true,
  "watch": ["src/"]
}

개발할 때 파일 변경 사항을 자동으로 감지하고 자동으로 실행 환경에 반영하게 도와주는 nodemon 패키지를 설치한다. 그리고 프로젝트 최상위 디렉토리에 nodemon 설정 파일을 생성한다.

  • verbose : 콘솔에 글이 더 자세히 출력된다. (디버그용)
  • watch : 해당 폴더에서 파일 변경 사항을 감지한다.
// package.json
{
  "main": "dist/index.js",
  "scripts": {
    "dev": "nodemon src/index.ts",
    "start": "node dist/index.js",
  }
}

package.json에 프로젝트 진입점 파일을 명시해주고 개발 모드와 배포 모드 스크립트를 추가한다.

  • yarn dev (개발 모드) : 파일이 변경되는 것을 자동으로 감지하고 프로세스를 자동으로 재시작하지만, ts-node가 필요할 때마다 ts 파일을 js 파일로 변환하고 실행하기 때문에 속도가 느릴 수 있다.
  • yarn start (배포 모드) : node로 js 파일을 네이티브하게 실행하기 때문에 속도가 빠를 수 있지만, 파일을 수정하면 변경 사항을 반영하기 위해 프로세스를 수동으로 재시작해야 한다. 이 스크립트를 실행하기 전에 yarn build을 통해 ts 파일을 js 파일로 트랜스파일해줘야 한다.

src

// src/index.ts

const a: string = 'Hello world!'
console.log(a)

src 폴더 안에 테스트 ts 파일을 생성하고 실행해본다.

Git

Editor

profile
이유와 방법을 알려주는 메모장 겸 블로그. 블로그 내용에 대한 토의나 질문은 언제나 환영합니다.

0개의 댓글