[algo-with-me] 개발 환경 준비! (4)

지금 3주차 시작하는 시기인데 벌써, 지난주에 했던 일들이 가물가물 해지기 시작했네요. 기억력 실화야?

열심히 되새기면서 2주차 개발 시작전 준비했던 것들에 대해 이야기 해볼게요.

 

이번 포스트에서는 본격적인 개발에 앞서 개발 환경 세팅에 대한 내용을 담았습니다.

 

프론트 - 무지, 콘, 네오

백 - 저(제이지), 프로도

 

API 서버, 채점 서버 구축

API 서버와 채점 서버는 동일하게 nestJs를 사용하기로 정했습니다!

아키텍처 참고

크게 두 가지 정도의 이유가 존재합니다.

1. redis message queue를 추상화한 패키지인 bull을 공식적으로 지원해 주기 때문에 사용이 매우 편리하다.(https://docs.nestjs.com/techniques/queues)

2. nest가 express에 비해 다운 받아야 할 패키지는 많지만, 구현이 훨씬 편리하고 속도 자체는 비슷하다. (typeorm, bull 등)

 

이런 이유로 api 서버와 채점서버는 동일한 nestJs를 사용하기로 결정하였습니다. 아키텍처에 관한 이야기는 추후에도 많이 다룰 예정이라 이번 포스트에서는 이정도로 넘어갈게요~!

 

패키지 매니저: pnpm

가장 중요한 패키지 매니저부터 이야기 해 볼게요. 우선 저희는 pnpm을 사용하기로 했습니다. 사실 저는 패키지 모듈 종류가 여러개라는 것은 알고 있지만, 각각 무슨 차이가 있는지는 전혀 모르고 있는 상태였습니다. 프론트 분들이 pnpm 사용을 고려하고 있었고, 그에 대해 멘토님께 질문을 드렸더니 pnpm이 좋다고 하셔서 저희 백엔드도 고민없이 pnpm 사용을 결정하였습니다. 사실 이렇게 결정하고 무슨 차이가 있는지 찾아 보려고 했는데 미루고 미루다 보니 안찾게 되더군요.. 블로그 쓰면서 찾아봤습니다 하하. 이래서 블로그를 쓰는거죠

 

사실 이 내용만 가지고도 하나의 포스트를 쓸정도로 그렇게 간단한 내용은 아닌것 같습니다. 그래도 최소한으로 정리해 볼게요. 차이점 알아보려고 "npm pnpm 차이" 라고 검색했더니 무수한 블로그가 나오는데, 대부분 그냥 npm은 패키지가 중복저장되고, pnpm은 아니다! 정도로 나오더군요. 왜 그런지 조금더 자세히 찾아봤습니다.

 

패키지 매니저는 알다시피 node.js를 사용하면서 다운받는 무수한 패키지들을 관리해줍니다. 여러가지 패키지 매니저가 생기게 된 이유는 node.js에 내장되어 있는 npm에 단점이 있기 때문이죠. 어떤 단점들이 있을까요? 그 전에 우선 npm의 특징들에 대해 알아봅시다.

npm으로 패키지를 설치하면 package.json, package-lock.json 파일이 생깁니다. 여기에 설치한 모듈과 버전, 의존성 등을 확인할 수 있죠. 그리고 설치된 패키지는 node_modules 폴더에 전부 저장됩니다.

 

npm 에는 어떤 단점이 있을까요?

패키지를 설치하거나 업데이터 할 때 속도가 느리고 비효율적일 수 있습니다 -> 왜????

 

디스크 공간을 차지하고 충돌을 일으키는 중복되거나 중첩된 종속성을 생성할 수 있습니다 -> 왜???

너무 궁금해서 이유를 찾아봤습니다! pnpm 공식문서에는 다음과 같이 써있습니다. npm은(version 3이상 기준)  flattened dependency tree 를 사용한다고 합니다. 이방식 때문에 디스크 공간을 더 많이 쓰고, node_modules 디렉토리가 복잡해진다고 합니다. (version 2에서는 nested 방식을 사용했다고 하네요. 블랙홀 밈이 여기서 나온듯?)

npm v2, v3

https://npm.github.io/how-npm-works-docs/npm3/how-npm3-works.html

npm3 방식을 보면 저렇게 할 수 밖에 없지 않나? 괜찮은것 같은데? 라는 생각이 듭니다. App에서 사용하는 B와 C가 의존하고 있는 B 의 버전이 다르기 때문이죠, 그럼 여기서 하나 더 꼬아보겠습니다.

 

오마이갓. 두번째 깊이에선 의존성 공유가 안되나 봅니다. 만약 이런패키지가 많아진다면??? 당연히 용량도 많이 차지하고 설치도 느려지겠죠? 이제 이해가 되네요! 그래도 그나마 위안이 되는 점이 있습니다. 만약 App이 의존하는 B의 버전을 2로 바꾼다면 모두 평탄화 되어서 다음과 같이 변한다고 하네요.

이상적이네요.

여튼 npm엔 이런 문제가 있다고 합니다. 그럼 pnpm은 어떻게 이런 문제를 해결했을까요??

(https://pnpm.io/ko/symlinked-node-modules-structure)

첫 번째, 용량문제 -> npm은 프로젝트가 100개 있는 경우 각 프로젝트의 종속성 사본 100개가 디스크에 저장되지만, pnpm을 사용하면 의존성이 "content-addressable" 저장소에 저장되어 공유 된다고 합니다.

공용공간을 쓰기 때문에 여러 프로젝트가 있을 경우 디스크 사용이 훨씬 줄어들겠네요.(https://pnpm.io/ko/motivation)

 

두 번째, 이것도 어떻게 보면 용량 문제네요(중복문제) -> 심볼릭 링크를 사용하여 의존성의 중첩 구조를 생성합니다.

써진 글을 차근차근 읽어보면 어느정도 이해가 됩니다. 의존성이 있는 패키지를 심볼릭 링크로 연결해서 중복 다운로드 없이 사용이 가능합니다. 이렇게하면 정말 중복이 없겠군요! 심볼릭 링크만 걸어주면 되니까요! 오!

(https://pnpm.io/ko/symlinked-node-modules-structure)

 

헉헉.. 덕분에 공부가 되었습니다. 이제 npm을 쓸이유가 전혀 없다는 것을 깨닫게 되었군요. 땅땅. 천재들이야 정말.

api 서버 구축 이야기 하려다가 딴길로 조금 새버렸군요. 그래도 중요한 내요이니까요! 흠. 따로 빼서 포스팅하고 링크를 걸어야 하남.. (보안 문제도 있다는데 이건 일단 패스)

 

이제 시작입니다.

 

코드 컨벤션

 

지옥의 ESlint와 prettier 설정을 시작해봅시다. 무지상태였던 저와 프로도(같은 백엔드)는 맨땅에 해딩하며 설정들을 완료했습니다. (사실 이것도 따로 포스트 올려도 될정도..) 나중에 추가 수정까지 했던 시간 까지 합치면 3~4시간 정도 걸린것 같아요.

 

우선, 사용하기 전에 가장 헷갈렸던, eslint와 prettier가 무슨 차이가 있는거지? 에 대한 궁금증을 해결해 봤습니다.

 

eslint - linter 라고 부른다. 코드를 분석해서 정해놓은 컨벤션에 위배되면 에러를 표시해 줍니다. 또한 간단한 포멧팅 기능도 제공해 줍니다. 린터는 크게 포맷팅(스타일) 룰퀄리티 룰이라는 두 가지 분야로 나눌 수 있습니다. 사용되지 않은 변수를 오류로 표시해 주는 기능 같은 것들을 코드 퀄리티 룰이라고 하고, 이것들은 린터에서만 작동합니다.

prettier - fomatter 라고 부른다. 코드 스타일을 일정하게 하기 위해 사용합니다. 저장 버튼을 누르는 등의 행위를 하면 정해진 컨벤션 대로 코드 스타일이 바뀝니다. 예를 들면 if else 중괄호 위치, 한 줄 최대 길이수 등이 포메팅에 해당합니다.

 

비슷한듯 다른 느낌이죠, 다른 블로그에서 가져온 글인데 어느정도 이해가 된 상태라면 이 한줄이면 납득하실수 있을겁니다.

 

"eslint는 코드 퀄리티를 보장하도록 도와주고, prettier는 코드 스타일을 깔끔하게 혹은 통일되도록 도와준다."

 

ESLint

사용하기 위해선 설치해야 합니다. 운영환경에선 필요 없으므로 -D 옵션을 주어 devdependency로 설치합니다. (nestjs 사용하면 알아서 다 됩니다.) (https://pnpm.io/cli/install)

pnpm install -D eslint

 

vsCode를 기준으로, extension도 반드시 설치해주어야 한다.

eslint는 실제 코드에서 사용되는게 아니라 에디터에 적용해서 사용하기 때문에 라이브러리와 익스텐션 모두 설치되어야 합니다. 실제로, 코드상에서 eslint 에러가 나더라도 실행하면 잘 동작하는 모습을 볼 수 있습니다.

eslint 설정을 위해선 .eslintrc 파일을 생성하여 작성해야 합니다. nestjs를 사용하여 프로젝트를 만들면 자동으로 js 파일로 기본 생성됩니다.

 

.eslintrc.js

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir: __dirname,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
  },
};

처음 보면, 읽어도 정말 이게 뭔가 싶은 마음이 들었습니다.

 

parser

코드 파일을 검사할 파서를 설정합니다. parserOptions는 파서 관련 옵션 설정을 할 수 있습니다.

 

root

true 이면, root 디렉토리부터 이 디렉토리 까지 존재하는 모든 eslint를 찾습니다.

 

plugins

프로젝트에 필요한 플러그인을 명시하여 사용합니다. 예를 들어 구글 스타일 가이드, 에어비엔비 스타일 가이드 등을 적용할 수 있습니다. 반드시 라이브러리 설치도 같이 진행해야 합니다. 지금은 jest에서 자동으로 ts 관련 플러그인을 넣어준것 같네요.

 

extends

eslint rule 설정이 저장되어 있는 외부 파일을 extends 합니다. 안에 적혀있는 것에 해당하는 eslint 룰이 적용됩니다.

 

rules

직접 eslint 룰을 설정하는 부분입니다.

 

이 외에도 다양한 설정들이 가능한데, 필요한 부분을 찾고 싶다면 역시 공식문서를 찾아보는것을 추천드립니다.

https://eslint.org/docs/latest/

https://typescript-eslint.io/getting-started/

 

 

아래는 저희가 필요한 내용들을 추가한 코드입니다.

module.exports = {
  extends: [
    'plugin:@typescript-eslint/recommended',
    // nestjs 스타일 가이드
    'plugin:nestjs/recommended',
    // google 스타일 가이드
    // 'google',
    // import sort 관련 설정
    'plugin:import/recommended',
    'plugin:import/typescript',
    // prettier
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir: __dirname,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin', 'nestjs'],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    // import sort 관련 설정
    'import/order': [
      'error',
      {
        groups: ['external', 'builtin', ['parent', 'sibling'], 'internal'],
        pathGroups: [
          {
            pattern: 'nest',
            group: 'external',
            position: 'before',
          },
        ],
        alphabetize: {
          order: 'asc',
          caseInsensitive: true,
        },
        'newlines-between': 'always',
      },
    ],
    //
  },
  settings: {
    // import sort 관련 설정
    'import/resolver': {
      typescript: {},
      node: {
        paths: ['src'],
      },
    },
  },
};

 

 

우선, 저희는 import를 정렬해주는 기능이 있다고 해서 써보려고 했습니다. 적용 과정을 전혀 모르니 정말 까다로웠습니다. 설치해야 하는 라이브러리도 많았습니다.

pnpm install eslint-plugin-import --save-dev

간단하게 설치할 수 있습니다. algo with me 프로젝트 할 때는 이렇게 하고나서 많은 오류가 터져서 이것저것 추가로 다운을 많아 받았었지만, 다른 프로젝트를 새로 시도해보면서 그럴 필요가 없다는것을 확인했습니다.

 

아래는 다른 프로젝트의 린트 파일입니다.

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    project: 'tsconfig.json',
    tsconfigRootDir: __dirname,
    sourceType: 'module',
  },
  plugins: ['@typescript-eslint/eslint-plugin', 'import'],
  extends: [
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
  ],
  root: true,
  env: {
    node: true,
    jest: true,
  },
  ignorePatterns: ['.eslintrc.js'],
  rules: {
    '@typescript-eslint/interface-name-prefix': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    // import sort 설정
    'import/order': [
      'error',
      {
        groups: ['external', 'builtin', ['parent', 'sibling'], 'internal'],
        pathGroups: [
          {
            pattern: 'nest',
            group: 'external',
            position: 'before',
          },
        ],
        alphabetize: {
          order: 'asc',
          caseInsensitive: true,
        },
        'newlines-between': 'always',
      },
    ],
  },
};

 

그리고 자주 발생하는 에러가 몇가지 있는데 발생시 확인해 보면 좋은 글들입니다.

Definition for rule 'import/order' was not found.eslint(import/order) 에러 해결

https://stackoverflow.com/questions/67489042/why-eslint-plugin-for-import-order-doesnt-work-in-cra

 

 

Unable to resolve path to module './app.service’ 에러 해결

https://stackoverflow.com/questions/55198502/using-eslint-with-typescript-unable-to-resolve-path-to-module/56696478#56696478

 

prettier

이번엔 프리티어 설정을 진행해 보겠습니다.

eslint와 prettier를 같이 사용하면 두 설정이 충돌을 일으킬 수 있습니다. 그래서 충돌하는 규칙을 꺼 주어야 하는데 prettier의 규칙을 끌 수도 있고, eslint의 규칙을 끌 수도 있습니다. 여러 검색을 해본 결과 성능 이슈 때문에 eslint의 규칙을 끄는 경우가 일반적이라고 합니다.

 

eslint-config-prettier를 설치하여 prettier와 충돌하는 eslint의 규칙을 끄도록 했습니다.

https://yrnana.dev/post/2021-03-21-prettier-eslint/

pnpm install eslint-config-prettier --save-dev

 

{
  "singleQuote": true,
  "trailingComma": "all",
  "tabWidth": 2,
  "printWidth": 120,
  "useTabs": false,
  "semi": true
}

.prettierrc

prettier 설정파일은 위와같이 설정을 해주었습니다.

vscode를 쓴다면 vscode에서도 설정해주어야 하는 부분이 있습니다. 자동저장 등의 기능을 사용하기 위해서 입니다!

{
  "code-runner.runInTerminal": true,
  "terminal.integrated.defaultProfile.windows": "Command Prompt",
  "terminal.integrated.enableMultiLinePasteWarning": false,
  "[python]": {
    "editor.formatOnType": true
  },
  // 파일을 저장할 때마다 `eslint` 규칙에 따라 자동으로 코드를 수정
  "editor.codeActionsOnSave": { "source.fixAll.eslint": true },
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "files.autoSave": "afterDelay",
  "files.autoGuessEncoding": true,
  "workbench.colorTheme": "Default Dark+",
  "editor.formatOnSave": true
  // "prettier.tabWidth": 4,
  // "prettier.printWidth": 160
}

User/settings.json

 

모두 복붙해 사용할 필요는 없고 옵션을 보고 필요한 부분을 가져다 사용하면 됩니다!

 

이제 포멧팅 관련 설정이 완료되었습니다. 다음부터 본격적인 개발을 시작해볼게요!!


참고

npm 공식문서

https://npm.github.io/how-npm-works-docs/npm3/index.html

 

pnpm 공식문서 (한국어 번역 개꿀)

https://pnpm.io/ko/

 

https://romanglushach.medium.com/comparing-npm-yarn-and-pnpm-package-managers-which-one-is-right-for-your-distributed-project-to-4d7de2f0db8e

 

심볼릭 링크란?

링크를 연결하여 원본 파일을 직접 사용하는 것과 같은 효과를 내는 링크이다.  윈도우의 바로가기와 비슷한 개념

https://qjadud22.tistory.com/22

 

eslint, prettier

https://yrnana.dev/post/2021-03-21-prettier-eslint/

https://helloinyong.tistory.com/325