본문 바로가기

카테고리 없음

모노레포 개발 초기 환경 세팅

728x90

초기 세팅의 결과 목표

  • [x] client와 server를 분리된 폴더에서 개발할 수 있다.
  • [x] root환경에서 client와 server의 dev 모드를 실행할 수 있다.
  • [x] vscode에서 저장을 하면 prettier와 eslint가 자동으로 돌아간다. </aside>

Node.js 및 Pnpm 설치

버전 확인

확인한 날짜 분류 LTS 버전 명령어 다운로드 링크

2024-11-04 Node.js v22.11.0 node -v 참고 링크
2024-11-04 pnpm v9.12.3 pnpm -v 참고 링크

Node.js 설치 방법

위의 다운로드 링크에 들어가서 설치

pnpm 설치 방법

npm install -g pnpm

pnpm 초기화 및 워크스페이스 설정

package.json 파일 생성

pnpm init

package.json 내용 수정

{
  "name": "boolock", // package는 name과 version으로 패키지의 고유성을 판별한다.
  "private": true, // publish 명령 거부
  "packageManager": "pnpm@9.12.3", // 특정 패키지 매니저를 사용할 떄 특정 패키지와 버전을 지정
  "engines": { // 동작 가능한 node 버전 지정
    "node": ">=20",
  }
}

 

 

engines field 주의할 점

사용자가 만약 engine-strict config flag를 설정하지 않으면 이 필드는 단지 조언용으로 사용된다. node 버전을 강제하고 싶어서 .npmrc 파일을 추가해 engine-strict flag를 true로 변경했다.

 

engine-strict=true

 

~rc 가 뭘까

bashrc, eslintrc, prettierrc 등 파일 끝에 붙는 rc는 run commands 혹은 run control의 약자다. rc로 끝나는 파일은 보통 명령어의 설정에 대한 내용을 담고 있다.

 

preinstall로 npm install 막기 (실패함)

pnpm으로 설치해야 하는데 습관적으로 npm으로 설치하게 되는 경우가 발생했다. 그래서 npm으로 install을 하려고 하면 warning하는 preinstall script를 추가했다.

"preinstall": "if [ \\"$(npm_config_user_agent | grep -oP 'pnpm')\\" != 'pnpm' ]; then echo '⚠️  This project requires pnpm. Please use pnpm to install dependencies.' && exit 1; fi",

 

폴더 구조 정의

네부캠에서 모노레포를 사용하라고 했고, 클라이언트와 서버 모두 TypeScript로 개발하기로 했기 때문에 다음과 같은 폴더 구조를 설계했다.

project/
├── apps/ # 애플리케이션을 모아두는 폴더
│   ├── client/ # 프론트엔드
│   └── server/ # 백엔드
├── packages/ # 공유 패키지
│   ├── eslint/ # eslint 설정
│   └── tsconfig/ # typescript 설정
├── .prettierrc # prettier는 전역으로 설정
└── package.json 

우선 client/와 server/와 같은 애플리케이션은 별도로 관리하기 위해 apps/ 폴더에 배치했다. 그리고 모든 애플리케이션에서 공통으로 사용하는 패키지나 설정 파일은 packages/ 폴더에 모아두기로 했다.

개발 스타일의 일관성을 유지하기 위해 전체 프로젝트에서 공통으로 사용할 TypeScript 설정과 ESLint 설정은 packages/ 폴더에서 공통으로 관리하려고 했다. 그리고 각 프로젝트에서 추가 설정이 필요한 경우 이 공통 설정을 extends해서 사용할 수 있도록 했다.

Prettier 설정은 단순 코드 포멧팅 설정이라서 apps/ 하위 프로젝트마다 별도로 설정이 필요한 경우가 드물 것이라고 판단했다. 그래서 설정 파일을 굳이 packages/ 안에 넣어 공유 설정으로 관리하지 않고, 프로젝트 최상위에 배치해서 사용하는 것이 효율적이라고 판단했다.

pnpm-workspace.yaml 설정

pnpm 워크스페이스를 사용하면 monorepo에서 여러 프로젝트 간의 의존성을 효율적으로 관리할 수 있다.

eslint와 tsconfig 같은 공통 설정을 client와 server 프로젝트에서 쉽게 공유하기 위해, root에 pnpm-workspace.yaml 파일을 생성하고 아래 내용을 추가한다.

packages:
  - "apps/*" # apps 하위 프로젝트를 워크스페이스에 포함
  - "packages/*" # packages 하위 설정 패키지를 워크스페이스에 포함

 

 

pnpm 옵션

추가로 알아두면 좋은 pnpm 옵션을 정리했다.

  • -w: 워크스페이스의 루트 패키지에만 패키지를 설치할 때 사용
  • pnpm -w add <package_name>
  • -F: 특정 워크스페이스 패키지를 대상으로 명령을 실행
  • "dev:server": "pnpm -F server dev"
  • -r: 모든 하위 패키지에 대해 재귀적으로 명령을 실행
  • "install:all": "pnpm install -r"

Client와 Server 기본 프로젝트 설정

현재 프로젝트 기술 스택에 맞게 client와 server에 각각 react와 express 프로젝트를 생성했다.

client: apps/ 에서 vite로 react + typescript 프로젝트 세팅

pnpm create vite client --template react-ts

package.json 내용의 일부다. 기본으로 설치되는 패키지는 다음과 같다.

...
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.13.0",
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.3",
    "eslint": "^9.13.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.14",
    "globals": "^15.11.0",
    "typescript": "~5.6.2",
    "typescript-eslint": "^8.11.0",
    "vite": "^5.4.10"
  }

eslint는 따로 설정해줄 것이기 때문에 관련 패키지 dependencies는 삭제했다.

...
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1"
  },
  "devDependencies": {
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.3",
    "typescript": "~5.6.2",
    "vite": "^5.4.10"
  }

server: apps/server/에 Express 프로젝트 세팅

pnpm init
pnpm add express
pnpm add --save-dev @types/express
pnpm add --save-dev ts-node
{
  "name": "server",
  "version": "1.0.0",
  "scripts": {
    "dev": "ts-node src/index.ts",
    "build": "tsc",
  },
  "dependencies": {
    "express": "^4.21.1",
  },
  "devDependencies": {
    "@types/express": "^4.17.13",
    "ts-node": "^10.0.0"
  }
}

 

추가로 설치한 라이브러리는 각각 검색해서 설치 방법을 찾아서 진행했기 때문에 설명은 생략한다.

 

참고

패키지 버전관리 틸드(~)와 캐럿(^)

 

Prettier 설정

vscode에서 prettier 플러그인 설치

https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

 

prettier 플러그인을 꼭 설치해야 하나요?

네… 처음에는 prettier 플러그인 없이 프로젝트에 prettier 패키지만 설치하고 VSCode formatOnSave 설정과 직접 연결하면 어떨까 생각했다. 그러면 실수로 prettier 플러그인을 설치하지 않은 팀원도 자동으로 포매팅이 적용될 수 있기 때문이다. 하지만 prettier 커스텀 설정 파일을 VSCode formatOnSave와 연결하는 것 자체가 prettier 플러그인이 제공하는 기능이었고, 그래서 prettier 플러그인 설치가 필수라는 것을 알게 되었다.

{
	"editor.formatOnSave": true,
	"editor.tabSize": 2, // editor 기본 포멧팅 기능
  "editor.insertSpaces": true
}

 

프로젝트 루트에 .vscode/ 폴더 생성 후 settings.json 파일 생성

프로젝트 루트에 .vscode/ 폴더를 생성하고 settings.json을 추가하면, 해당 프로젝트(워크스페이스)에서만 적용되는 VSCode 설정을 관리할 수 있다. 이 설정은 사용자의 로컬 VSCode 설정(개인 settings.json)보다 우선적으로 적용된다.

{
  "editor.formatOnSave": true, // 저장 시 코드 스타일 정리
  "editor.defaultFormatter": "esbenp.prettier-vscode", // prettier 플러그인의 고유 아이디
  "prettier.requireConfig": true, // 설정 파일이 있을 때만 활성화(prettier 플러그인이 있을 때만 사용할 수 있는 설정)
}

프로젝트 루트에 .prettierrc 파일 생성

Prettier는 설정 파일을 찾을 때 현재 포매팅하려는 파일이 있는 디렉토리에서 시작해서 설정 파일을 찾을 때까지 상위 디렉토리로 이동한다. 단, 프로젝트의 일관된 코드 스타일을 제공하기 위해 프로젝트 루트 디렉토리까지만 검색하고 그 이상은 검색하지 않는다.

{
  "printWidth": 100, // 한 줄 최대 길이 100자 제한
  "tabWidth": 2, // 들여쓰기 간격
  "singleQuote": true, // 문자열 작은 따옴표
  "trailingComma": "es5", // ES5에서 허용되는 위치와 객체 배열의 마지막 요소 뒤에 쉼표 추가
  "semi": true, // 문장 끝에 세미콜론 추가
  "arrowParens": "always", // 화살표 함수의 매개변수 괄호로 감싸기
  "plugins": ["prettier-plugin-tailwindcss"] // Tailwind CSS 클래스 자동 정렬
}

 

Tailwind CSS 설정은 클라이언트에서만 사용할 예정이라 전역 설정으로 두는 것이 조금 신경 쓰였다. 그러나 prettier-plugin-tailwindcss 하나 때문에 Prettier 설정을 packages 폴더에 공통으로 두고, 클라이언트에서 이를 extends해서 prettier-plugin-tailwindcss와 합치는 방식은 너무 번거롭다고 생각해 그대로 전역 설정으로 사용하기로 결정했다.

프로젝트 루트에 .prettierignore 파일 생성

prettier로 포멧팅하지 않을 파일이나 폴더를 설정한다.

node_modules/
dist/
build/

EsLint 설정

Prettier는 단순하게 코드 스타일을 예쁘게 수정하는 기능이라면, EsLint 미사용 변수, 안전하지 않는 코드 패턴, 잘못된 변수 명명 규칙 등 단순 코드 스타일뿐만 아니라 코드 품질을 올릴 수 있는 규칙을 정의한다.

앞서 말한 것처럼 EsLint는 클라이언트와 서버가 공통으로 사용할 설정을 공유하면서도, 각자의 환경에 맞게 커스터마이즈해서 사용할 수 있도록 packages/ 하위에서 관리한다.

vscode에서 EsLint 플러그인 설치

https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint

packages/eslint/에 EsLint 설치

client와 server에서 공통으로 사용할 규칙을 설정하기 위해 packages/eslint 폴더에 우선 ESLint를 설치한다.

pnpm init
pnpm create @eslint/config@latest

다음으로는 공통으로 사용할 설정을 고려해 옵션을 체크한다.

설치된 dependencies는 다음과 같다.

  "devDependencies": {
    "@eslint/js": "^9.14.0",
    "eslint": "^9.14.0",
    "globals": "^15.12.0",
    "typescript-eslint": "^8.13.0",
  }

eslint-config-prettier 설치하기

eslint에서 prettier와 겹치는 포멧팅 룰을 꺼주는 eslint-config-prettier를 설치한다.

pnpm install --save-dev eslint-config-prettier // "eslint-config-prettier": "^9.1.0"

 

참고

EsLint와 Prettier의 충돌 막기

packages/eslint/ 폴더에 index.js 파일 생성

client와 server에서 공통으로 사용할 eslint 파일을 packages/eslint/ 폴더 하위에 index.js 파일을 만들어서 작성한다.

팀원분이 주신 파일은 현재 eslint 버전과 맞지 않아서 다음 명령어로 마이그레이션을 진행했다.

pnpx @eslint/migrate-config .\\.eslintrc 

 

참고

잠깐, eslint의 새로운 config 시스템에 대해 아시나요?

이후 필요한 부분은 수정해서 최종 index.js는 다음과 같다.

import js from '@eslint/js';
import globals from 'globals';
import tsEslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import eslintConfigPrettier from 'eslint-config-prettier';

export default {
  jsCommended: js.configs.recommended,
  base: {
    files: ['**/*.{js,ts}'],
    ignores: [
      '**/node_modules/**',
      '**/dist/**',
      '**/build/**',
      '**/*.d.ts',
      '**/next-env.d.ts',
      '**/vite-env.d.ts',
      '**/*.config.js',
      '**/*.config.ts',
      '**/.storybook/**',
    ],
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
      parser: tsParser,
      ecmaVersion: 12,
      sourceType: 'module',
    },
    plugins: {
      '@typescript-eslint': tsEslint,
    },
    rules: {
      'no-unused-vars': 'warn',
      'no-constant-condition': 'warn',
      'prefer-arrow-callback': 'error',
      curly: ['error', 'all'],
      'no-else-return': 'error',
      '@typescript-eslint/naming-convention': [
        'error',
        {
          selector: 'variable',
          format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
          leadingUnderscore: 'allow',
        },
        {
          selector: 'variable',
          filter: {
            regex: 'store$',
            match: true,
          },
          format: ['camelCase'],
          suffix: ['store'],
        },
        {
          selector: 'function',
          format: ['camelCase'],
        },
        {
          selector: 'function',
          filter: {
            regex: '^use[A-Z]',
            match: true,
          },
          format: ['camelCase'],
          prefix: ['use'],
        },
        {
          selector: 'class',
          format: ['PascalCase'],
        },
        {
          selector: ['typeAlias'],
          format: ['PascalCase'],
          prefix: ['T'],
          filter: {
            regex: 'Props$',
            match: false,
          },
        },
      ],
      camelcase: 'error',
      'spaced-comment': ['error', 'always'],
      'multiline-comment-style': ['error', 'starred-block'],
      'lines-around-comment': ['error', { beforeBlockComment: true }],
    },
  },
  ignorePrettier: eslintConfigPrettier,
};

그리고 작성한 index.js 파일을 packages/eslint 패키지에서 기본적으로 불러오도록 package.json을 다음과 같이 수정한다.

{
  "name": "@packages/eslint",
  "version": "0.0.0",
  "private": true,
  "main": "index.js",
  "type": "module",
  "files": [
    "index.js"
  ],
  "devDependencies": {
    "@eslint/js": "^9.14.0",
    "eslint": "^9.14.0",
    "eslint-config-prettier": "^9.1.0",
    "globals": "^15.12.0",
    "typescript-eslint": "^8.13.0"
  }
}

client에서 eslint 기본 설정 불러와서 추가 설정하기

packages/eslint 워크스페이스를 불러오기 위해 client 폴더에 있는 package.json의 devDependencies를 다음처럼 수정한다.

  "devDependencies": {
    "@packages/eslint": "workspace:*",
    ...

그리고 client 폴더에 eslint.config.js 파일을 만들고 packages/eslint에 있는 eslint 공통 설정을 불러온 뒤에 추가로 필요한 설정을 추가한다.

import defaultConfig from '@packages/eslint';
import react from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
import storybookPlugin from 'eslint-plugin-storybook';

export default [
  defaultConfig.jsCommended,
  defaultConfig.base,
  {
    files: [...defaultConfig.base.files, '**/*.{jsx,tsx}'],
    plugins: {
      ...defaultConfig.base.plugins,
      react,
      'react-hooks': reactHooksPlugin,
      'react-refresh': reactRefreshPlugin,
      storybook: storybookPlugin,
    },
    languageOptions: {
      ...defaultConfig.base.languageOptions,
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
    },
    settings: {
      react: {
        version: '18.3.1', // 버전을 명시해야 eslint가 react 버전을 찾느라 느려지지 않음
      },
      'import/resolver': {
        alias: {
          map: [['@', './src']],
          extensions: ['.ts', '.tsx', '.js', '.jsx'],
        },
      },
    },
  },
  defaultConfig.ignorePrettier,
];

.vscode/settings.json 에 EsLint 설정 추가

ESLint 규칙을 어긴 부분에 빨간 줄로 표시해주고, 파일을 저장할 때마다 자동으로 해당 규칙에 맞게 코드를 수정하기 위해 .vscode/settings.json 에 추가 설정을 해준다.

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.requireConfig": true,
  "eslint.enable": true, // eslint 플러그인 활성화
  "eslint.useFlatConfig": true, // flat config 형식 사용
  "eslint.validate": ["javascript", "typescript", "javascriptreact", "html", "typescriptreact"], // eslint가 검사할 파일 타입 명시
  "editor.codeActionsOnSave": { // 파일 저장 시 eslint 자동으로 적용
    "source.fixAll.eslint": "explicit"
  },
  "eslint.workingDirectories": [ // eslint가 인식할 디렉토리 설정
    {
      "directory": "apps/client",
      "!cwd": true, // 상대 경로 사용
      "changeProcessCWD": true, // 실행 중에도 상대 경로 사용
      "configFile": "eslint.config.js"
    },
    {
      "directory": "apps/server",
      "!cwd": true,
      "changeProcessCWD": true,
      "configFile": "eslint.config.js"
    }
  ]
}

 

참고

eslint 규칙을 어겨도 editor에 빨간 줄이 안 뜸

참고

https://velog.io/@yoosion030/PNPM으로-모노레포-구축하기

https://d2.naver.com/helloworld/0923884

https://programmingsummaries.tistory.com/385

https://docs.npmjs.com/cli/v10/configuring-npm/package-json

https://keepworking.tistory.com/34

모노레포 기반의 환경설정 과정

https://prettier.io/docs/en/configuration.html

https://code.visualstudio.com/docs/getstarted/settings#_workspace-settings

https://velog.io/@jiynn_12/개발자로-협업하기-husky

https://eslint.org/blog/2022/08/new-config-system-part-2/

https://imch.dev/posts/pnpm-a-manager-what-is-not-flat/

https://prettier.io/docs/en/integrating-with-linters.html#notes

https://techblog.woowahan.com/15903/

https://github.com/prettier/eslint-config-prettier

반응형