상세 컨텐츠

본문 제목

[monorepo] pnpm workspace monorepo 모노레포 구축기 (official gsm)

기술

by 기먕로기 2023. 7. 6. 14:14

본문

아래의 서비스(official gsm)의 환경을 세팅한 경험을 담았습니다.
해당 레포 구조를 참고하셔서 글 읽어주시면 감사하겠습니다.
https://github.com/themoment-team/official-gsm-front

 

GitHub - themoment-team/official-gsm-front: 광주소프트웨어마이스터고등학교 공식 홈페이지

광주소프트웨어마이스터고등학교 공식 홈페이지. Contribute to themoment-team/official-gsm-front development by creating an account on GitHub.

github.com

 

목차

0. 구축 배경
1. pnpm workspace 구조 세팅
2. package 생성
3. dependency 세팅
4. root package.json 세팅 [선택]


0. 구축 배경

official gsm이라는 광주소프트웨어마이스터고등학교 공식 홈페이지를 제작하며,
admin 서비스와 client 서비스를 분리하게 되었고, 
admin과 client를 분리하였지만, 서비스 구조 상 두 서비스간의 중복되는 모듈이 많다고 판단되어 모노레포로 구축하게 되었습니다.


1.  pnpm workspace 구조 세팅

workspace란, 직역하면 작업공간이고, 이 workspace로 인해 하나의 repository 안에서 여러 프로젝트 제작이 가능합니다. 
 
workspace 안에 여러 package들을 위치시킬 수 있고,
a package와 b package 가 존재한다면, a 에서 b를 사용할 수도, b 에서 a를 사용할 수도 있습니다.
 
위 서비스의 경우 package들을 apps/*, packages/* 으로 분기하였습니다.
apps 디렉토리의 경우, 빌드 및 실행이 가능한 application을 위치시켰습니다. (client, admin, storybook)
packages 디렉토리의 경우, 위 application에서 공통적으로 사용되는 config나 모듈들을 위치시켰습니다. (ex. tsconfig, ui)
 
디렉토리 구조를 짰으면, 해당 package 들을 pnpm-workspace.yaml 에 아래와 같이 명시해주어야 합니다.

packages:
  - 'apps/*'
  - 'packages/*'

2. package 생성

원하는 위치에 directory 생성 및 pnpm init 해주면 됩니다.
아래는 root workspace/packages에 ui라는 package를 생성해주는 과정입니다.

$ cd packages
$ mkdir ui
$ cd ui
$ pnpm init

이후, 해당 디렉토리에 생성된 package.json을 목적에 맞게끔 수정해주면 됩니다.


3. dependency 세팅

- Root dependency

workspace의 root에 dependency를 설치하게 되면, 
하위 packages에서 dependency를 설치하지 않아도 해당 dependency를 사용할 수 있습니다.
 
root에 dependency 설치 시, pnpm 에서 아래와 같은 에러를 띄웁니다

$ pnpm add [dependency]
# ERR_PNPM_ADDING_TO_ROOT  Running this command will add the dependency to the workspace root, which might not be what you want - if you really meant it, make it explicit by running this command again with the -w flag (or --workspace-root). If you don't want to see this warning anymore, you may set the ignore-workspace-root-check setting to true.

실수로 workspace root에 dependency를 추가함을 방지하는 것으로
우리가 root에 설치를 의도하였으므로 아래 커멘드로 root에 dependency를 설치합니다.

$ pnpm add [dependency] -w

※ trouble shooting

몇몇 의존성의 경우, root에 설치하지 않고 다른 package에 각각 의존성을 설치하는 경우 정상적으로 작동되지 않습니다.
 
emotion의 경우 package 마다 설치하여 사용하게 되면 아래와 같은 경고가 뜹니다.

You are loading @emotion/react when it is already loaded. Running multiple instances may cause problems. This can happen if multiple versions are used, or if multiple builds of the same version are used.

이미 emotion이 load 되었다는 뜻인데, 말 그대로 emotion이 단일로 실행되지 않았다는 것 입니다.
 
이 경고는 theme 같은 부분에 영향을 끼칩니다. 
예시 케이스로, 각 애플리케이션(ex. apps/client)에서 theme 설정을 해둔 상태로,
package/ui에서 해당 theme을 사용하게되면, theme이 불러와지지 않는 문제가 발생합니다.
 
react-query의 경우도, 각 애플리케이션(ex. apps/client)에서 QueryClientProvider를 설정해두어도 아래와 같은 에러가 뜹니다.

Error: No QueryClient set, use QueryClientProvider to set one

그 이유는 useQuery custom hook을 제작해둔 package(packages/api) 에도 react-query가 설치되어 있어,
애플리케이션과 같은 dependency를 사용하지 않고, 서로 각각의 react-query를 사용하고 있어 해당 이슈가 발생하는 듯 합니다.
 
해결 방법이 두개정도가 있었는데,
1. packages/api에 react-query를 peerDependencies로 추가 
2. workspace root에 react-query 추가
위 두 선택지를 활용하면 되겠습니다.
 
위 처럼 되도록 공통으로 사용되는 dependency들을 root에 설치하여 이러한 이슈를 막아야 합니다.
 

- workspace 내의 package 설치

workspace 내의 package를 workspace 내의 package에 설치하는 방법입니다.
 

$ pnpm --filter [package] add [another package] --workspace
$ pnpm --filter client add ui --workspace

 
위 커멘드로 가능합니다.


4. root package.json 세팅 [선택]

특정 package에 command를 입력하기 위해서는

$ pnpm --filter [package] [command]

위 같이 --filter filtering을 작성해주어야 한다.
 
특정 package에 명령어를 입력하는 경우가 많은데, 매번 --filter를 작성하기가 번거롭다고 생각되어
 
root package.json의 scripts 에 미리 --filter를 아래와 같이 매핑해두었습니다.

  "scripts": {
    "client": "pnpm --filter client",
    "admin": "pnpm --filter admin",
    "storybook": "pnpm --filter storybook",
    "api": "pnpm --filter api",
    "common": "pnpm --filter common",
    "ui": "pnpm --filter ui",
    "tsconfig": "pnpm --filter tsconfig",
    "eslint-config-custom": "pnpm --filter eslint-config-custom",
    "types": "pnpm --filter types"
  },

이런 식으로 미리 --filter를 매핑해두면, 특정 package 명령어를 아래처럼 사용 가능합니다.

$ pnpm client dev #pnpm --filter client dev
$ pnpm [package] [command] # command는 [package]/package.json의 scripts에 작성된 커멘드

정리

monorepo로 약 2달간 개발을 진행해보며 정말 많은 트러블 슈팅을 하였습니다.
ts 관련 문제나 dependency와 관련된 문제 등..
 
국내에 monorepo 관련 자료가 많이 없어 (아무래도 개인 프로젝트를 하며 monorepo를 구축하는 경우가 많이 없고, 작은 규모의 프로젝트에서는 monorepo를 사용하는 경우가 적기 때문이 아닌가 싶습니다.) 해외 article들과 친해지느라 조금 고생했습니다.
 
초기 구조 설계적인 부분에 있어서는 오픈 소스 repository를 많이 참고 하였습니다.
이 글을 읽게 되신다면 저랑 같은 고민들을 많이 하실텐데, 아래 repository 참고하시면 monorepo 설계에 도움받을 수 있을 듯 합니다.
https://github.com/calcom/cal.com
 

관련글 더보기

댓글 영역