모노레포

하니·2025년 1월 7일

React 길잡이

목록 보기
2/21

모노레포

다수의 프로젝트를 1 개의 레포지토리 내에서 관리하는 sw 개발 전략

멀티 레포지토리의 문제점

1. 프로젝트마다 서로 다른 컨벤션
프로젝트마다 다른 린트 환경과 코드 패턴들 (eslint, prettier, stylelint, tsconfig)

2. 새로운 서비스(프로젝트)를 구축하기 위한 비용
새로운 프로젝트를 생성할 때마다 서비스 환경 반복 세팅

3. 중복된 코드와 프로젝트 관리의 비효율
각 서비스에서 사용하는 프로젝트의 라이브러리 버전이 통일되지 X음
변경 사항이 생겼을 경우 모든 레포지토리 수정 필요
히스토리가 레포지토리별로 흩어져 있음

모노레포 도입을 위한 고민

1. 모노레포 통합 후 서비스별 배포 전략
서비스의 배포는 독립적으로 진행한다.
2. 사용 라이브러리의 자유도
일관된 개발자 경험을 위해 각 서비스마다 다양한 라이브러리를 사용하는 것은 제한한다.
3. 공통 모듈을 생성해야하는 비용
초기 공통 모듈에 들어가는 비용 < 완성된 모듈로 인해 향상된 개발 생산성

모노레포 도구

🔨 모노레포 도구 종류

  • Yarn
  • Lerna
  • Nx
  • Turborepo
YarnLernaNxTurborepo
zero install, pnp 개념 사용 가능- 가장 많이 쓰이며 오래됨
- 관리자가 바뀜(nx 만드는 곳)
- 툴체인이 많음
- 프레임워크 컨셉
-Vercel에서 인수
- Next.js, TypeScript 기본 사용
- 캐싱으로 빌드 최적화 기능

트렌드


https://npmtrends.com/lerna-vs-nx-vs-turbo


https://2024.stateofjs.com/ko-KR/libraries/monorepo_tools/

결론

선정한 모노레포 관리 도구 : Pnpm worspace + Turborepo

  • 패키지 매니저 : Pnpm workspace
  • 모노레포 관리 툴 : Turborepo

선정 이유

🔨 Pnpm workspace
최근 배민 등 많은 곳에서 사용중인만큼 뜨고 있는 패키지 매니저

🔨 Turborepo
- 모노레포 관리 툴 사용 이유
모노레포를 구축하는 과정에서 파이프라인을 구축하는게 복잡하다.
모노레포 관리 툴을 이용해 복잡한 스크립트 처리 위임
- Turbporepo 선정 이유
Pnpm workspace를 지원하는 모노레포 관리툴은 Turborepo, Nx를 많이 사용한다.
이후 프로젝트에 Next.js를 사용할 예정

실습

1. 설치(저장소 구조화)

npx create-turbo@latest

turborepo 구성

크게 packages 디렉토리와 apps 디렉토리로 나뉜다.

  • packages : 다른 프로젝트에서도 공유되어 사용할 수 있는 ui, eslint, typescript 설정
    ex) tailwind-config에 대한 워크스페이스
  • app : 운영중인 서비스들을 관리하는 폴더
    현재 Next.js 워크스페이스가 2개 생성되어 있다.web docs
    ex) ui 컴포넌트를 관리할 수 있는 storybook 워크스페이스, react-native 워크스페이스

ex) 모노레포 내 폴더 구조

무신사에서는 packages 폴더 역할을 2개로 분리하여 libs, packages 폴더로 구성하였다.
libs : 도메인에 종속되지 않고, 다양한 서비스에서 라이브러리 형태로 사용할 수 있는 폴더
packages : libs 패키지들을 사용해서 더 고도화된 기능을 사용하는 폴더

https://medium.com/musinsa-tech/journey-of-a-frontend-monorepo-8f5480b80661

typescript-config 구조

tsconfig.json : Typescript 프로젝트의 설정 파일
1. 컴파일러 기본 옵션
2. 프로젝트 구성 설정
include : 컴파일할 파일 구조 패턴
exclude : 컴파일에서 제외할 디렉토리, 파일
extends : 다른 tsconfig 파일의 설정을 성속

typescript-config 폴더 : Typescript 설정을 모아둔 패키지

  • base.json : 기본 설정
  • react-library.json : React 라이브러리용 설정
  • next.json : Next.js 프로젝트용 설정

2. 종속성 관리

package.json : Node.js 프로젝트의 핵심 구성 파일
1. 프로젝트 메타데이터
2. 의존성 관리dependencies
3. 스크립트 : 프로젝트에서 자주 사용하는 명령어를 정의한다.

외부 종속성 : npm 레지스토리에서 설치되는 패키지들
ex) React, Axios
내부 종속성 : 모노레포 내에서 만든 패키지들

// ./apps/web/package.json
{
  "dependencies": {
    "next": "latest", // 외부 종속성
    "@repo/ui": "workspace:*" // 내부 종속성
  }
}

✔️ 종속성 관리 명령어

// 공식문서 예제
pnpm install jest --save-dev --recursive --filter=web --filter=@repo/ui --filter=@repo/web

: 다음 패키지들web @repo/ui @repo/web에만 jest를 설치합니다.
그러면 각 페이지의 package.json에 jest가 추가된 것을 확인할 수 있다.

--filter : 특정 패키지를 대상으로 명령어 실행
--recursive : 하위 디렉토리의 모든 패키지에 적용
--save-dev : 개발 의존성으로 설치

✔️ 동일한 버전에 대한 종속성 유지
모든 패키지에서 동일한 버전에 대한 종속성을 유지하고자 한다면..
1. synpack, manypkg, sherif 도구 사용
2. 패키지 관리자 사용

pnpm up --recursive typescript@latest

: 모든 패키지의 typescript 의존성을 최신 버전으로 업데이트합니다.
3. IDEVSCode 사용
ex) next를 원하는 버전으로 업데이트
검색창에 정규식.* 아이콘 클릭해서 활성화하고, "next": ".*"를 입력한다.
나온 결과들을 새 버전으로 Replace All 해준다.
pnpm install 실행해서 lock 파일을 업데이트한다.

3. 내부 패키지 생성

모노레포에서 족속성으로 사용할 내부 패키지를 만든다.
내부 패키지 : 작업 공간의 구성 요소

즉, 여러 개의 프로젝트에서 공통으로 사용하는 패키지를 만든다고 생각하면 된다.
패키지 그래프

1️⃣ 빈 디렉토리 생성 ./packages/math

2️⃣ package.json 추가 ./package/math/package.json

{
  "name": "@repo/math",
  "type": "module",
  "scripts": { // TS 컴파일 관련
    "dev": "tsc --watch", // 코드 변경 시 자동 재컴파일
    "build": "tsc"
  },
  "exports": { // 다른 패키지에서 사용할 수 있는 진입점 정의
    "./add": { // import { add } from '@repo/math' 가능
      "types": "./src/add.ts",
      "default": "./dist/add.js"
    },
    "./subtract": {
      "types": "./src/subtract.ts",
      "default": "./dist/subtract.js"
    }
  },
  "devDependencies": {
    "@repo/typescript-config": "workspace:*", // 내부 패키지
      // @repo/typescript-config를 내부 패키지로 사용함으로써, Turborepo는 이를 인식하고 작업 순서를 자동으로 결정한다.
    "typescript": "latest"
  }
}

3️⃣ tsconfig.json 추가 ./package/math/tsconfig.json
이 패키지에 대한 TS 구성을 지정한다.

{
  "extends": "@repo/typescript-config/base.json",
  "compilerOptions": {
    "outDir": "dist", // 컴파일된 파일 저장 위치
    "rootDir": "src" // 소스 코드 위치
  },
  "include": ["src"], // src 폴더만 컴파일
  "exclude": ["node_modules", "dist"] // 제외
}
  • 컴파일 시 src의 구조가 dist에 동일하게 복사되는 것이다.
packages/
 └─ math/             # math 패키지
     ├─ src/          # 소스 코드
     │   ├─ add.ts
     │   └─ subtract.ts
     ├─ dist/         # 컴파일된 결과물
     │   ├─ add.js
     │   └─ subtract.js
     ├─ package.json
     └─ tsconfig.json

4️⃣ src 폴더 안에 2개의 파일 생성 add.ts subtract.ts
📁 ./packages/math/src/add.ts

export const add = (a: number, b: number) => a + b;

📁 ./packages/math/src/subtract.ts

export const subtract = (a: number, b: number) => a - b;

5️⃣ 패키지를 애플리케이션에 추가 apps/web/package.json
해당 패키지를 사용할 애플리케이션프로젝트의 package.json에 추가해준다.

  "dependencies": {
+   "@repo/math": "workspace:*",
    "next": "latest",
    "react": "latest",
    "react-dom": "latest"
  },

6️⃣ @repo/math를 web 애플리케이션에서 사용 가능

import { add } from '@repo/math/add';
 
function Page() {
  return <div>{add(1, 2)}</div>;
}
 
export default Page;

7️⃣ 빌드 작업 설정 위한 turobo.json 수정 ./turbo.json
빌드 결과물을 캐싱하여 다음 빌드 시 재사용한다.

{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],  // 다른 빌드가 먼저 실행되어야 함
      "outputs": [ // 빌드 결과물 캐싱 대상 지정
        ".next/**",             // Next.js 빌드 파일들
        "!.next/cache/**",      // Next.js 캐시 제외
        "dist/**"               // !!컴파일된 파일들!!
      ]
    }
  }
}

7️⃣ 실행
루트 디렉토리에서 실행 or package.json의 build 스크립트로 실행

$ turbo build
  • 빌드 순서
  1. @repo/math 패키지가 먼저 빌드된다.
  2. web 애플리케이션이 빌드된다.
    ➡️ 따라서 packages/math/dists의 코드를 web 앱이 사용할 수 있다.

참고

모노레포 도입 이유
Turborepo, Next.js, TypeScript를 이용한 프론트엔드 모노레포 적용기 | 인프콘2023
도구 선정 이유
프로젝트 구조 설명
모노레포 내 폴더 구조
공식문서-실습

profile
Hi, I am HANI Developer(╹◡╹). .....1hani me?

0개의 댓글