일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- PongWorld
- babel.config.js
- react native font
- react native CLI
- 백준
- GIT
- img upload
- 리엑트 네이티브 아이콘
- Access Key 생성
- aws bucket 정책
- Project
- AWS
- error
- Next.js
- 리액트
- react native 세팅
- 리액트 네이티브 에러
- 리액트 네이티브
- 문자열 대소문자 구별
- fire base
- react native 개발
- firebase 라이브러리
- AWS Access Key
- s3 upload
- react native
- 에러
- js
- React
- react native picker
- 문자열 대소문자
- Today
- Total
밝을희 클태
Yarn Berry로 Monorepo 구성하기 본문
기존의 어드민 페이지가 한 도메인으로 통합되어 uri로 구분을 하고 있었는데 아예 도메인을 분리하려고 한다. 그러면서 Monorepo를 함께 적용해보려고 한다.
Monorepo란?
한 레포지토리에서 독립된 여러 프로젝트를 관리할 수 있음
Multirepo를 사용한다면 의존성이 낮아지는 장점은 있지만, 코드를 재사용할 수 없고, 의존하는 코드의 변경이 일어나면 일일이 해당 코드를 사용하는 프로젝트로 가서 적용을 시켜줘야 한다. Monorepo를 사용하면 패키지를 여러 프로젝트에서 공유해 한 번의 설치로 공유해서 사용할 수 있다.
패키지 매니저 yarn vs pnpm
yarn을 사용하는 기업
Yarn
- 다양한 기업에서 yarn workspaces을 사용해 모노레포를 구성하고 있다. yarn berry는 PnP(Plug'n'Play)로 node_module 없이 의존성을 관리할 수 있고, Zero-Install을 통해 Yarn Berry에서 의존성을 node_modules에 설치하는 대신, .yarn/cache에 압축된 형태로 저장하고 Git에 커밋해 의존성을 다시 설치할 필요 없이 즉시 사용할 수 있어 설치 시간을 없앨 수 있다.
pnpm
- pnpm은 node_module을 사용하되 한 번만 설치를 하고 하드 링크로 연결하는 방식이다.
Yarn 결정!
나는 최종적으로 yarn을 사용하기로 했다. 유령 의존성 등 자잘한 버그가 있다는 걸 봤지만, 이미 많은 기업에서 사용을 하고 있고, v4로 올라가면서 유령 의존성 문제도 해결 됐다고 한다.
모노레포 툴
모노레포 툴은 turborepo, nx, lerna 등이 있는데 turborepo를 사용하려고 했으나(Next.js, TS, vercel 등 프로젝트 스택이 너무 잘 맞음) yarn의 pnp를 지원하지 않는다는 걸 알게 됐고, 직접 처음부터 구성하고 싶어 따로 사용하지 않았다.
Lerna는 가장 많이 사용되고 있는 방식이었지만, 2022년 5월에 Nx를 관리하고 있는 Nrwl에 소유권을 넘기면서 더 이상 유지보수하지 않는다.
터보레포를 사용해 3가지 패키지 매니저의 배포 속도를 측정해 봤다. vercel의 서버 환경에 따라 차이가 있을 순 있지만 yarn berry가 가장 빨리 배포가 됐다.



구성하기
yarn 설치하기 (이미 설치되어 있으면 X)
$ npm install -g yarn
$ mkdir monorepo && cd monorepo
yarn berry로 버전을 바꿔준다. 2.x 이상이면 berry
$ yarn set version berry && yarn -v
$ yarn init -p


나는 yarn pnp를 사용할 거기 때문에. yarnrc.yml 파일에 nodeLinker: pnp를 추가
echo nodeLinker: pnp >> .yarnrc.yml

package.json에 workspaces를 추가한다. workspaces는 여러 패키지를 모노레포 형태로 관리할 수 있게 도와주는 설정으로, 하위 패키지들의 의존성을 통합적으로 관리하고 로컬 참조를 쉽게 설정할 수 있다.
// package.json { "name": "keynut-monorepo", "packageManager": "yarn@4.5.3", "private": true, "workspaces": [ "apps/*", "packages/*" ] }
실제 작업 공간을 만들어준다.
$ mkdir -p apps/web apps/admin packages/ui packages/utils

방금 만든 web, admin 디렉터리로 가서 프로젝트를 만들어준다.
$ yarn create next-app .
Error
그러고 프로젝트의 소스를 보면 이렇게 에러가 난다.
yarn pnp에서는 node-modules 디렉터리가 생성이 되지 않는다. vscode는 기본적으로 의존성 탐색을 node-modules 디렉터리에서 하기 때문에 pnp 환경에서는 의존성을 찾을 수 없어 에러가 난다.
아래는 typescript를 찾지 못해 발생하는 에러다.

터미널에서 루트로 이동해 아래 명령어 실행
$ yarn dlx @yarnpkg/sdks vscode
vscode
mac: cmd + shift + p
win: ctrl + shift + p
-> TypeScript: Select TypecSript Version -> Use Workspace Version 선택
zero-install
zero-install은 yarn berry에서 지원하는 기능으로 압축된 의존성을 git에 포함하여 clone을 받고 yarn install 없이 바로 실행할 수 있게 해주는 기능이다.
zero install 사용 시
// .gitignore .yarn/* !.yarn/cache !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions
zero install 미사용 시
// .gitignore .pnp.* .yarn/* !.yarn/cache !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions
실행하기
scripts에 dev 명령어를 추가해 주면 병렬적으로 모든 app을 실행시킬 수 있다.
- all: 모든 workspace
- parallel: 병렬 작업
- interlaced: 로그 확인
{ "name": "keynut-monorepo", "packageManager": "yarn@4.5.3", "private": true, "workspaces": [ "apps/*", "packages/*" ], "devDependencies": { "typescript": "^5.7.2" }, "scripts": { "dev": "yarn workspaces foreach --all --parallel --interlaced run dev" } }
typescript 통합 관리
루트 디렉터리로 돌아가서 타입스크립트 설치
$ yarn add -D typescript
각 프로젝트에도 설치
$ yarn workspace admin add -D typescript $ yarn workspace web add -D typescript
tsconfig.base.json 파일을 만들어준다. 아래의 파일로 모든 workspace의 ts 설정을 해줄 거다.
{ "compilerOptions": { "target": "ES2022", "declaration": true, "declarationMap": true, "esModuleInterop": true, "incremental": false, "isolatedModules": true, "lib": ["es2022", "DOM", "DOM.Iterable"], "module": "NodeNext", "moduleDetection": "force", "noEmit": true, "allowJs": false, "moduleResolution": "NodeNext", "noUncheckedIndexedAccess": true, "resolveJsonModule": true, "skipLibCheck": true, "strict": true, "jsx": "preserve" } }
// root 경로의 tsconfig.json { "extends": "./tsconfig.base.json", "compilerOptions": { "plugins": [ { "name": "next" } ] }, "include": [ "next-env.d.ts", "next.config.js", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts" ], "exclude": ["node_modules"] }
// apps 프로젝트들의 tsconfig.json { "extends": "../../tsconfig.base.json", "compilerOptions": { "plugins": [ { "name": "next" } ] }, "include": [ "next-env.d.ts", "next.config.js", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts" ], "exclude": ["node_modules"] }
Prettier 설정
$ yarn add -D prettier
// root 경로에 .prettierrc { "singleQuote": true, "semi": true, "useTabs": false, "tabWidth": 2, "trailingComma": "all", "printWidth": 80, "bracketSpacing": true, "arrowParens": "always", "endOfLine": "auto" }
packages/ui 구성
packages 디렉터리는 서로 다른 프로젝트에서 재사용되는 코드를 정의하는 작업 공간이다.
ui 디렉터리에서 package.json을 만들고 필요한 의존성 연결
$ cd packages/ui && touch package.json
$ yarn add react react-dom
$ yarn add -D @types/react @types/react-dom typescript
package.json 설정
{ "name": "@keynut/ui", "private": true, "exports": { ".": "./index.ts" }, "dependencies": { "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "typescript": "^5.7.2" } }
import 할 때 나는 전자보다 후자를 더 선호해서 index에서 한 번에 처리를 해줬다.
import Button from '@keynut/ui/Button'; import Button from '@keynut/ui/AlertButton'; import { Button, AlertButton } from '@keynut/ui';
// .index.ts export * from './src/Button'; export * from './src/AlertButton';
만약 위처럼 각각 import 하고 싶으면 아래처럼 exports를 바꿔주면 된다.
{ ... "exports": { "Button": "./src/Button.tsx", "AlertButton": "./src/AlertButton.tsx" }, ... }
// ./src/Button.tsx 'use client'; export const Button = () => { return <button onClick={() => alert(`Hello World!`)}>BUTTON/</button>; };
이렇게 ui 패키지를 구성하고 만들었으면 사용할 워크스페이스에 연결하면 된다. web 워크스페이스에서 사용하고 싶다면
$ yarn workspace web add @keynut/ui

그리고 일반 패키지 사용하듯이 사용하면 된다.
vscode extensions
ZipFS는 .zip 형태로 압축된 의존성을 풀지 않고도 사용할 수 있게 해 준다.

next/image에 정의된 Image 컴포넌트 내부를 확인할 때 ZipFS가 없으면 압축된 의존성의 내부 코드를 보지 못해 아래와 같이 나온다.

느낀 점
모노레포를 구성하면서 가장 편리했던 점은 공통 의존성을 만들어 여러 워크스페이스에서 사용할 수 있었다는 것이다. 특히 admin과 client를 분리하면서 공통으로 사용하는 의존성을 한 번만 정의해 사용할 수 있어 효율적이었다.
Yarn PnP를 사용해 디스크 공간을 절약하고 의존성 관리가 간단해진 점도 좋았다. 하지만 의존성을 찾지 못하는 문제가 자주 발생했고, VSCode에서 의존성 탐색이 제대로 이루어지지 않아 불편했다. 그리고 처음에는 zero-install을 무조건적으로 좋은 방식이라고 생각을 했는데 github 과부하와 .yarn/cache 파일을 포함한 대규모 파일을 푸시하는 데 상당한 시간이 소요되는 문제가 있어 사용하지 않았다.
그럼에도 워크스페이스 간 코드 공유가 쉬워지고, 공통 라이브러리를 효율적으로 관리할 수 있어 만족스러웠다. 초기 설정과 호환성 문제는 있었지만, 전체적인 생산성과 관리 효율성을 크게 높일 수 있었다.
계속 사용을 해보다가 끝으로는 뭔가 pnpm으로 다시 바꿀 거 같다.
모노레포를 구성하면서 처음으로 Yarn과 pnpm을 학습해 봤다. npm만 사용했을 때는 몰랐던 패키지 매니저의 차이와 효율적인 의존성 관리 방법을 알게 되었다
'KEYNUT 프로젝트' 카테고리의 다른 글
번들 사이즈 최적화 (0) | 2025.01.12 |
---|---|
react-three/drei Environment 태그 렌더링 블로킹 (0) | 2024.11.29 |
느린 네트워크 환경에서 PhotoSwipe 이미지 로딩 속도 개선 (2) | 2024.09.09 |
[ Next.JS ] userAgent를 효율적으로 관리해보자 (1) | 2024.09.04 |
내가 설정한 description이 안 나온다. (0) | 2024.08.28 |