어드민 모노레포 적용기 - (1)

s2ksh77·2024년 6월 11일
1

monorepo

목록 보기
1/2
post-thumbnail

들어가기에 앞서 회사 백오피스 어드민 프로젝트를 여러 개 진행하게 되면서 모노레포의 필요성을 느끼게 되었고, 이에 회사 연구본부 방에 공유했었던 가이드를 옮겨 작성한 내용입니다.

어드민 모노레포 전환을 진행하게 되면서 얻게 된 지식들의 전파와 더불어 해당 가이드를 통해 모노레포 적용을 하심에 있어 도움이 되고자 가이드 작성 합니다.

ps. 저희도 이번에 전환하고 다양한 상황에 아직 대응해보지 못한 미흡한 부분이 있으니, 서로 트러블 슈팅 되는 부분들은 공유되었으면 좋겠습니다.

모노레포 필요성

  1. 현재 저희 팀에서의 어드민 사업건 [ AppMaster, FoodistAdmin, EkrAdmin ] 과 같이 각 프로젝트 간의 비슷한 기능 중복 구현 요구.
  2. 각각의 eslint와 같은 config 설정들이 맞추려고 노력했지만 독자적으로 스타일이 전부 달라 유지보수하기 힘듬.
  3. 매번 사업건이 생길때마다 깃을 새로파서 진행해야 하는 불편한 점.

모노레포 선택한 이유

  • 코드 재사용, 버전 관리, 종속성 관리 등의 장점을 제공
  • 매 환경마다 git을 옮겨가며 그에 맞는 개발을 진행함에 있어 복잡도를 줄일 수 있음
  • 완전한 독립성을 주는 멀티레포에 비해 하나의 일관성 있는 config 설정으로 효율성을 높이게 됨

어드민 모노레포 구조

위에 그림의 모노레포 구조처럼 하나의 config 설정을 두고 각 apps 하위에 app들의 설정들을 두어 각각을 개별로 동작할 수 있게 하고, 또한 packages 하위에 공통 로직 및 compontents, utils 등을 두어 개별 앱들에서 참조 할 수 있도록 구조를 적용 하였습니다.

가이드

들어가기에 앞서

저희 팀은 지난 2023년부터 yarn berry를 도입하여 PnP mode와 zero-install을 통해 패키지들간의 의존성 관리를 수월하게 해왔었습니다.

이번 모노레포로 진행하게 되면서 yarn berry에서 진행하는 workspace를 통해 모노레포를 구성했어도 되었지만, pnpm을 사용한 모노레포를 구성하면서 PnP mode 뿐만 아니라 symbolic link로도 의존성 관리가 쉽게 되고 모노레포의 기본적인 기능에 충실하다고 생각하여 결정하게 되었습니다.

ps. yarn berry 든 pnpm 이든지 선택일 것 같습니다.

설치 및 설정 과정

  • 설치하기

    PNPM 설치하기

    npm install -g pnpm  || yarn add pnpm

    pnpm-workspace.yaml

    packages:
      - 'apps/*'      // app들을 위한 디렉토리
      - 'packages/*'  // 공통 package등 library를 관리하기 위한 디렉토리

루트 package.json 설정하기

{
  "name": "admin-front",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "workspaces": [
    "apps/*",
    "packages/*"
  ],
  "scripts": {
    "app_master_dev": "pnpm -F app-master dev",
    "foodist_dev": "pnpm -F foodist-admin dev",
    "ekr_dev": "pnpm -F ekr-admin dev",
    "app_master_build": "pnpm -F app-master build",
    "foodist_build": "pnpm -F foodist-admin build",
    "foodist_deploy": "pnpm -F foodist-admin app_deploy"
  },
  .... 중략
}
  • workspaces에서 서브 패키지들의 경로에 대해 설정
  • scripts에서는 pnpm 의 -F filter 옵션을 통해 어떤 서브 앱의 명령어를 시행할지 정의

각 앱 및 패키지 설정하기

{
  "name": "app-master",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build && build.bat app_master 1",
    "deploy": "yarn build && ./deploy.sh",
    "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  ... 중략
}
  • name 으로 설정한 app-master가 root 디렉토리에서 참조할 명칭과 같아야 합니다.

root 디렉토리의 dependencies & devDependencies 설정

  "dependencies": {
    "mobx": "^6.12.0",
    "mobx-react": "^9.1.0",
    "mobx-react-lite": "^4.0.5",
    "react-hook-form": "^7.48.2",
    "react-router-dom": "^6.19.0",
    "react-virtuoso": "^4.6.2"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3.0.2",
    "@types/node": "^20.2.5",
    "@typescript-eslint/eslint-plugin": "^5.59.8",
    "@typescript-eslint/parser": "^5.59.8",
    "@vitejs/plugin-react": "^4.0.0",
    "eslint": "^8.42.0",
    "eslint-plugin-import": "^2.29.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.1",
    "typescript": "^5.1.3",
    "vite": "^4.3.9",
    "vite-plugin-svgr": "^3.2.0",
    "vite-tsconfig-paths": "^4.2.0"
  },
  • [dependencies]
    모노레포 하위의 앱들이 공통적으로 사용할 패키지들인 mobx, react-router-dom 등
    - react 버전은 현재 foodist에서 17버전을 사용하고 있고, app-master와 ekr에는 18버전을 따로 명시해두었습니다.
  • [devDependencies]
    모노레포 전역적으로 사용하게 될 config 설정 eslint ts 설정 등

각 앱에서의 dependencies & devDependencies 설정

  • app master
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@wapl/core": "0.1.0-sas",
    "@wapl/superapp-websocket": "^1.0.23",
    "@wapl/ui": "^0.3.1-superapp"
  },
  "devDependencies": {
    "@types/react": "^18.2.8",
    "@types/react-dom": "^18.2.4"
  }
  • ekr 농어촌 공사
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@wapl/core": "0.1.0-sas",
    "@wapl/superapp-websocket": "^1.0.23",
    "@wapl/ui": "^0.3.1-superapp",
    "admin-common": "workspace:^0.0.1"
  },
  "devDependencies": {
    "@types/react": "^18.2.8",
    "@types/react-dom": "^18.2.4"
  }
  • foodist admin
  "dependencies": {
    "@wapl/calendar": "0.0.38-foodist",
    "@wapl/core": "0.4.120-foodist",
    "@wapl/foodist-coaching": "0.2.17",
    "@wapl/ui": "0.2.40-foodist",
    "ag-grid-community": "^30.0.0",
    "ag-grid-react": "^30.0.0",
    "axios": "^1.4.0",
    "dayjs": "^1.11.9",
    "dompurify": "^3.0.6",
    "exceljs": "^4.3.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-beautiful-dnd": "^13.1.1",
    "react-csv": "^2.2.2",
    "recharts": "^2.10.4"
  },
  "devDependencies": {
    "@types/dompurify": "^3",
    "@types/prop-types": "^15",
    "@types/react": "17.0.2",
    "@types/react-dom": "^17.0.2",
    "@types/react-beautiful-dnd": "^13",
    "@types/react-csv": "^1.1.3",
    "@types/react-is": "^17",
    "prettier": "^2.8.8",
    "prop-types": "^15.8.1",
    "react-is": "^18.2.0"
  }

이처럼 각 환경별로 요구되는 core 및 ui 버전 혹은 각 패키지에서 주되게 사용하고 있는 버전들을 각 앱의 package.json에 명시해두어 사용하게 됩니다.

packages 하위 공통 패키지

{
  "name": "admin-common",
  "version": "0.0.1",
  "main": "./index.ts",
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "ag-grid-community": "^30.0.0",
    "ag-grid-react": "^30.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.8",
    "@types/react-dom": "^18.2.4"
  },
  "peerDependencies": {
    "@wapl/ui": "*",  // 같은 모노레포에서 사용하기 위해 버전
    "@wapl/calendar": "0.0.38-foodist"
  }
}
  • name에 공통으로 사용하고자하는 패키지 명을 작성 해둡니다.
실제 공통 컴포넌트 사용 예시
import { Table, TableHeaderRow } from 'admin-common';

common을 사용하고자 하는 패키지에서의 package.json

"admin-common": "workspace:^0.0.1"

root 디렉토리에서 tsconfig.json 설정

{
  "compilerOptions": {
    /* Modules */
    "baseUrl": ".",
    "module": "commonjs",
    "moduleResolution": "node",
    "paths": {
      "@/*": ["src/*"],
      "@common/*": ["src/common/*"],
      "@constants/*": ["src/common/constants/*"],
      "@wcomponents/*": ["src/web/components/*"],
      "@mcomponents/*": ["src/mobile/components/*"],
      "@contexts/*": ["src/common/contexts/*"],
      "@storybook/*": ["src/stories/calendar/*"]
    },
    "resolveJsonModule": true,

    /* Type Checking */
    "noFallthroughCasesInSwitch": true,
    "strict": true,
    "strictNullChecks": false,
    "typeRoots": ["./node_modules/@types", "./src/@types"],

    /* Language and Environment */
    "experimentalDecorators": true,
    "jsx": "react-jsx",
    "lib": ["dom", "dom.iterable", "ESNext", "ESNext.AsyncIterable"],
    "target": "es2020",

    /* Interop Constraints */
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,

    /* Emit */
    "declaration": true,
    "downlevelIteration": true,
    "inlineSources": true,
    "preserveConstEnums": true,
    "removeComments": false,
    "sourceMap": true,
    "noEmit": false,

    /* Completeness*/
    "allowJs": false,
    "skipLibCheck": true,

    "outDir": "./dist",
    "noImplicitAny": false
  },
  "include": ["src/**/*", "**/@types/*", "**/assets/index.d.ts"],
  "exclude": ["**/dist/*"]
}
  • 공통에서 전역으로 사용할 tsconfig.json

하위 각 앱에서의 tsconfig 설정

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "./dist",
    "baseUrl": "./"
  }
}
  • 추가적으로 더 설정하고 싶은 앱 tsconfig에 설정하게 되면 overrides 됩니다.

마무리하며

2024년 3월에 모노레포를 적용하여 머지 후 한달간 진행하는 동안 별다른 큰 이슈는 발생하지 않았습니다.

추가적으로 현재 구현된 모노레포에 turborepo 도 함께 구축하여 사업건 별로 빠른 빌드 시스템 설정 및 배포 시스템을 구축 해볼 예정입니다. (TODO)

앞으로 코어나 ui 혹은 다른 프로젝트 내에서 모노레포가 적용되어 유지보수하기 쉬운 환경에 조금이나마 도움이 되었으면 좋겠습니다.

문의나 궁금한 점은 연락주시면 감사하겠습니다!

profile
오너십을 가지고 끊임없이 더 나은 방향을 고민하는 개발자 입니다. 새로운 기술을 적용하고 배우는 것을 좋아합니다.

0개의 댓글