일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- GIT
- error
- 문자열 대소문자
- react native font
- 에러
- 리액트 네이티브 에러
- react native
- react native CLI
- 리액트 네이티브
- img upload
- js
- Access Key 생성
- s3 upload
- 리액트
- react native 개발
- AWS
- 문자열 대소문자 구별
- 리엑트 네이티브 아이콘
- PongWorld
- 백준
- babel.config.js
- firebase 라이브러리
- aws bucket 정책
- AWS Access Key
- react native 세팅
- fire base
- Next.js
- React
- Project
- 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 프로젝트' 카테고리의 다른 글
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 |
[ React ] Custom Confirm Modal 만들기 (0) | 2024.08.24 |