๐ Catalogs & ๊ณ ๊ธ ๊ธฐ๋ฅ โ pnpm ๋ง์ด ์ค ์ ์๋ ๊ฒ๋ค
๐ ๊ฐ์
catalog: ํ๋กํ ์ฝ๋ก ๋ฒ์ ์ ํ ๊ณณ์์ ๊ด๋ฆฌํ๊ณ , pnpmfile.mjs ๋ก ์์กด์ฑ์ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ์ ์ดํ๋ pnpm ๊ณ ๊ธ ๊ธฐ๋ฅ ์์ ์ ๋ณต
05. Catalogs & ๊ณ ๊ธ ๊ธฐ๋ฅ
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐ Catalogs
- ๐ท๏ธ Named Catalogs
- ๐ฃ pnpmfile.mjs
- ๐งน pnpm dedupe
- ๐ only-allow
- ์ด์ ๋ฆฌ
- ๋ง๋ฌด๋ฆฌ ํด์ฆ
- [์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ](#-์์ฒ ์ด์-ํด๊ทผ ์ผ๊ธฐ)
- ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: ์ฝ 25๋ถ(์ ์ฒด) / ํต์ฌ ํํธ๋ง: 12๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
[Catalogs ๊ธฐ๋ณธ] โ [Named Catalogs] โ [pnpmfile.mjs] โ [dedupe] โ [only-allow]
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
catalog:ํ๋กํ ์ฝ๋ก ๋ชจ๋ ธ๋ ํฌ ์์กด์ฑ ๋ฒ์ ์ ์ค์ ๊ด๋ฆฌํ ์ ์๋ค - Named Catalogs ๋ก React 17/18 ๋ฑ ๋ฒ์ ๋ถ๊ธฐ๋ฅผ ๋ช ํํ๊ฒ ๊ด๋ฆฌํ ์ ์๋ค
-
pnpmfile.mjs๋ก ํน์ ํจํค์ง์ ์์กด์ฑ์ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ์์ ํ ์ ์๋ค -
only-allow๋ก ํ ์ ์ฒด๊ฐ pnpm ์ ์ฌ์ฉํ๋๋ก ๊ฐ์ ํ ์ ์๋ค
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'

- ๐ฃ ์์ฒ ( ์ ์
): "์ํธ ๋, ๋ชจ๋
ธ๋ ํฌ์์
react๋ฒ์ ์ดapps/community์์ 18.2.0,apps/admin์์ 18.2.1 ๋ก ๋ฌ๋ผ์. ๋๊ตฐ๊ฐpnpm update๋ฅผ ํ์ชฝ์์๋ง ํ๋๋ด์. ์ด๋ฐ ๊ฒ ์์ด๋ฉด ๋์ค์ ๋ฒ์ ์ถฉ๋์ด ๋ ๊ฒ ๊ฐ์์ ๊ฑฑ์ ์ธ๋ฐ, ๋ชจ๋ ์ฑ์์ ๊ฐ์ ๋ฒ์ ์ ๊ฐ์ ํ๋ ๋ฐฉ๋ฒ์ด ์๋์?" - ๐ฆ ์ํธ ( ๋ฆฌ๋ ): "Catalogs ์ฐ๋ฉด ๋ผ์.
pnpm-workspace.yaml์ ๋ฒ์ ์ ํ ๋ฒ๋ง ์ ์ธํ๊ณ , ๊ฐpackage.json์์'catalog:'๋ฅผ ์ฐ๋ฉด ์๋์ผ๋ก ์ค์ ๋ฒ์ ์ ์ฐธ์กฐํด์. ๋ฒ์ ์ฌ๋ฆด ๋๋pnpm-workspace.yamlํ ์ค๋ง ๋ฐ๊พธ๋ฉด ์ ์ฒด๊ฐ ๋ฐ๋๊ณpnpm-lock.yaml์์ ์ถฉ๋๋ ์ค์ด๋ค์ด์. Catalogs ๋ ๋ชจ๋ ธ๋ ํฌ๊ฐ ์๋ ํ์ด๋ผ๋ฉด ๋ฌด์กฐ๊ฑด ์จ์ผ ํ๋ ๊ธฐ๋ฅ์ด์์."
๐ค ์ ์์์ผ ํ๋๊ฐ
๋ฒ์ ๋๋ฆฌํํธ ๋ฌธ์
๋ชจ๋
ธ๋ ํฌ์์ 10๊ฐ์ ์ฑ์ด ๋ชจ๋ react ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ๊ฐ package.json ์ ๋ฒ์ ๋ฒ์๊ฐ 10๋ฒ ๋ฑ์ฅํ๋ค.
// apps/community/package.json
{ "react": "^18.2.0" }
// apps/admin/package.json
{ "react": "^18.2.1" } โ ๋๊ตฐ๊ฐ ์
๋ฐ์ดํธํจ
// apps/landing/package.json
{ "react": "^18.1.0" } โ ์ค๋๋ ๋ฒ์ ์
๋ฐ์ดํธํ ๋ 10๊ฐ ํ์ผ์ ๋ชจ๋ ์์ ํด์ผ ํ๋ค. ํ๋๋ผ๋ ๋น ์ง๋ฉด ๋ฒ์ ๋๋ฆฌํํธ(drift) ๊ฐ ๋ฐ์ํ๋ค. PR ๋ง๋ค package.json ์์ ์ด ๋ฐ์ํด merge conflict ๋ ์ฆ์์ง๋ค.
Catalogs ๊ฐ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
๐ Catalogs โ ๋ฒ์ ๊ด๋ฆฌ์ ๋จ์ผ ์ง์ค ๊ณต๊ธ์
๊ธฐ๋ณธ Catalog ์ค์
# pnpm-workspace.yaml
# ๐ก ์ด ๊ณต๊ฐ์ด ๋ชจ๋
ธ๋ ํฌ ์ ์ฒด์ "๋จ์ผ ์ง์ค ๊ณต๊ธ์(Single Source of Truth)"์ด ๋ฉ๋๋ค.
packages:
- 'apps/*'
- 'packages/*'
# ๐ฆ catalog: (๋จ์ํ) โ ๋ชจ๋
ธ๋ ํฌ ์ ์ฒด์์ ๊ณตํต์ผ๋ก ์ธ "๊ธฐ๋ณธ ์นดํ๋ก๊ทธ"๋ฅผ ์ ์ํฉ๋๋ค.
catalog:
# ์๋ ์ ํ ํจํค์ง๋ค์ ์ด์ ๋ถํฐ ์ด ๋ฒ์ ์ผ๋ก ์ ์ฒด ํต์ผ๋ฉ๋๋ค!
react: ^18.2.0
react-dom: ^18.2.0
next: ^14.2.0
typescript: ^5.3.3
"@types/react": ^18.2.0
"@types/node": ^20.11.0
tailwindcss: ^3.4.1
axios: ^1.6.7
zod: ^3.22.4
"@tanstack/react-query": ^5.17.15
prisma: ^5.9.1
"@prisma/client": ^5.9.1package.json ์์ catalog: ์ฐธ์กฐ
// apps/community/package.json
{
"name": "@youngsu/community",
"dependencies": {
// ๐ฆ [ํต์ฌ] ๋ฒ์ ์ ^18.2.0 ์ด๋ผ๊ณ ์ง์ ์ฐ์ง ์๊ณ "catalog:" ๋ผ๊ณ ๋ง ์ ์ต๋๋ค.
// ์ด๋ ๊ฒ ํ๋ฉด pnpm ์ด ์์์ pnpm-workspace.yaml ์ ์ ์ธ๋ ๋ฒ์ ์ ๋น๊ฒจ์ต๋๋ค.
"next": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"axios": "catalog:",
"@tanstack/react-query": "catalog:",
// ๐ก ์ฐ๋ฆฌ ๋ชจ๋
ธ๋ ํฌ ๋ด๋ถ์ ๋ค๋ฅธ ํจํค์ง๋ฅผ ์ธ ๋๋ workspace:* ๋ฅผ ์๋๋ค.
// (์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ = catalog: / ๋ด๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ = workspace: ๋ก ์๋ฒฝํ๊ฒ ๋ถ๋ฆฌ!)
"@youngsu/ui": "workspace:*"
},
"devDependencies": {
"typescript": "catalog:",
"@types/react": "catalog:",
"@types/node": "catalog:",
"tailwindcss": "catalog:"
}
}// apps/admin/package.json
{
"name": "@youngsu/admin",
"dependencies": {
"next": "catalog:", // community ์ ์์ ํ ๋์ผํ ๋ฒ์ ๋ณด์ฅ
"react": "catalog:",
"react-dom": "catalog:"
}
}๋ฒ์ ์ ๊ทธ๋ ์ด๋ โ ํ ์ค๋ง ์์
# pnpm-workspace.yaml ์์ ํ ์ค๋ง ์์
catalog:
react: ^18.3.0 # โ ์ด ํ ์ค์ด ๋ชจ๋ ์ฑ์ ์๋ ์ ์ฉpnpm install
# โ lock ํ์ผ ์
๋ฐ์ดํธ, ๋ชจ๋ ์ฑ์์ react@18.3.x ์ฌ์ฉcatalog: ํ๋กํ ์ฝ ๋ณํ
{
"dependencies": {
"react": "catalog:", // default catalog ์ react
"react": "catalog:default" // ๋ช
์์ ์ผ๋ก default ์ง์ (๋์ผ)
}
}publish ์ ์๋ ๋ณํ
// ๋ฐฐํฌ ์
{ "react": "catalog:" }
// ๋ฐฐํฌ ํ (pnpm publish ๋๋ pnpm pack)
{ "react": "^18.2.0" } // ์ค์ ๋ฒ์ ์ผ๋ก ๋ณํ๋จ๐ท๏ธ Named Catalogs โ ๋ฒ์ ๋ถ๊ธฐ ๊ด๋ฆฌ
์ฌ๋ฌ ๋ฒ์ ์ ๋์์ ๊ด๋ฆฌํด์ผ ํ ๋ Named Catalogs ๋ฅผ ์ฌ์ฉํ๋ค.
๋ค์ค React ๋ฒ์ ๊ด๋ฆฌ
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
catalog:
# ๊ธฐ๋ณธ (์ต์ )
react: ^18.2.0
react-dom: ^18.2.0
catalogs:
# ๋ ๊ฑฐ์ ์ฑ์ฉ (React 17)
react17:
react: ^17.0.2
react-dom: ^17.0.2
"@types/react": ^17.0.0
# ์ต์ ์คํ์ฉ (React 19)
react19:
react: ^19.0.0
react-dom: ^19.0.0
"@types/react": ^19.0.0
# ๊ณตํต ๋๊ตฌ ๋ฒ์ (๋ฒ์ ๋ถ๋ฆฌ ๊ด๋ฆฌ)
dev:
typescript: ^5.3.3
eslint: ^8.57.0
prettier: ^3.2.5// apps/legacy/package.json (React 17 ๋ ๊ฑฐ์ ์ฑ)
{
"dependencies": {
"react": "catalog:react17",
"react-dom": "catalog:react17"
}
}// apps/community/package.json (๊ธฐ๋ณธ React 18)
{
"dependencies": {
"react": "catalog:", // default catalog
"react-dom": "catalog:"
},
"devDependencies": {
"typescript": "catalog:dev" // dev catalog
}
}๊ธฐ์กด ๋ชจ๋ ธ๋ ํฌ์์ Catalogs ๋ง์ด๊ทธ๋ ์ด์
# codemod ๋ฅผ ์ฌ์ฉํด์ ์๋ ๋ง์ด๊ทธ๋ ์ด์
pnpm dlx codemod pnpm/catalog๋ง์ด๊ทธ๋ ์ด์
์ :
apps/community/package.json: { "react": "^18.2.0" }
apps/admin/package.json: { "react": "^18.2.0" }
๋ง์ด๊ทธ๋ ์ด์
ํ:
pnpm-workspace.yaml: catalog: { react: ^18.2.0 }
apps/community/package.json: { "react": "catalog:" }
apps/admin/package.json: { "react": "catalog:" }
๐ฃ pnpmfile.mjs โ ์์กด์ฑ ํ๋ก๊ทธ๋๋ฐ ์ ์ด
pnpmfile.mjs ๋ pnpm ์ ์ค์น ํ
ํ์ผ์ด๋ค. ํจํค์ง ์ค์น ์ค์ ์์กด์ฑ์ ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ์์ ํ ์ ์๋ค.
๊ธฐ๋ณธ ๊ตฌ์กฐ
// .pnpmfile.mjs (๋ชจ๋
ธ๋ ํฌ ๋ฃจํธ)
// ๐ก ์ด ํ์ผ์ pnpm ์ด `pnpm install` ์ ์คํํ ๋ ์ค๊ฐ์ ๊ฐ์
(๊ฐ๋ก์ฑ๊ธฐ)ํ ์ ์๊ฒ ํด์ฃผ๋ ๋ง๋ฒ์ ํ
(Hook) ํ์ผ์
๋๋ค.
export const hooks = {
// ๐ฆ [1๋จ๊ณ] readPackage ํ
: pnpm ์ด ํน์ ํจํค์ง์ package.json ์ ๋ง ์ฝ์ด๋ค์ธ ์๊ฐ์ ํธ์ถ๋ฉ๋๋ค.
// ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๊ฐ ๊ฐ์ ๋ก package.json ์ ๋ด์ฉ์ ์กฐ์ํด์ ๋ฐํํ ์ ์์ต๋๋ค!
readPackage(pkg, context) {
// pkg.dependencies ๋ฑ ์์ ๋ก์ง...
return pkg; // ์์ ๋ ํจํค์ง ์ ๋ณด๋ฅผ pnpm ์๊ฒ ๋ค์ ๋๋ ค์ค
},
// ๐ฆ [2๋จ๊ณ] afterAllResolved ํ
: ๋ชจ๋ ์์กด์ฑ ๊ณ์ฐ์ด ๋๋ ์งํ lock ํ์ผ์ ์ต์ข
์์ฑํ๊ธฐ ์ ์ ํธ์ถ.
afterAllResolved(lockfile, context) {
return lockfile;
}
};์ค์ ์ผ์ด์ค 1: ๊ตฌํ ํจํค์ง์ ์๋ชป๋ ํผ์ด ์์กด์ฑ ๋ฌด๋ ฅํ
// .pnpmfile.mjs
export const hooks = {
readPackage(pkg, context) {
// ๐ฃ ์์ฒ ์ด ๋ฐ๊ฒฌํ ๋ฌธ์ :
// 'some-old-package' ๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ "๋๋ ๋ฌด์กฐ๊ฑด react 16 ๋ฒ์ ์์๋ง ๋์!"(peerDependencies) ๋ผ๊ณ ์ฐ๊ธฐ๊ณ ์์ต๋๋ค.
// ํ์ง๋ง ์ฐ๋ฆฌ๋ react 18์ ์ฐ๊ณ ์์ด์, ๋ชจ๋ํฐ์ ๋งค๋ฒ ๋ณด๊ธฐ ์ซ์ ๋
ธ๋์ ๊ฒฝ๊ณ (Warning)๊ฐ ๋น๋๋ค.
if (pkg.name === 'some-old-package') {
// ๐ฆ ์ํธ ๋ฆฌ๋์ ํด๊ฒฐ์ฑ
:
// pnpm ์ด ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ๊ธฐ ์ง์ ์, ์ฐ๋ฆฌ๊ฐ ๋ชฐ๋ package.json ์์ ์๊ตฌ ์กฐ๊ฑด์ ์์ ํด๋ฒ๋ฆฝ๋๋ค!
pkg.peerDependencies = {
...pkg.peerDependencies,
react: '>=16.0.0' // "16 ์ด์์ด๋ฉด ๋ค ํธํ๋๋ค๊ณ ์ณ๋ผ~" ํ๊ณ ์กฐ๊ฑด์ ์ํ์์ผฐ์
};
// ํฐ๋ฏธ๋์ ๋ก๊ทธ๋ ๋จ๊ฒจ์ ํ๋ ๋๋ฒ๊น
ํ๊ธฐ ์ข๊ฒ ๋ง๋ญ๋๋ค.
context.log('some-old-package ์ react peerDep ๋ฒ์๋ฅผ ๊ฐ์ ๋ก ์ํํ์ต๋๋ค.');
}
return pkg;
}
};์ค์ ์ผ์ด์ค 2: ์๋ชป๋ ์์กด์ฑ ๋ฒ์ ๊ต์ฒด
// .pnpmfile.mjs
export const hooks = {
readPackage(pkg, context) {
// ๐ฆ ์ํธ ๋ฆฌ๋์ ์ค์ ์ฌ๋ก:
// ํน์ ํจํค์ง๊ฐ ์ทจ์ฝํ ๋ฒ์ ์ axios ๋ฅผ deps ๋ก ๊ณ ์ ํ๊ณ ์์
// overrides ๋ก ํด๊ฒฐํ ์๋ ์์ง๋ง, ์กฐ๊ฑด๋ถ ์ฒ๋ฆฌ๊ฐ ํ์ํ ๋ pnpmfile ์ด ์ ์ฉ
if (pkg.dependencies && pkg.dependencies['axios']) {
const currentVersion = pkg.dependencies['axios'];
// 0.x.x ๋ฒ์ ์ ๋ชจ๋ ์ต์ ์์ ๋ฒ์ ์ผ๋ก ๊ต์ฒด
if (currentVersion.startsWith('0.')) {
pkg.dependencies['axios'] = '^1.6.7';
context.log(`${pkg.name}: axios ${currentVersion} โ ^1.6.7 ๋ก ๊ฐ์ ๋ณ๊ฒฝ`);
}
}
return pkg;
}
};์ค์ ์ผ์ด์ค 3: ๋ถํ์ํ devDependencies ์ ๊ฑฐ
// .pnpmfile.mjs
export const hooks = {
readPackage(pkg, context) {
// ํน์ ํจํค์ง์ ๋ถํ์ํ ๋น๋ ๋๊ตฌ ์ ๊ฑฐ (์ค์น ์๋ ํฅ์)
if (pkg.name === 'some-heavy-package') {
delete pkg.devDependencies['webpack'];
delete pkg.devDependencies['babel-loader'];
}
return pkg;
}
};pnpmfile vs overrides vs patch
| ๋ฐฉ๋ฒ | ์ฉ๋ | ํน์ง |
|---|---|---|
overrides | ๋ฒ์ ๋ฒ์ ๊ฐ์ | ์ ์ธ์ , ๋จ์ |
pnpmfile.mjs | ํ๋ก๊ทธ๋๋ฐ์ ์์ | ์กฐ๊ฑด๋ถ ๋ก์ง ๊ฐ๋ฅ |
pnpm patch | ์ฝ๋ ์ง์ ์์ | .patch ํ์ผ๋ก ์ถ์ |
# overrides ๋ก ํด๊ฒฐ ๊ฐ๋ฅํ ์ผ์ด์ค (๋ ๋จ์)
# pnpm-workspace.yaml
overrides:
axios: ^1.6.7
semver: ^7.5.4๐งน pnpm dedupe โ ์ค๋ณต ์์กด์ฑ ์ ๋ฆฌ
pnpm dedupe ๋ pnpm-lock.yaml ์์ ์ค๋ณต ์ค์น๋ ํจํค์ง๋ฅผ ํ๋๋ก ํฉ์น๋ค.
# ์ค๋ณต ํ์ธ
pnpm dedupe --check
# ์ค์ ์ค๋ณต ์ ๊ฑฐ (lock ํ์ผ ์
๋ฐ์ดํธ)
pnpm dedupe์ ์ค๋ณต์ด ๋ฐ์ํ๋๊ฐ:
์ด๊ธฐ ์ค์น ์: lodash@4.17.20 (์ต์ )
3๊ฐ์ ํ lodash@4.17.21 ๋ฆด๋ฆฌ์ฆ
์ ํจํค์ง ์ค์น ์ lodash@4.17.21 ๋ ํจ๊ป ์ค์น
๊ฒฐ๊ณผ:
node_modules/.pnpm/lodash@4.17.20 (๊ธฐ์กด ํจํค์ง ์์กด์ฑ)
node_modules/.pnpm/lodash@4.17.21 (์ ํจํค์ง ์์กด์ฑ)
โ pnpm dedupe ๋ก 4.17.21 ๋ก ํต์ผ
๐ only-allow โ ํจํค์ง ๋งค๋์ ๊ฐ์
ํ์์ npm, yarn, pnpm ํผ์ฉ์ ๋ฐฉ์งํ๋ค.
// package.json
{
"scripts": {
"preinstall": "npx only-allow pnpm"
}
}# npm install ์๋ํ๋ฉด
npm install
# ์๋ฌ:
# Use "pnpm install" for installing dependencies in this project.
# If you don't have pnpm, install it via "npm i -g pnpm".
# For more details, go to https://pnpm.js.org/๋ ๋์ ๋ฐฉ๋ฒ: packageManager ํ๋ + Corepack
// package.json
{
"packageManager": "pnpm@9.15.0"
}# Corepack ํ์ฑํ ์ ์๋์ผ๋ก ์ง์ ๋ ๋ฒ์ ๋ง ํ์ฉ
corepack enable
# ์ดํ yarn install ์๋ํ๋ฉด ์๋์ผ๋ก ์๋ฌ๐ ์ด์ ๋ฆฌ
pnpm ๊ณ ๊ธ ๊ธฐ๋ฅ ์์ฝ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Catalogs: pnpm-workspace.yaml ์ ๋ฒ์ ์ ์ธ โ package.json ์์ catalog: ์ฐธ์กฐ
Named Cat.: catalogs: { react17: {...}, react19: {...} } โ catalog:react17 ์ฐธ์กฐ
pnpmfile: hooks.readPackage ๋ก ์ค์น ์ค ์์กด์ฑ ํ๋ก๊ทธ๋๋ฐ ์์
dedupe: pnpm dedupe ๋ก ์ค๋ณต ํจํค์ง ๋ฒ์ ํต์ผ
only-allow: preinstall ์คํฌ๋ฆฝํธ ๋๋ packageManager ํ๋๋ก pnpm ๊ฐ์
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| ๋ฌธ์ | ํด๊ฒฐ์ฑ |
|---|---|
| ๋ชจ๋ ธ๋ ํฌ ๋ฒ์ ๋๋ฆฌํํธ | Catalogs (catalog:) |
| ํน์ ๊ฐ์ ์์กด์ฑ ๋ฒ์ ๊ฐ์ | overrides |
| ๋ณต์กํ ์กฐ๊ฑด๋ถ ์์กด์ฑ ์์ | pnpmfile.mjs |
| ํจํค์ง ์ฝ๋ ์ง์ ์์ | pnpm patch |
| ์ค๋ณต ํจํค์ง ๋ฒ์ ์ ๋ฆฌ | pnpm dedupe |
| ํจํค์ง ๋งค๋์ ํต์ผ | only-allow ๋๋ packageManager |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. Catalogs ์์ catalog: (๊ธฐ๋ณธ๊ฐ) ์ catalog:dev (Named) ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ์ด์ ๋?
โ
์ ๋ต: Default catalog ๋ ๋ฐํ์ ์์กด์ฑ(react, next ๋ฑ) ์ ๋ฒ์ ์ ๊ด๋ฆฌํ๊ณ , Named catalog (์: catalog:dev) ๋ ๊ฐ๋ฐ ๋๊ตฌ(typescript, eslint ๋ฑ) ์ ๋ฒ์ ์ ๋ถ๋ฆฌ ๊ด๋ฆฌํ๋ค. ์ด๋ ๊ฒ ๋ถ๋ฆฌํ๋ฉด ๋ฐํ์ ์์กด์ฑ๊ณผ ๊ฐ๋ฐ ๋๊ตฌ์ ๋ฒ์ ์
๊ทธ๋ ์ด๋ ์ฃผ๊ธฐ๊ฐ ๋ค๋ฅผ ๋ ๋
๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ณ , ์๋๊ฐ ๋ช
ํํด์ง๋ค.
๐ก ์์ธ ํด์ค:
- Default catalog: ๋ชจ๋ ์ฑ์ด ๊ณต์ ํ๋ ํต์ฌ ์์กด์ฑ ๋ฒ์ (react, next, @tanstack/react-query ๋ฑ)
- Named catalog: ํน์ ๋ชฉ์ ๊ทธ๋ฃน (dev ๋๊ตฌ, react ๋ฒ์ ๋ณ ๋ถ๊ธฐ, ๋ ๊ฑฐ์ ์ฑ์ฉ ๋ฑ)
- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "Default = ๊ณตํต, Named = ๋ชฉ์ ๋ณ ๋ถ๋ฆฌ"
Q2. pnpmfile.mjs ์ hooks.readPackage ๋ ์ธ์ ์คํ๋๋ฉฐ, overrides ์ ์ด๋ค ์ํฉ์์ ๊ฐ๊ฐ ์ ํํด์ผ ํ๋๊ฐ?
โ
์ ๋ต: hooks.readPackage ๋ ๊ฐ ํจํค์ง์ manifest(package.json) ๋ฅผ ํ์ฑํ ์งํ pnpm ์ด ์์กด์ฑ์ ํด๊ฒฐํ๊ธฐ ์ ์ ์คํ๋๋ค. overrides ๋ ๋จ์ํ ํน์ ํจํค์ง์ ๋ฒ์ ๋ฒ์๋ฅผ ๊ฐ์ ํ ๋ ์ฌ์ฉํ๊ณ , pnpmfile.mjs ๋ ์กฐ๊ฑด๋ถ ๋ก์ง(ํจํค์ง๋ช
์ฒดํฌ, ๋ฒ์ ํจํด ๋งค์นญ) ์ด ํ์ํ๊ฑฐ๋ peerDependencies ๋ฅผ ์์ ํด์ผ ํ ๋ ์ฌ์ฉํ๋ค.
๐ก ์์ธ ํด์ค:
overrides: { axios: "^1.6.7" }โ ๋ชจ๋ axios ๋ฅผ ๊ฐ์ ๋ณ๊ฒฝ (๋จ์, ์ ์ธ์ )pnpmfile readPackageโ "ํน์ ํจํค์ง์ deps ์ค 0.x.x ๋ฒ์ ๋ง" ๊ฐ์ ์กฐ๊ฑด๋ถ ์ฒ๋ฆฌ ๊ฐ๋ฅ- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๋จ์ ๋ฒ์ ๊ต์ฒด โ overrides, ๋ณต์กํ ๋ก์ง โ pnpmfile"
Q3. ์์ฒ ์ด์ ํ ์คํธ ํ์: ์๋์ด ๋ฉด์ ์ง๋ฌธ (์์ฒ ์ ๋ฉด์ ๋์ )
์์ฒ ์ด๊ฐ ์ด์ง ๋ฉด์ ์์ ๋ฉด์ ๊ด์ด ๋ฌผ์๋ค.
"pnpm Catalogs ๋ฅผ ์ฌ์ฉํ๋ฉดpnpm-lock.yaml์ merge conflict ๊ฐ ์ค์ด๋๋ ์ด์ ๋ฅผ ์ค๋ช ํด์ฃผ์ธ์."
โ
์ ๋ต: Catalogs ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ ์ฑ์ package.json ์์ ๋ฒ์ ๋ฒ์ ๋์ catalog: ์ฐธ์กฐ ๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ค. ๋ฒ์ ์ ์ฌ๋ฆด ๋ package.json ์ ์์ ํ์ง ์์ผ๋ฏ๋ก ํด๋น ํ์ผ์์ merge conflict ๊ฐ ๋ฐ์ํ์ง ์๋๋ค. ๋ฒ์ ๋ณ๊ฒฝ์ pnpm-workspace.yaml ํ ๊ณณ์์๋ง ์ผ์ด๋๋ฏ๋ก conflict ์ง์ ์ด ๋ถ์ฐ๋์ง ์๋๋ค.
๐ก ์์ธ ํด์ค:
- Before(Catalogs ์์): react ๋ฒ์ ์ โ apps/community/package.json, apps/admin/package.json ... 10๊ฐ ํ์ผ ์์ โ PR ์์ 10๊ฐ ํ์ผ conflict ๊ฐ๋ฅ
- After(Catalogs ์ฌ์ฉ): react ๋ฒ์ ์ โ pnpm-workspace.yaml 1๊ฐ ํ์ผ๋ง ์์ โ conflict ์ง์ 1๊ณณ์ผ๋ก ์ง์ค
- ์ถ๊ฐ๋ก:
pnpm-lock.yaml์ ๋ณ๊ฒฝ ํญ๋ ์ค์ด๋ค์ด lock ํ์ผ conflict ๋ ์ค์ด๋ฆ - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๋ฒ์ ์ ๋จ์ผ ์ง์ค ๊ณต๊ธ์ = ๋ฒ์ ์์ ํ์ผ์ด 1๊ฐ = conflict ์ง์ 1๊ฐ"
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ Catalogs ๋ฅผ ํ๋ก์ ํธ์ ์ ์ฉํ๋ค. codemod ๋ก ์๋ ๋ง์ด๊ทธ๋ ์ด์
ํด๋ดค๋๋ฐ ์ ๋ง ๋ง๋ฒ๊ฐ์ด package.json ๋ค์ ๋ฒ์ ์ด catalog: ๋ก ๋ฐ๋์๋ค.
pnpmfile.mjs ๋ ์์งํ ์ฒ์์ "์ด๊ฑธ ์ธ์ ์จ?" ์ถ์๋๋ฐ, ์ํธ ๋ฆฌ๋ ๋์ด ์์ ์ ํฌํ
์์กด์ฑ ๋๋ฌธ์ ๊ณ ์ํ๋ ์ผ์ด์ค๋ฅผ ๋ณด์ฌ์ฃผ์๋ฉด์ ์ค๋ช
ํด์ฃผ์๋๊น ์ดํด๊ฐ ๋๋ค. ๊ตฌํ ํจํค์ง๊ฐ ์๋ชป๋ ํผ์ด ๋ฒ์ ์ ์๊ตฌํ ๋, overrides ๋ก ์ ๋๋ ๋ณต์กํ ์ผ์ด์ค์์ pnpmfile.mjs ๊ฐ ์ง์ง ์ ์ฉํ๊ฒ ๊ตฌ๋ ์ถ์๋ค.
๊ทธ๋์ ๋ Catalogs ๊ฐ ์ merge conflict ๋ฅผ ์ค์ด๋์ง ๋ฉด์ ์ง๋ฌธ์ผ๋ก ๋์ฌ ์ ์๋ค๋ ์๊ฐ์ ๋ชป ํ๋๋ฐ... ์ด๊ฒ ์ง์ง ์๋์ด๊ฐ ์์์ผ ํ๋ ์์ค์ด๊ตฌ๋. ๋จ์ํ "ํธํ๋ค"๊ฐ ์๋๋ผ "์ ํธํ๊ฐ", "์ด๋ค ๋ฉ์ปค๋์ฆ์ผ๋ก conflict ๋ฅผ ์ค์ด๋๊ฐ" ๊น์ง ์ค๋ช ํ ์ ์์ด์ผ ํ๋ค.
๐ก ์ค๋์ ๊ตํ: "๋ฒ์ ์ ๋จ์ผ ์ง์ค ๊ณต๊ธ์(Single Source of Truth) ์ ๋ง๋๋ ๊ฒ โ Catalogs ๊ฐ ๋ฐ๋ก ๊ทธ๊ฒ์ด๋ค. ์์ ํ ๊ณณ์ด ํ๋๋ฉด ์ค์๋, ์ถฉ๋๋ ์ค์ด๋ ๋ค."
์ค๋ ์ ๋ ์ ์ํธ ๋ฆฌ๋ ๋์ด ๊ณ ์ํ๋ค๊ณ ์์ก์ ์ฌ์ฃผ์ จ๋ค. ์ญ์ ๋ฐฐ์ธ ๊ฒ ๋ง์ ํ์ด ๋ง์๋ ๊ฒ๋ ๋ง๋ค. ๋ด์ผ์ Next.js + Docker + CI/CD ํธ์ด๋ค. ๋ง์ง๋ง ๋ ์ฑํฐ ํ์ดํ !