Post

Monorepo 01

Monorepo 01

Monorepo?

모노레포는 하나의 버전 관리 저장소(Repository) 안에 여러 개의 독립적인 프로젝트, 애플리케이션, 라이브러리 코드를 모두 포함하여 관리하는 방식. 이는 각 프로젝트마다 별도의 저장소를 구성하는 전통적인 방식(Polyrepo 또는 Multirepo)과는 대조됨.

React 환경에서 모노레포를 사용하는 이점

React 기반 프론트엔드 개발에서 모노레포를 사용하면 다음과 같은 주요 이점을 얻을 수 있다

장점설명
코드 재사용성 극대화여러 React 애플리케이션(예: 사용자 앱, 관리자 앱)이 공통의 UI 컴포넌트 라이브러리(ui-kit)나 공통 로직(utils)을 쉽게 공유하고 가져와 사용 가능. 별도의 배포 과정 없이 내부 패키지처럼 사용 가능.
원자적(Atomic) 커밋 및 리팩토링공유 라이브러리의 버그를 수정하거나 API를 변경할 때, 해당 라이브러리를 사용하는 모든 애플리케이션의 코드를 하나의 커밋으로 동시에 업데이트하고 테스트할 수 있어 안정성이 높다
일관된 개발 환경모든 프로젝트가 하나의 루트 레벨에서 단일한 설정 파일 (ESLint, Prettier, TypeScript 설정 등)을 공유할 수 있어, 개발 표준과 품질을 일관성 있게 유지 가능
효율적인 의존성 관리pnpm이나 Yarn의 Workspaces 기능으로 의존성을 루트 레벨로 호이스팅(Hoisting)하여, 중복되는 패키지를 한 번만 설치하고 디스크 공간을 절약 가능

Monorepo 실례

최신 및 추천되는 방식은 Vite + pnpm + Tailwind CSS JIT 모드 + Workspace Protocol 을 사용하는 Monorepo 구성

예제 Monorepo 구조

1
2
3
4
5
6
7
8
9
10
11
12
my-monorepo/
├─ packages/
│  ├─ shared/
│  └─ tailwind-config/
├─ apps/
│  ├─ web/
│  └─ admin/
├─ tailwind.config.js
├─ postcss.config.js
├─ vite.config.js
├─ pnpm-workspace.yaml
├─ package.json

1. 프로젝트 초기화

1
2
mkdir my-monorepo
cd my-monorepo
1
2
3
4
5
# pnpm 설치 (node.js가 설치되어 있어야 함)
npm install -g pnpm

# monorepo 초기화
pnpm init

pnpm-workspace.yaml 생성

1
2
3
packages:
  - 'apps/*'
  - 'packages/*'

2. Tailwind 공통 설정 패키지 (optional)

Tailwind 설정을 여러 앱에서 공유하기 위해 별도 패키지로 관리하는 것이 좋다.

1
2
3
mkdir -p packages/tailwind-config
cd packages/tailwind-config
pnpm init

tailwind.config.js를 루트에 생성:

// my-monorepo/tailwind.config.js
module.exports = {
  content: [
    './apps/**/*.{js,jsx,ts,tsx}',
    './packages/**/*.{js,jsx,ts,tsx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
1
2
3
4
5
6
7
8
9
10
11
// packages/tailwind-config/index.js
// 다른 앱에서 tailwind.config.js를 공유하기 위한 파일
module.exports = {
  content: {
    files: ['./apps/**/*.{js,jsx,ts,tsx}', './packages/**/*.{js,jsx,ts,tsx}'],
  },
  theme: {
    extend: {},
  },
  plugins: [],
}

package.json (tailwind-config):

1
2
3
4
{
  "name": "tailwind-config",
  "version": "1.0.0"
}

3. React 앱 생성 (Vite 기반)

Web 앱 생성

1
2
3
mkdir -p apps/web
cd apps/web
pnpm create vite@latest . --template react

Admin 앱 생성 (또 다른 React 앱)

1
2
3
mkdir -p apps/admin
cd apps/admin
pnpm create vite@latest . --template react

4. Tailwind CSS 설치

각 앱에 Tailwind 설치 (또는 공통 설치 가능)

1
2
3
cd ../../apps/web
pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

tailwind.config.js는 루트에 있는 것을 참조하게 한다.

1
2
3
4
// apps/web/tailwind.config.js
module.exports = {
  presets: [require('../../tailwind.config')],
}

postcss.config.js:

1
2
3
4
5
6
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

5. 공유 라이브러리 생성

1
2
3
mkdir -p packages/shared
cd packages/shared
pnpm init

React 공통 컴포넌트, 훅, 타입 등을 여기에 넣을 수 있다. </br> 예시: packages/shared/src/utils.ts

1
2
3
export function formatDate(date: Date) {
  return date.toLocaleDateString()
}

tsconfig.json 설정:

1
2
3
4
5
6
7
8
9
10
11
12
{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "declaration": true
  },
  "include": ["src"]
}

빌드 스크립트 추가:

1
2
3
4
5
{
  "scripts": {
    "build": "tsc"
  }
}

6. 앱에서 공유 라이브러리 사용

apps/web/package.json에 추가:

1
2
3
4
5
{
  "dependencies": {
    "shared": "workspace:*"
  }
}

컴포넌트에서 사용:

1
2
3
4
5
6
// apps/web/src/App.tsx
import { formatDate } from 'shared'

function App() {
  return <div>{formatDate(new Date())}</div>
}

7. TypeScript 설정 (루트 기준)

tsconfig.json에 path 추가 가능:

1
2
3
4
5
6
7
8
9
{
  "compilerOptions": {
    "types": ["vite/client"],
    "baseUrl": ".",
    "paths": {
      "@shared/*": ["./packages/shared/src/*"]
    }
  }
}

사용:

1
import { formatDate } from '@shared/utils'

최종 package.json (루트)

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": {
    "packages": ["apps/*", "packages/*"]
  },
  "scripts": {
    "dev:web": "cd apps/web && pnpm dev",
    "dev:admin": "cd apps/admin && pnpm dev",
    "build:web": "cd apps/web && pnpm build",
    "build:admin": "cd apps/admin && pnpm build"
  }
}

요약

  • Monorepo 도구: pnpm Workspaces
  • 빌드 도구: Vite
  • 스타일링: Tailwind CSS (공통 설정 공유)
  • 공통 라이브러리: packages/shared로 분리
  • TypeScript Path: path alias 설정으로 깔끔한 import
  • 빌드/개발: pnpm dev, pnpm build로 앱별 실행

자동 설치 스크립트 (Shell Script)

이 스크립트는 아래 구조를 자동으로 생성한다:

1
2
3
4
5
6
7
8
9
10
11
12
my-monorepo/
├─ apps/
│  ├─ web/
│  └─ admin/
├─ packages/
│  ├─ shared/
│  └─ tailwind-config/
├─ pnpm-workspace.yaml
├─ package.json
├─ tailwind.config.js
├─ postcss.config.js
├─ vite.config.js

실행방법

  1. 아래 스크립트를 복사하여 setup-monorepo.sh 파일로 저장합니다.
  2. 실행 권한 부여: chmod +x setup-monorepo.sh
  3. 실행: ./setup-monorepo.sh

setup-monorepo.sh 스크립트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#!/bin/bash

# 1. 기본 디렉토리 생성
mkdir -p my-monorepo/apps/web my-monorepo/apps/admin my-monorepo/packages/shared my-monorepo/packages/tailwind-config
cd my-monorepo || exit

# 2. pnpm 설치 (이미 설치되어 있으면 생략)
if ! command -v pnpm &> /dev/null
then
  echo "pnpm이 설치되지 않았습니다. 설치 중..."
  npm install -g pnpm
fi

# 3. 루트 package.json 생성
cat <<EOT > package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": {
    "packages": ["apps/*", "packages/*"]
  },
  "scripts": {
    "dev:web": "cd apps/web && pnpm dev",
    "dev:admin": "cd apps/admin && pnpm dev",
    "build:web": "cd apps/web && pnpm build",
    "build:admin": "cd apps/admin && pnpm build"
  }
}
EOT

# 4. pnpm-workspace.yaml 생성
cat <<EOT > pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
EOT

# 5. Tailwind 공통 설정
cat <<EOT > tailwind.config.js
module.exports = {
  content: {
    files: ['./apps/**/*.{js,jsx,ts,tsx}', './packages/**/*.{js,jsx,ts,tsx}'],
  },
  theme: {
    extend: {},
  },
  plugins: [],
}
EOT

cat <<EOT > postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
EOT

# 6. Vite config (루트에 기본 vite.config.js)
cat <<EOT > vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
})
EOT

# 7. 공통 TypeScript 설정
cat <<EOT > tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "types": ["vite/client"],
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["apps/**/*", "packages/**/*"],
  "references": [
    { "path": "./apps/web/tsconfig.json" },
    { "path": "./apps/admin/tsconfig.json" },
    { "path": "./packages/shared/tsconfig.json" }
  ]
}
EOT

# 8. Shared 패키지 생성
cd packages/shared || exit

cat <<EOT > package.json
{
  "name": "shared",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc"
  },
  "main": "dist/index.js",
  "types": "dist/index.d.ts"
}
EOT

mkdir src
cat <<EOT > src/utils.ts
export function formatDate(date: Date): string {
  return date.toLocaleDateString()
}
EOT

cat <<EOT > tsconfig.json
{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "strict": true,
    "declaration": true
  },
  "include": ["src"]
}
EOT

cd ../../..

# 9. Tailwind Config 패키지 생성
cd packages/tailwind-config || exit

cat <<EOT > package.json
{
  "name": "tailwind-config",
  "version": "1.0.0"
}
EOT

cat <<EOT > index.js
module.exports = {
  content: {
    files: [
      '../../apps/**/*.{js,jsx,ts,tsx}',
      '../../packages/**/*.{js,jsx,ts,tsx}'
    ]
  },
  theme: {
    extend: {},
  },
  plugins: [],
}
EOT

cd ../../..

# 10. Web 앱 생성
cd apps/web || exit

pnpm create vite@latest . --template react

# Web 앱에 Tailwind 설치
pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

cat <<EOT > tailwind.config.js
module.exports = {
  presets: [require('../../tailwind.config')],
}
EOT

cat <<EOT > tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "..",
    "types": ["vite/client"]
  },
  "include": ["src"]
}
EOT

# Web 앱에서 shared 사용
cd ../../packages/shared
pnpm build
cd ../../apps/web

# Web 앱에 공유 패키지 링크
cd ../../apps/web
pnpm add ../shared

# Web 앱 App.tsx 수정
cd src
cat <<EOT > App.tsx
import React from 'react'
import { formatDate } from 'shared'

function App() {
  return (
    <div className="p-4 bg-blue-500 text-white">
      Hello from Web App!
      <p>{formatDate(new Date())}</p>
    </div>
  )
}

export default App
EOT

cd ../../apps/web

# Web 앱에 Tailwind 설정 반영
cat <<EOT > postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
EOT

# Tailwind CSS 추가
cat <<EOT > tailwind.config.js
module.exports = {
  presets: [require('../../tailwind-config')]
}
EOT

# index.html에 Tailwind 적용
cd ../../apps/web
echo '<div id="root"></div>' > index.html
echo 'import React from "react";' > src/main.tsx
echo 'import ReactDOM from "react-dom/client";' >> src/main.tsx
echo 'import App from "./App";' >> src/main.tsx
echo 'ReactDOM.createRoot(document.getElementById("root")!).render(<App />);' >> src/main.tsx

# Web 앱에 TypeScript Path 설정
cd ../../apps/web
cat <<EOT > tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "types": ["vite/client"],
    "paths": {
      "@shared/*": ["../../packages/shared/src/*"]
    }
  },
  "include": ["src"]
}
EOT

cd ../../..

# 11. Admin 앱 생성
cd apps/admin || exit

pnpm create vite@latest . --template react

# Admin 앱에 Tailwind 설치
pnpm add -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

cat <<EOT > tailwind.config.js
module.exports = {
  presets: [require('../../tailwind.config')],
}
EOT

cat <<EOT > tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "types": ["vite/client"],
    "paths": {
      "@shared/*": ["../../packages/shared/src/*"]
    }
  },
  "include": ["src"]
}
EOT

cd ../../apps/admin
pnpm add ../shared

cd ../../apps/admin/src

# Admin App.tsx 수정
cat <<EOT > App.tsx
import React from 'react'
import { formatDate } from 'shared'

function App() {
  return (
    <div className="p-4 bg-green-500 text-white">
      Hello from Admin App!
      <p>{formatDate(new Date())}</p>
    </div>
  )
}

export default App
EOT

cd ../../admin

# 12. 모든 작업 완료
cd ../../

echo "✅ Monorepo 생성 완료!"
echo "👉 다음 명령어로 개발 시작:"
echo "cd apps/web && pnpm dev"
echo "또는"
echo "cd apps/admin && pnpm dev"

사용법

1
2
3
4
5
# 웹 앱 실행
cd apps/web && pnpm dev

# 관리자 앱 실행
cd apps/admin && pnpm dev

GitHub 템플릿 (선택 사항)

GitHub에 템플릿으로 사용하려면 아래와 같이 템플릿 저장소를 생성할 수 있다

  • 예시 저장소: https://github.com/your-username/react-tailwind-monorepo-template

템플릿 생성 시:

1
2
3
4
5
6
# GitHub 템플릿으로 생성
git init
git add .
git commit -m "Initial commit"
git remote add origin https://github.com/your-username/react-tailwind-monorepo-template.git
git push -u origin main
This post is licensed under CC BY 4.0 by the author.