[아키텍처] 기존 프로젝트 구조를 모노레포로 바꾸기
모노레포가 무엇이고, 모노레포를 구성하기 위한 툴로 왜 pnpm, turbo를 선택했는지에 대한 설명은
검색하면 워낙 많이 나오기 때문에 여기서는 따로 하지 않을 것이다.
모노레포 구조는 처음 시작할 때부터 모노레포로 세팅하는 것이 훨씬 쉽다.
하지만 일을 하다 보면 회사가 서비스를 늘리기도 하고
내부적으로 사용할 라이브러리를 제작해야 할 경우도 생긴다.
그래서 멀티 레포가 점점 늘어나서 유지관리에 허덕이다 보면
모노레포 구조를 이식해야 하는 경우가 생긴다.
이 글은 A Guide to adding monorepo architecture to an existing project!
사용할 툴 세팅하기
pnpm과 turbo를 global로 설치해준다.
pnpm은 npm과 같은 패키지 관리 도구인데, node_modules설치를 더 빠르게 수행한다.
turbo는 각 패키지에서 정의한 build, test, lint등의 스크립트를 병렬로 실행하고,
작업 간의 의존성을 관리하며 (어떤 패키지를 먼저 설치해야 하는지 등을 자동으로 결정)
필요에 따라 작업을 캐시하는 역할을 한다. (이전에 build를 했던 프로젝트에 변경사항이 없으면 같은 build작업을 반복하지 않음)
각 프로젝트를 build, start하는 등의 작업을 하기 쉽게 만들어준다.
npm install -g pnpm
pnpm install turbo --global
디렉토리 구성하기
이미 존재하는 두 개의 멀티 레포를 하나의 모노레포로 합치는 과정이다. (이하 project1, project2)
모노레포로 만들 레포를 하나 고른 뒤 다음 스텝을 하나씩 따른다.
1. project1의 디렉토리 구조 변경
가장 대중적으로 사용되는 모노레포 컨벤션은
root에 apps디렉토리와 packages디렉토리를 만들어
apps에는 어플리케이션 프로젝트들을,
packagess에는 모노레포에서 사용할 라이브러리 프로젝트들을 위치시키는 것이다.
apps
ㄴapplication1
ㄴapplication2
packages
ㄴlib1
ㄴlib2
일단 우리는 라이브러리를 따로 제작할 것은 아니며 project1과 project2 모두 어플리케이션이라고 가정하고
아래와 같이 구성했다. (라이브러리를 제작할 것이라면 동일한 과정을 거치면 된다.)
//기존
project1
//변경 후
apps
ㄴ project1
2. project2 clone하기
project2 레포를 apps밑 위치에서 git clone해준다.
이 때 project2내부에 있을 .git폴더를 삭제해주어야 한다.
삭제하지 않으면 git push를 할 때마다 기존의 project2의 깃허브 레포에 계속 푸시되기 때문이다.
apps
ㄴ project1
ㄴ project2 //git clone으로 추가!
3. 프로젝트의 name변경해주기
각 프로젝트 내부에는 package.json파일이 있을 것이다.
각 프로젝트의 package.json의 name필드를 일관성있게 변경해준다.
예를들면 @servicename/appname 이렇게.
//project1 예시
{
"name": "@servicename/project1",
"version": "0.1.0",
...
}
//project2 예시
{
"name": "@servicename/project2",
"version": "0.1.0",
...
}
추후 이 name에 따라 프로젝트 task명령어를 만들 것이다. (build, start 등)
4. 기존 프로젝트들의 node_modules폴더, lock파일 삭제하기
lock파일은 각 프로젝트 내부에 존재할 필요가 없어진다. lock파일은 root에서 관리 될 예정이다.
그리고 node_modules는 pnpm을 사용해 다시 설치되도록 할 것이므로 삭제해준다.
워크스페이스 세팅하기
1. root에 pnpm-workspace.yaml 파일 만들기
아래와 같이 workspace를 등록해준다.
packages:
- "apps/*"
이렇게 추가해줘야 apps폴더 아래에 있는 프로젝트들을 workspace로 인식한다.
root에서 pnpm i 실행했을 때
apps내부에 있는 프로젝트들의 node_modules가 설치된다.
만약 packages도 추가했다면 아래와 같이 작성해주면 된다.
packages:
- "apps/*"
- "packages/*"
2. root의 package.json파일 수정하기
root위치에서 pnpm init명령어를 실행하면 package.json파일이 생성될 것이다.
기본 명령어를 작성해두자.
build, dev, lint명령어는 기본적으로 아래와 같이 turbo를 사용하는데 이는 추후 수정할 것이지만 일단은
아래와 같이 작성해둔다.
그리고 pnpm사용을 강제하기 위해 scripts에 preinstall필드도 작성해준다.
{
...
"name": "monorepo",
...
"scripts": {
"preinstall": "npx only-allow pnpm", //pnpm사용을 강제하는 코드
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint"
...
},
}
3. turbo.json 파일 생성하기
수행하고 싶은 tasks들을 작성해준다. 아래는 build, dev 테스크만 작성해두었지만
만약 다른 테스크들 (ex: start, check-types..etc)도 추가하고 싶다면 추가하면 된다.
이 때 dependsOn명령어를 주목하자.
주석에 설명해둔 대로
build를 할 때 각 프로젝트들이 서로 의존하고 있다면,
build를 의존성이 없는 것 부터 하는 것이 중요하다.
예를들어 project1이 스타일 라이브러리라고 가정하고,
project2가 project1라이브러리를 설치해서 쓰고 있다면
project2는 project1이 먼저 빌드 되어야 정상적으로 작동할 것이다.
이런 의존 구조를 찾아 빌드가 먼저 필요한 프로젝트를
먼저 빌드해주는 작업을 자동으로 해주는 필드가 dependsOn 필드이다.
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], //각 프로젝트를 빌드할 때 만약 project1이 project2에
//의존하고 있다면 project2를 먼저 build하고 project1를 빌드하게 해준다.
"outputs": [".next/**", "!.next/cache/**"]
},
"dev": {
"persistent": true,
"cache": false
}
}
}
script명령어 세팅하기 - package.json 수정
이제 worskpaces에는 apps/project1, apps/project2가 존재한다.
이걸 일일이 apps에 들어가서.. project1에 들어가서 pnpm start, pnpm build를 하기는 번거롭다.
모든 작업을 root위치에서 실행할 수 있도록 만들어보자.
root위치의 package.json파일의 scripts명령어를 아래와 같이 수정한다.
{
...
"name": "monorepo",
...
"scripts": {
"preinstall": "npx only-allow pnpm",
"build:all": "turbo run build", => 명령어 수정됨
"dev:all": "turbo run dev", => 명령어 수정됨
"build:project1": "turbo run build --filter=@servicename/project1...", => project1만 빌드
"build:project2": "turbo run build --filter=@servicename/project2...", => project2만 빌드
"dev:project1": "turbo run dev --filter=@servicename/project1...", => project1만 실행
"dev:project2": "turbo run dev --filter=@servicename/project2...", => project2만 실행
"lint": "turbo run lint"
...
},
}
명령어 살펴보기
1. turbo run build는 각 workspace의 package.json에 있는 build명령어를 수행한다.
2. 이때 --filter=@servicename/project1 을 해주면 workspace들 중에서
해당 프로젝트만 딱 골라서 작업을 수행하겠다는 의미다.
3. 그리고 --filter=@servicename/project1... 에서 마지막의 ... 부분은
turbo.json에 작성한 dependsOn": ["^build"]과 유사한 작업을 수행한다.
@servicename/project2가 만약 @servicename/project1를 dependency로 설치해서 사용하고 있다면
pnpm build:project2 를 수행했을 때 @servicename/project1가 먼저 빌드되고
@servicename/project2가 그 다음에 빌드된다.
pnpm dev:project2 도 마찬가지다.
@servicename/project2가 실행되기 위해 @servicename/project1의 빌드가
필요할 경우 @servicename/project1가 빌드된 뒤에 @servicename/project2가 실행된다.
사실 dependsOn과 거의 비슷하게 동작하기 때문에 scripts명령어에 별도로 ...를 붙여줄 필요가 없지만
build, dev명령어들을 일관되게 작성하기 위해 붙여주었다.
궁금했던 사항
각 프로젝트가 사용하던 패키지 매니저가 다를 경우?
예 ) project1에서는 npm을 사용했고 project2에서는 yarn을 사용했을 경우
답 ) 알아서 잘 작동한다. 모노레포를 위해 일일이 각 패키지의 패키지 매니저를 맞춰 줄 필요 없다.
root에서 pnpm i를 하면 root에서 설치한 디팬던시만 설치되는거 아닌가?
예 ) root에서 pnpm i를 하면 root에서 설치한 디팬던시만 설치되고 하위 프로젝트들의 디팬던시는 설치 안 되는거 아닌가?
답 ) root에서 pnpm i를 실행하면 알아서 각 프로젝트(workspace)들의 node_modules가 설치된다.
프로젝트에 새로운 디팬던시를 설치하려면 어느 위치에서 해야할까?
예 ) root/apps/project1에 lodash를 설치하려면 어디에서 해야할까?
답 ) 각 프로젝트에 필요한 디팬던시는 해당 프로젝트 위치에서 설치하면 된다. root/apps/project1 위치에서 npm i lodash 하면 됨
모노레포에서 제작한 라이브러리는 어떻게 설치할까?
예 ) root/packages/style 위치에 스타일 라이브러리를 제작했다. root/apps/project1에 이 라이브러리를 설치하려면?
답 ) root/apps/project1 위치에서 'pnpm i --workspace [라이브러리이름]' 명령어로 설치한다.
만약 패키지매니저가 pnpm이 아닐 경우 각 패키지매니저에 맞는 workspace 라이브러리 설치 명령어를 검색해보기..
pnpm i --workspace [라이브러리이름]
//라이브러리 이름이 @servicenames/style 일 경우
pnpm i --workspace @servicenames/style
참조
run | Turborepo
API reference for the `turbo run` command
turbo.build
monorepo아키텍쳐로 만들어진 오픈소스 프로젝트 xyflow
https://github.com/xyflow/xyflow
GitHub - xyflow/xyflow: React Flow | Svelte Flow - Powerful open source libraries for building node-based UIs with React (https
React Flow | Svelte Flow - Powerful open source libraries for building node-based UIs with React (https://reactflow.dev) or Svelte (https://svelteflow.dev). Ready out-of-the-box and infinitely cust...
github.com
monorepo setup for existing setup (nx사용)
Monorepo setup using pnpm for existing repos using pnpm workspaces and integrating nx into them
Monorepo github repository
blog.stackademic.com
'프론트엔드' 카테고리의 다른 글
[Next.js:에러] 가장 무서운 빌드 에러 : Error occurred prerendering... (0) | 2024.12.28 |
---|---|
[Canvas] Tainted canvas 이슈 완벽하게 해결하기 (0) | 2024.11.21 |
[Canvas] 이미지 에디터 만들기 6 : 이미지 저장하기 (0) | 2024.08.26 |
[Canvas] 이미지 에디터 만들기 5 : Redo, Undo기능 구현 (0) | 2024.08.26 |
[Canvas] 이미지 에디터 만들기 4 : 이미지 중앙정렬 기능 구현 (0) | 2024.08.26 |