๐๏ธ pnpm workspace & filtering โ ๋ชจ๋ ธ๋ ํฌ์ ์
๐ ๊ฐ์
pnpm-workspace.yaml ์ค์ ๋ถํฐ --filter ์ ๋ ํฐ ๋ฌธ๋ฒ, workspace: ํ๋กํ ์ฝ, ํจํค์ง ๊ฐ ์์กด์ฑ๊น์ง โ ๋ชจ๋ ธ๋ ํฌ๋ฅผ pnpm ์ผ๋ก ์์ ์ ๋ณตํ๋ค
04. workspace & filtering
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ pnpm-workspace.yaml ๊ธฐ๋ณธ ๊ตฌ์ฑ
- ๐ workspace: ํ๋กํ ์ฝ
- ๐ฏ --filter ์ ๋ ํฐ
- ๐ pnpm -r recursive ๋ช ๋ น์ด
- ๐๏ธ ๋ชจ๋ ธ๋ ํฌ ์ค์ ๊ตฌ์ฑ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: ์ฝ 30๋ถ(์ ์ฒด) / ํต์ฌ ํํธ๋ง: 15๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[workspace.yaml ๊ตฌ์ฑ] โ [workspace: ํ๋กํ ์ฝ] โ [--filter ์ ๋ ํฐ] โ [recursive ๋ช ๋ น] โ [์ค์ ๋ชจ๋ ธ๋ ํฌ ๊ตฌ์ฑ]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
pnpm-workspace.yaml๋ก ๋ชจ๋ ธ๋ ํฌ๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค -
workspace:ํ๋กํ ์ฝ๋ก ๋ด๋ถ ํจํค์ง๋ฅผ ์๋ก ์์กด์ฑ์ผ๋ก ์ฐ๊ฒฐํ ์ ์๋ค -
--filter์ ๋ ํฐ๋ก ํน์ ํจํค์ง์๋ง ๋ช ๋ น์ ์คํํ ์ ์๋ค - CI ์์ ๋ณ๊ฒฝ๋ ํจํค์ง๋ง ๋น๋/ํ ์คํธํ๋ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ฑํ ์ ์๋ค
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'

- ๐ฃ ์์ฒ ( ์ ์ ): "์์ ๋์ด ๊ฐ์๊ธฐ ์ฐ๋ฆฌ ํ๋ก์ ํธ๋ฅผ ๋ชจ๋ ธ๋ ํฌ๋ก ๋ฐ๊พธ์๊ณ ํ์ จ์ด์. ํ๋ก ํธ์๋, ๋ฐฑ์๋, ๊ณตํต UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ๋์ ๋ ํฌ์ ํฉ์น๋์. ๊ทผ๋ฐ ์ ๋ชจ๋ ธ๋ ํฌ ์ค์ ํด๋ณธ ์ ์ด ์์ด์ ๋ง๋งํด์. Turborepo ๋ ์จ์ผ ํ๋์? Lerna ๋์? pnpm ๋ง์ผ๋ก ๋ชจ๋ ธ๋ ํฌ๊ฐ ๋๋ ๊ฑด๊ฐ์? ๊ณตํต ์ปดํฌ๋ํธ๋ฅผ ํ๋ก ํธ์๋์์ ์ฐ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํด์?"
- ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "์์ฒ ๋, pnpm ์์ฒด๊ฐ ์ด๋ฏธ ๊ฐ๋ ฅํ ๋ชจ๋
ธ๋ ํฌ ๋๊ตฌ์์.
pnpm-workspace.yamlํ๋๋ก ์์ํ๊ณ , ํจํค์ง ๊ฐ ์์กด์ฑ์workspace:ํ๋กํ ์ฝ๋ก ์ฐ๊ฒฐํด์. Turborepo ๋ ๋น๋ ์บ์ฑ์ ์ํด ๋์ค์ ์ถ๊ฐํ๋ฉด ๋๊ณ ์.--filter๋ก ํน์ ํจํค์ง์๋ง ๋ช ๋ น์ ์คํํ๊ณ ,-r๋ก ์ ์ฒด๋ฅผ ์ฌ๊ท์ ์ผ๋ก ๋๋ฆด ์ ์์ด์. ์ด๊ฒ๋ถํฐ ์ ๋๋ก ์๊ณ ๋๋ฉด Turborepo ๋ ๊ธ๋ฐฉ ์ดํด๋ผ์."
๐ค ์ ์์์ผ ํ๋๊ฐ
๊ท๋ชจ๊ฐ ์ปค์ง ํ๋ก์ ํธ๋ ํ์ฐ์ ์ผ๋ก ๋ชจ๋ ธ๋ ํฌ ๋ฅผ ๊ณ ๋ฏผํ๊ฒ ๋๋ค.
์์๋ค ์ปค๋ฎค๋ํฐ๊ฐ ์ฒ์์ ๋จ์ผ Next.js ์ฑ์ด์์ง๋ง, ์ง๊ธ์ ๋ค์์ด ํ์ํด์ก๋ค:
ํ์ฌ ์ํฉ:
โโโ youngsu-community (Next.js ํ๋ก ํธ์๋)
โโโ youngsu-admin (Next.js ์ด๋๋ฏผ)
โโโ youngsu-api (NestJS ๋ฐฑ์๋)
๋ฌธ์ :
- ๊ณตํต ํ์
์ ์ (User, Post, Comment ๋ฑ) ๊ฐ ์ธ ๊ณณ์์ ์ค๋ณต
- ๊ณตํต UI ์ปดํฌ๋ํธ (Button, Card ๋ฑ) ๋ฅผ ๋ชจ๋๊ฐ ๋ฐ๋ก ๋ง๋ค๊ณ ์์
- ํ ํ๋ก์ ํธ์์ ์์ ํ๋ฉด ๋ค๋ฅธ ๋ ๊ณณ์ ์๋์ผ๋ก ๋ณต์ฌํด์ผ ํจ
๋ชจ๋ ธ๋ ํฌ์ ํด๊ฒฐ์ฑ :
youngsu-monorepo/
โโโ apps/
โ โโโ community/ (Next.js ํ๋ก ํธ์๋)
โ โโโ admin/ (Next.js ์ด๋๋ฏผ)
โ โโโ api/ (NestJS ๋ฐฑ์๋)
โโโ packages/
โโโ ui/ (๊ณตํต UI ์ปดํฌ๋ํธ)
โโโ types/ (๊ณตํต ํ์
)
โโโ config/ (๊ณตํต ESLint, TypeScript ์ค์ )
๐๏ธ pnpm-workspace.yaml ๊ธฐ๋ณธ ๊ตฌ์ฑ
์ต์ ์ค์
# pnpm-workspace.yaml (๋ ํฌ ๋ฃจํธ ์์ญ์ ์์ฑ)
# ๐ก ์ด ํ์ผ ํ๋๊ฐ "์ฌ๊ธฐ์๋ถํฐ ๋ชจ๋
ธ๋ ํฌ์ผ!" ๋ผ๊ณ ์ ์ธํ๋ ํธ์ ๋ฑ๋ณธ ๊ฐ์ ์ญํ ์ ํฉ๋๋ค.
packages:
# ๐ฆ apps ํด๋ ๋ฐ์ ์๋ ๋ชจ๋ ํด๋๋ฅผ ํ๋์ ๊ฐ๋ณ ํ๋ก์ ํธ(Workspace ํจํค์ง)๋ก ์ทจ๊ธํฉ๋๋ค.
# ์: apps/community, apps/admin ๋ฑ
- 'apps/*'
# ๐ฆ packages ํด๋ ๋ฐ์ ์๋ ๊ฒ๋ค๋ ๋์ผํ๊ฒ ๊ฐ๋ณ ํจํค์ง๋ก ์ทจ๊ธํฉ๋๋ค.
# ์: packages/ui, packages/types ๋ฑ
- 'packages/*'
# ๋ง์ฝ ํ์ ํด๋๊ฐ ๋ ๊น๊ฒ ์๋ค๋ฉด ** (๊ธ๋ก๋ธ) ํจํด์ ์ธ ์๋ ์์ต๋๋ค.
- 'packages/**'
# ๐ก ๋๋ํ(!)๋ฅผ ๋ถ์ด๋ฉด ํน์ ํด๋๋ฅผ ๋ชจ๋
ธ๋ ํฌ ๋์์์ ์ ์ธ์ํฌ ์ ์์ต๋๋ค.
# ํ
์คํธ ํด๋๋ ์บ์ ํด๋๋ ํจํค์ง๊ฐ ์๋๋๊น ์ ์ธํด์ค๋๋ค.
- '!**/test/**'
- '!**/.cache/**'๊ฐ ํจํค์ง์ package.json
// packages/ui/package.json
{
"name": "@youngsu/ui",
"version": "1.0.0",
"main": "./src/index.ts",
"exports": {
".": "./src/index.ts"
}
}// packages/types/package.json
{
"name": "@youngsu/types",
"version": "1.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts"
}// apps/community/package.json
{
"name": "@youngsu/community",
"dependencies": {
"@youngsu/ui": "workspace:*",
"@youngsu/types": "workspace:*"
}
}๐ workspace ํจํค์ง ๊ฐ ์์กด์ฑ โ workspace: ํ๋กํ ์ฝ
workspace: ํ๋กํ ์ฝ์ด๋
๊ฐ์ workspace ์์ ํจํค์ง๋ฅผ ์์กด์ฑ์ผ๋ก ์ฐ๊ฒฐํ ๋ workspace: ํ๋กํ ์ฝ์ ์ฌ์ฉํ๋ค. npm ๋ ์ง์คํธ๋ฆฌ์์ ์ฐพ์ง ์๊ณ ๋ก์ปฌ ํจํค์ง๋ฅผ ์ง์ ์ฐธ์กฐ ํ๋ค.
{
"dependencies": {
"@youngsu/ui": "workspace:*", // ํ์ฌ ๋ก์ปฌ ๋ฒ์ ๊ทธ๋๋ก ์ฌ์ฉ
"@youngsu/types": "workspace:^", // SemVer ^ ๋ฒ์๋ก ๋ก์ปฌ ์ฐธ์กฐ
"@youngsu/config": "workspace:~" // SemVer ~ ๋ฒ์๋ก ๋ก์ปฌ ์ฐธ์กฐ
}
}| ํ๊ธฐ | ์๋ฏธ |
|---|---|
workspace:* | ๋ก์ปฌ ํจํค์ง์ ํ์ฌ ๋ฒ์ ์ ๊ทธ๋๋ก ์ฌ์ฉ (๊ฐ์ฅ ๋ง์ด ์) |
workspace:^ | ํ์ฌ ๋ฒ์ ์ ^1.2.3 ํํ๋ก ์ฐธ์กฐ |
workspace:~ | ํ์ฌ ๋ฒ์ ์ ~1.2.3 ํํ๋ก ์ฐธ์กฐ |
workspace:1.0.0 | ์ ํํ ๋ฒ์ ์ง์ |
publish ํ ๋ ๋ณํ
pnpm publish ๋๋ pnpm pack ์์ workspace:* ๊ฐ ์ค์ ๋ฒ์ ๋ฒ์๋ก ์๋ ๋ณํ ๋๋ค.
// ๋ฐฐํฌ ์ (๋ก์ปฌ)
{ "@youngsu/ui": "workspace:*" }
// ๋ฐฐํฌ ํ (์ค์ npm ํจํค์ง)
{ "@youngsu/ui": "^1.0.0" } // ํ์ฌ @youngsu/ui ๋ฒ์ ์ด 1.0.0 ์ด๋ฉดworkspace ํจํค์ง ์ถ๊ฐ
# @youngsu/community ์ @youngsu/ui ์์กด์ฑ ์ถ๊ฐ
pnpm --filter @youngsu/community add @youngsu/ui
# pnpm ์ด ์๋์ผ๋ก workspace: ํ๋กํ ์ฝ์ ๊ฐ์งํด์ ๋ก์ปฌ ์ฐ๊ฒฐ๐ฏ --filter ์ ๋ ํฐ ์์ ์ ๋ณต
๊ธฐ๋ณธ ํจํด ๋งค์นญ
# ์ ํํ ํจํค์ง๋ช
pnpm --filter @youngsu/ui build
# ์ค์ฝํ ์ ์ฒด
pnpm --filter "@youngsu/*" build
# ์ด๋ฆ ํจํด (์์ผ๋์นด๋)
pnpm --filter "*community*" dev
# ๋จ์ถํ -F
pnpm -F @youngsu/ui build์์กด์ฑ ๊ธฐ์ค ์ ํ
# @youngsu/ui ์ @youngsu/ui ๊ฐ ์์กดํ๋ ํจํค์ง๋ค ๋ชจ๋
pnpm --filter "@youngsu/ui..." build
# @youngsu/ui ์ ์์กดํ๋ ๋ชจ๋ ํจํค์ง๋ค (community, admin ๋ฑ)
pnpm --filter "...@youngsu/ui" build
# @youngsu/ui ์ ์ง์ ์์กด์ฑ๋ง (ui ์์ ์ ์ธ)
pnpm --filter "@youngsu/ui^..." build๋ณ๊ฒฝ๋ ํ์ผ ๊ธฐ์ค ์ ํ (CI ์์ ๊ฐ๋ ฅ)
# main ๋ธ๋์น ๋๋น ๋ณ๊ฒฝ๋ ํจํค์ง๋ง ๋น๋
pnpm --filter "[origin/main]" build
# main ๋๋น ๋ณ๊ฒฝ๋ ํจํค์ง + ๊ทธ์ ์์กดํ๋ ํจํค์ง ๋ชจ๋
pnpm --filter "...[origin/main]" build
# apps/ ๋๋ ํ ๋ฆฌ ๋ด ๋ณ๊ฒฝ๋ ํจํค์ง๋ง
pnpm --filter "{apps/**}[origin/main]" build๋๋ ํ ๋ฆฌ ๊ธฐ์ค ์ ํ
# apps/ ํ์ ๋ชจ๋ ํจํค์ง
pnpm --filter "./apps/**" dev
# ํ์ฌ ๋๋ ํ ๋ฆฌ ๊ธฐ์ค (cd apps/community ํ)
pnpm --filter "." dev๐ pnpm -r โ recursive ๋ช ๋ น์ด
-r ๋๋ --recursive ๋ workspace ์ ๋ชจ๋ ํจํค์ง์ ๋ช
๋ น์ ์คํํ๋ค.
# ๋ชจ๋ ํจํค์ง ์ค์น
pnpm -r install
# ๋ชจ๋ ํจํค์ง ๋น๋ (์์กด์ฑ ์์๋๋ก)
pnpm -r build
# ๋ชจ๋ ํจํค์ง ํ
์คํธ
pnpm -r test
# ๋ชจ๋ ํจํค์ง lint
pnpm -r lint
# ํน์ script ๊ฐ ์๋ ํจํค์ง์๋ง ์คํ
pnpm -r --if-present test๋ณ๋ ฌ vs ์์ฐจ ์คํ
# ๊ธฐ๋ณธ: ์์กด์ฑ ๊ทธ๋ํ ์์๋ก ์คํ (์์ฐจ)
pnpm -r build
# ๊ฐ์ ๋ณ๋ ฌ ์คํ (์์กด์ฑ ๋ฌด์ - ์ฃผ์)
pnpm -r --parallel build
# ๋์ ์คํ ์ ์ ํ
pnpm -r --workspace-concurrency=3 build๋ฃจํธ ํจํค์ง ํฌํจ
# ๋ฃจํธ package.json ์ ์คํฌ๋ฆฝํธ๋ ํฌํจํด์ ์คํ
pnpm -r --include-workspace-root build๐๏ธ ์์๋ค ์ปค๋ฎค๋ํฐ ๋ชจ๋ ธ๋ ํฌ ์ค์ ๊ตฌ์ฑ
์ต์ข ๊ตฌ์กฐ
youngsu-monorepo/
โโโ pnpm-workspace.yaml
โโโ package.json (๋ฃจํธ โ ๊ณตํต devDeps, scripts)
โโโ .npmrc
โโโ apps/
โ โโโ community/ (@youngsu/community)
โ โ โโโ package.json
โ โ โโโ src/
โ โโโ admin/ (@youngsu/admin)
โ โโโ package.json
โ โโโ src/
โโโ packages/
โโโ ui/ (@youngsu/ui)
โ โโโ package.json
โ โโโ src/
โโโ types/ (@youngsu/types)
โ โโโ package.json
โ โโโ src/
โโโ config/ (@youngsu/config)
โโโ eslint.js
โโโ tsconfig.base.json
pnpm-workspace.yaml
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'๋ฃจํธ package.json
// package.json (๋ชจ๋
ธ๋ ํฌ ๋ฃจํธ - ์ต์๋จ)
// ๐ก ๋ฃจํธ ํจํค์ง๋ ์์ฒด์ ์ผ๋ก ๋ฐฐํฌํ ์ฑ์ด ์๋๋ผ, ๋ชจ๋
ธ๋ ํฌ ์ ์ฒด๋ฅผ ๋ฌถ์ด์ฃผ๋ ๊ป๋ฐ๊ธฐ ์ญํ ์
๋๋ค.
{
"name": "youngsu-monorepo",
"private": true, // ๐ฆ [์ค์] ๋ฃจํธ ์์ฒด๋ฅผ npm์ ์๋ชป ๋ฐฐํฌํ๋ ์ค์๋ฅผ ๋ง์์ค๋๋ค.
"packageManager": "pnpm@9.15.0",
"engines": {
"node": ">=20.0.0",
"pnpm": ">=9.0.0"
},
"scripts": {
// ๐ฆ [ํต์ฌ] '-r' ์ต์
์ 'recursive(์ฌ๊ท)'์ ์ฝ์๋ก, ํ์์ ๋ชจ๋ ํจํค์ง๋ฅผ ๋๋ฉด์ ๋ช
๋ น์ด๋ฅผ ์คํํ๋ผ๋ ๋ป์
๋๋ค.
// ๐ก ์ ์ฒด ๋น๋: main ๋ธ๋์น ๊ธฐ์ค์ผ๋ก "๋ณ๊ฒฝ๋ ์์ค"๊ฐ ์๋ ํจํค์ง์ ๊ทธ์ ์ํฅ์ ๋ฐ๋ ํจํค์ง๋ง ์ถ๋ ค์ ๋น๋ํฉ๋๋ค. (์บ์ฑ ๊ทน๋ํ)
"build": "pnpm -r --filter \"[origin/main]...\" build",
// ๐ก ์ ์ฒด ๊ฐ๋ฐ๋ชจ๋ ์ผ๊ธฐ: --parallel ์ต์
์ ์ฃผ๋ฉด admin๊ณผ community ์ฑ ๋ชจ๋ ๋์์ ์คํ์ฐฝ์ด ๋น๋๋ค.
"dev": "pnpm -r --parallel --filter \"./apps/**\" dev",
// ํ์
๊ฒ์ฌ: ๋ชจ๋ ํจํค์ง์ ๋ํด ์์๋๋ก ์งํ
"typecheck": "pnpm -r typecheck",
// ํ
์คํธ: ๋ณ๊ฒฝ๋ ํจํค์ง๋ง ๋๋ํ๊ฒ ๊ณจ๋ผ์ ํ
์คํธ
"test": "pnpm --filter \"...[origin/main]\" test",
// ๐ก --if-present ์ต์
: ํ์ ํจํค์ง ์ค์ "lint" ์คํฌ๋ฆฝํธ๊ฐ ์ ํ์๋ ๊ณณ์์๋ง ์์์ ์คํํ๊ณ , ์์ผ๋ฉด ๋ฌด์ํฉ๋๋ค.
"lint": "pnpm -r --if-present lint"
},
"devDependencies": {
// ๐ฆ ์ ์ฒด ์ฑ์์ ๊ณตํต์ผ๋ก ์ฐ๋ ๋๊ตฌ(ํ์
์คํฌ๋ฆฝํธ, ESLint ๋ฑ)๋ ๋ฃจํธ์ ํ ๋ฒ๋ง ๊น๋๋ค.
// ๊ฐ ์ฑ๋ง๋ค ๋ฐ๋ณตํด์ ๊น ํ์๊ฐ ์์ด pnpm์ด ์์์ ๋ค ์ฐ๊ฒฐํด์ค๋๋ค.
"typescript": "^5.3.3",
"@types/node": "^20.11.0",
"eslint": "^8.57.0",
"prettier": "^3.2.5"
}
}์ฑ package.json (apps/community)
// apps/community/package.json
{
"name": "@youngsu/community",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"typecheck": "tsc --noEmit",
"lint": "next lint"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
// ๐ฆ [์ดํต์ฌ] ๋ชจ๋
ธ๋ ํฌ ๋ด๋ถ ํจํค์ง ์ฐ๊ฒฐ์ ๋ง๋ฒ!
// NPM ์๊ฒฉ ์ ์ฅ์๊ฐ ์๋๋ผ, ์ฐ๋ฆฌ ๋ก์ปฌ ํด๋(packages/ui)์ ์๋ ์ฝ๋๋ฅผ ๋ฐ๋ก ๊ฐ์ ธ๋ค ์๋๋ค.
// "workspace:*" ๋ ํญ์ ๋ก์ปฌ์ ์ต์ ์ฝ๋๋ฅผ ๋ฐ๋ผ๋ณด๊ฒ ๋ค๋ ๋ป์
๋๋ค. (์ฌ๋งํฌ ํจ๊ณผ)
"@youngsu/ui": "workspace:*",
"@youngsu/types": "workspace:*"
},
"devDependencies": {
// ๊ณตํต ์ค์ ํ์ผ(packages/config)๋ ๋์ผํ๊ฒ workspace ํ๋กํ ์ฝ๋ก ๋น๊ฒจ์ต๋๋ค.
"@youngsu/config": "workspace:*"
}
}๊ณตํต UI ํจํค์ง (packages/ui)
// packages/ui/package.json
{
"name": "@youngsu/ui",
"version": "1.0.0",
"exports": {
".": {
"import": "./src/index.ts",
"types": "./src/index.ts"
},
"./button": "./src/components/Button.tsx",
"./card": "./src/components/Card.tsx"
},
"scripts": {
"typecheck": "tsc --noEmit",
"lint": "eslint src/"
},
"devDependencies": {
"react": "^18.2.0",
"@types/react": "^18.2.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
}๊ณตํต TypeScript ์ค์ (packages/config)
// packages/config/tsconfig.base.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": "."
}
}// apps/community/tsconfig.json โ ๊ณตํต ์ค์ ํ์ฅ
{
"extends": "@youngsu/config/tsconfig.base.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}์ค์ ์ํฌํ๋ก์ฐ
# ์ ์ฒด ์ค์น
pnpm install
# community ์ฑ๋ง ๊ฐ๋ฐ ์๋ฒ ์คํ
pnpm --filter @youngsu/community dev
# UI ํจํค์ง ๋ณ๊ฒฝ ํ ํ์
์ฒดํฌ
pnpm --filter "@youngsu/ui..." typecheck
# community ์ ์ ํจํค์ง ์ถ๊ฐ
pnpm --filter @youngsu/community add axios
# ๋ฃจํธ devDep ์ถ๊ฐ
pnpm add -D -w eslint-plugin-tailwindcss
# -w: --workspace-root (๋ฃจํธ package.json ์ ์ถ๊ฐ)
# ๋ณ๊ฒฝ๋ ํจํค์ง์ ์ํฅ๋ฐ๋ ์ฑ๋ง ๋น๋ (CI ์ต์ ํ)
pnpm --filter "...[origin/main]" build๐ ์ด์ ๋ฆฌ
pnpm workspace ํต์ฌ ํจํด
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๊ตฌ์ฑ: pnpm-workspace.yaml ์ packages: ['apps/*', 'packages/*']
๋ด๋ถ ์ฐธ์กฐ: "workspace:*" ํ๋กํ ์ฝ๋ก ๋ก์ปฌ ํจํค์ง ์ฐ๊ฒฐ
ํน์ ํจํค์ง: --filter @scope/name ์ผ๋ก ํ๊ฒ ์ง์
์์กด ์ถ์ : --filter "...@scope/name" ๋ก ์ํฅ๋ฐ๋ ํจํค์ง ์ ํ
๋ณ๊ฒฝ ๊ฐ์ง: --filter "[origin/main]" ๋ก CI ์ต์ ํ
์ ์ฒด ์คํ: pnpm -r ๋ก recursive ์คํ
๋ฃจํธ ์ค์น: pnpm add -D -w (--workspace-root) ํ๋๊ทธ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. workspace:* ์ workspace:^ ์ ์ฐจ์ด๋ ๋ฌด์์ด๋ฉฐ, ์ธ์ ๊ฐ๊ฐ์ ์ฌ์ฉํ๋๊ฐ?
โ
์ ๋ต: workspace:* ๋ ํ์ฌ ๋ก์ปฌ ๋ฒ์ ์ ๊ทธ๋๋ก ์ฐธ์กฐํ๊ณ , ๋ฐฐํฌ ์ "*" ํํ๋ก ๋ณํ๋๋ค. workspace:^ ๋ ๋ฐฐํฌ ์ "^1.2.3" ์ฒ๋ผ SemVer ๋ฒ์ ๋ก ๋ณํ๋๋ค. ๋ด๋ถ ์ฑ(private: true)์์๋ ๋ฐฐํฌํ์ง ์์ผ๋ฏ๋ก workspace:* ๊ฐ ์ผ๋ฐ์ ์ด๊ณ , ์ธ๋ถ์ publish ํ๋ ํจํค์ง์์ ๋ค๋ฅธ workspace ํจํค์ง๋ฅผ ์ฐธ์กฐํ ๋๋ workspace:^ ๋ฅผ ์ฌ์ฉํ๋ค.
๐ก ์์ธ ํด์ค:
workspace:*โ publish ํ"@youngsu/ui": "*"(์ด๋ค ๋ฒ์ ์ด๋ OK) โ ๋ด๋ถ ์ฑ์ ์ ํฉworkspace:^โ publish ํ"@youngsu/ui": "^1.0.0"(semver ๋ฒ์) โ ์ธ๋ถ ๋ฐฐํฌ ํจํค์ง์ ์ ํฉ- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๋ด๋ถ์ฉ ์ฑ์
*, ์ธ๋ถ ๋ฐฐํฌํ๋ ํจํค์ง๋^"
Q2. pnpm --filter "...[origin/main]" build ๋ช
๋ น์ด๊ฐ CI ์์ ์ ์ ์ฉํ๊ฐ?
โ
์ ๋ต: [origin/main] ๋ main ๋ธ๋์น ๋๋น ๋ณ๊ฒฝ๋ ํจํค์ง ๋ฅผ ์ ํํ๊ณ , ์์ ... (ellipsis) ๋ ๊ทธ ํจํค์ง์ ์์กดํ๋ ๋ชจ๋ ํจํค์ง ๊น์ง ํฌํจํ๋ค. ์ฆ, @youngsu/ui ๊ฐ ๋ณ๊ฒฝ๋๋ค๋ฉด @youngsu/ui ์ ์์กดํ๋ @youngsu/community, @youngsu/admin ๋ ์๋์ผ๋ก ํฌํจ๋์ด ๋น๋๋๋ค. ๋ณ๊ฒฝ์ด ์๋ ํจํค์ง๋ ๋น๋ํ์ง ์์ CI ์๊ฐ์ ํฌ๊ฒ ์ค์ผ ์ ์๋ค.
๐ก ์์ธ ํด์ค:
- ๋ชจ๋ ธ๋ ํฌ ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด ์ ์ฒด ๋น๋์ ์์ญ ๋ถ์ด ๊ฑธ๋ฆด ์ ์์
- ๋ณ๊ฒฝ๋ ํจํค์ง + ์ํฅ๋ฐ๋ ํจํค์ง๋ง ๋น๋ โ PR ๋น CI ์๊ฐ 80% ์ด์ ๋จ์ถ ๊ฐ๋ฅ
- Turborepo ์ ์บ์ฑ๊ณผ ๋ณํํ๋ฉด ๋์ฑ ๊ฐ๋ ฅํด์ง
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๋ณ๊ฒฝ๋ ๊ฒ + ๊ทธ๊ฑธ ์ฐ๋ ๊ฒ๋ง ๋น๋ โ
...[origin/main]"
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์: ์ํคํ ์ฒ ์ค๊ณ (์์์ UX ์์๋ฆฌ)
์์ ๋์ด ๋์์ธ ๋ฆฌ๋ทฐ์์ ๋งํ๋ค.
"์์ฒ ๋,apps/community์apps/admin์ ๋ฒํผ ์คํ์ผ์ด ์๋ก ๋ฌ๋ผ์. ์ฌ์ฉ์๊ฐ ์ปค๋ฎค๋ํฐ์์ ์ด๋๋ฏผ์ผ๋ก ์ด๋ํ๋ฉด UI ๊ฐ ๋ค๋ฅด๊ฒ ๋๊ปด์ ธ์ ํผ๋์ค๋ฝ๋์."์์ฒ ์ด๊ฐ
packages/ui์ ๊ณตํต Button ์ปดํฌ๋ํธ๋ฅผ ์ถ๊ฐํ๊ณ ๋ ์ฑ์์ ์ฐ๋ ค๊ณ ํ๋ค. ์ด๋ pnpm workspace ๋ฅผ ํ์ฉํ ์ฌ๋ฐ๋ฅธ ๊ตฌ์ฑ ๋ฐฉ๋ฒ์?
โ
์ ๋ต: packages/ui/src/components/Button.tsx ๋ฅผ ๋ง๋ค๊ณ , packages/ui/package.json ์ exports ์ "./button": "./src/components/Button.tsx" ๋ฅผ ์ถ๊ฐํ๋ค. ๊ทธ ๋ค์ apps/community ์ apps/admin ์ package.json ์ "@youngsu/ui": "workspace:*" ๋ฅผ ์ถ๊ฐํ๊ณ pnpm install ํ๋ฉด ๋๋ค.
๐ก ์์ธ ํด์ค:
- ๊ตฌ์ฑ ์์:
packages/ui์ ์ปดํฌ๋ํธ ์์ฑpackages/ui/package.jsonexportsํ๋ ๋ฑ๋ก- ๊ฐ ์ฑ์์
pnpm --filter @youngsu/community add @youngsu/ui์คํ import { Button } from '@youngsu/ui/button'๋๋from '@youngsu/ui'๋ก ์ฌ์ฉ
- TypeScript ์ค์ ์ฃผ์:
apps/community/tsconfig.json์paths์@youngsu/ui๊ฒฝ๋ก ๋งคํ์ด ํ์ํ ์ ์์ - HMR ์ฃผ์: Next.js ์์
packages/uiํ์ผ ์์ ์next.config.js์transpilePackages: ['@youngsu/ui']์ถ๊ฐ ํ์ - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "workspace ํจํค์ง ์ถ๊ฐ โ
workspace:*์ ์ธ โpnpm installโ import."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ ๋๋์ด ๋ชจ๋ ธ๋ ํฌ ์ด๊ธฐ ์ค์ ์ ๋ง์ณค๋ค. ์ฒ์์ ๋๋ฌด ๋ณต์กํด ๋ณด์ฌ์ "์ด๊ฑฐ ๋ค ์ค์ ํ๋ค๊ฐ ํ๋ฃจ ๋ค ๋ ๋ฆฌ๊ฒ ๋ค"๊ณ ์๊ฐํ๋๋ฐ, ์๊ฐ๋ณด๋ค ํจ์ฌ ๊ฐ๋จํ๋ค.
pnpm-workspace.yaml ์ ํด๋ ๊ฒฝ๋ก ๋ ์ค, ๊ฐ ํจํค์ง์ workspace:* ์ฐ๊ฒฐ, pnpm install ํ ๋ฒ โ ์ด๊ฒ ์ ๋ถ์๋ค. ๋ฌผ๋ก TypeScript paths ์ค์ ์ด๋ Next.js transpilePackages ๊ฐ์ ์ธ๋ถ ์ค์ ์ ๋ ์์์ง๋ง, ํต์ฌ ๊ฐ๋
์ ์ ๋ง ๋จ์ํ๋ค.
--filter "...[origin/main]" ์ด ๊ฐ์ฅ ์ธ์์ ์ด์๋ค. CI ์์ ๋ณ๊ฒฝ๋ ํจํค์ง์ ๊ทธ์ ์์กดํ๋ ํจํค์ง๋ง ๋น๋ํ๋ค๋ ๊ฐ๋
์ด ๋๋ฌด ์ฐ์ํ๋ค. ์ ์ฒด๋ฅผ ๋งค๋ฒ ๋น๋ํ๋ ๊ฒ๊ณผ ๋น๊ตํ๋ฉด CI ์๊ฐ์ด ๋ช ๋ฐฐ๋ ์ค์ด๋ค๊ฒ ๋ค.
๐ก ์ค๋์ ๊ตํ: "๋ชจ๋ ธ๋ ํฌ๊ฐ ๋ณต์กํด ๋ณด์ด๋ ๊ฑด ๋๊ตฌ๋ฅผ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค.
workspace:*์--filter๋ ๊ฐ์ง๋ง ์ดํดํ๋ฉด pnpm ๋ชจ๋ ธ๋ ํฌ์ ์ ๋ฐ์ ๋ง์คํฐํ ๊ฒ์ด๋ค."
์ํธ ๋ฆฌ๋ ๋์ด ๋ค์์ Catalogs ๋ ์จ๋ณด๋ผ๊ณ ํ์ จ๋ค. ๊ฐ์ ํจํค์ง ๋ฒ์ ์ ์ฌ๋ฌ ์ฑ์์ ์ธ ๋ ๋ฒ์ ์ ํ ๊ณณ์์ ๊ด๋ฆฌํ๋ ๊ธฐ๋ฅ์ด๋ผ๋๋ฐ, ๋ค์ ์ฑํฐ์์ ๋ณด์. ์ค๋์ ์ผ์์ผ๋ก ๋ผ๋ฉด ๋์ฌ ๋จน๊ณ ์์ผ๊ฒ ๋ค. ๋ชจ๋ ธ๋ ํฌ ์ค์ ์ฑ๊ณต ๊ธฐ๋ !