모노레포 가이드 (pnpm, Storybook, Rollup.js, typescript, React, Emotion)

Derhon·2023년 7월 2일
4
post-thumbnail

프로젝트 규모가 크다면, 모노레포 도입을 고려해볼만 하다.
특히 디자인 시스템을 분리하기 위해선 사실상 필수적인데, 레퍼런스가 생각보다 없다고 느껴졌다.

나도 Rollup.js를 활용해서 npm 배포를 한 적은 있지만, 모노레포 구성을 처음부터 직접 해본 경험은 없었기에...
이번에 제대로 정리해보았다.

아래 레포 전략과 동일하다거나 유사하다면 이 글이 도움이 될지도

레포지토리 전략

└── project
    └── packages
        ├── common
        │   └── 디자인 시스템
        └── application
            └── 어플리케이션
  • 패키지 매니저
    • pnpm
      : yarn berry 많이 쓰이는거 아는데, 이번 프로젝트에서 pnpm 이 쓰였다.
      사실 딱히 이유없음
  • 디자인 시스템
    • Storybook
      • 잔 설정 세팅은 다음 포스팅에서
    • emotion
      • 최고
    • react typescript
      • peer-dependency로 할 예정
  • 어플리케이션
    • t3-stack
      • 요즘에 또 풀스택이 모던 패러다임이라는 소문이...

모노레포 세팅하기

디렉토리

└── project (레포 이름)
    └── packages
        ├── common (디자인 시스템 이름 | 보통 common이라고 많이함)
        └── application (프로젝트명)

모노레포 설정 파일

📄 pnpm-workspace.yaml

./pnp-workspace.yaml

packages:
  - 'packages/*'

📄 package.json (root)

./package.json

{
  "name": "mono-repo",
  "version": "1.0.0",
  "description": "",
  "packageManager": "pnpm@8.5.1",
  "scripts": {
    "common": "pnpm -F @mono-repo/common",
    "app": "pnpm -F @mono-repo/application",
    "setting": "pnpm -r setting"
  },
  "keywords": [],
  "author": ""
}

-F 속성은 특정 패키지에 대해서만 스크립트를 실행한다는 뜻
-r 속성은 하위 패키지에 setting 스크립트를 실행하겠다는 뜻

여기까지 기본 설정이니까 pnpm install 해주어도 된다.
내부 디렉토리까지 세팅하고 해줘도 문제는 없다.

📄 package.json (common)

./packages/common/package.json

{
  "name": "@mono-repo/common",
  "main": "src/index.ts",
  ...
}

📄 package.json (application)

./packages/application/package.json

{
  "name": "@mono-repo/application",
  ...
}

사실 내부 application과 common의 package.json은 직접 작성하기보다 프레임워크(혹은 라이브러리) 세팅 후 수정하는 방향으로 가면 된다.

세팅은 지금부터 고고

디자인 시스템 설정하기

Storybook typescript react Rollup.js emotion/css emotion/styled

CRAvite template는 사용하지 않았다.
디자인 시스템은 최대한 컴팩트하게 가는게 목표다.

기본 설정

고군분투

사실 처음에 Vite 기반으로 디자인 시스템을 설정하다가,
Vite는 개발 서버를 위한 번들러라는 것을 알게 되고... Rollup.js로 다시 회귀했다.
모노레포를 생각하면서 세팅하니까 생각보다 까다로웠다.
하지만 아래 방법으로 하면 이제 더 시간낭비 안해도 될 듯하다.

React, TS, Emotion, Rollup.js

pnpm add --filter common react react-dom @emotion/react @emotion/styled tslib
pnpm add -D typescript rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-peer-deps-external rollup-plugin-typescript2 @rollup/plugin-json rollup-plugin-terser @types/react @types/react-dom

pnpm에서 workspace를 활용한다면, 특정 프로젝트에만 모듈을 설치할 때는 --filter <프로젝트명> <패키지명>을 입력해야한다. 위에서 봤던 -F랑 동일하다.

우선 common에만 storybook을 설정할거니까 위와 같이 입력했다.

peer-dependency

처음 피어디펜던시 설정할 때에는 모듈화 생각하면서 설정했는데,,,

생각해보니까 npm 배포용이 아니니까 굳이 필요가 없나? 싶다가도
어디선가 pnpm은 피어디펜던시에 굉장히 엄격하다 는 말을 본 적 있는 것 같아서... 우선 설정하였다.

./packages/common/package.json

...
"peerDependencies": {
  "react": "^17.0.2",
  "react-dom": "^17.0.2",
  "@emotion/react": "^11.4.1",
  "@emotion/styled": "^11.3.0"
}
...

중간에 peerDependencies를 추가해주면 된다.
어? 스토리북 쓴다면서 갸는 왜 추가 안하는지라고 묻는다면, 걔는 다른 프로젝트에 가져다 쓸 때 의존되지 않기 때문이다.
쉽게 말해서 여기서만 쓰는애들은 피어디펜던시가 아님.

Storybook 설정

해당 프로젝트의 핵심인 스토리북
여러가지 애드온이나 설정이 필요하지만, 우선 여기서는 이정도만!

npx sb init

하면 이제 스토리북 세팅이 알아서 대충 완료된다.

추가 설정

./packages/common/package.json

  "name": "@mono-repo/common",
  "version": "1.0.0",
  "description": "",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "rollup -c rollup.config.mjs",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
...생략

./packages/common/tsconfig.json

{
  "compilerOptions": {
    "outDir": "dist",
    "module": "ESNext",
    "target": "ES6",
    "lib": ["ES6", "dom"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDirs": ["src"],
    "baseUrl": ".",
    "forceConsistentCasingInFileNames": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "esModuleInterop": true,
    "declaration": true,
    "skipLibCheck": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "importHelpers": true,
    "paths": {
      "tslib" : ["path/to/node_modules/tslib/tslib.d.ts"]
    },
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.stories.tsx"]
}

나는 UI 컴포넌트를 src 디렉토리 하위에 위치시킬 것이고, dist 디렉토리에 빌드할 것이라 이렇게 설정하였다.

./packages/common/rollup.config.mjs

import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import json from "@rollup/plugin-json";
import { terser } from "rollup-plugin-terser";

export default {
  input: "src/index.ts",
  output: [
    {
      file: "dist/index.js",
      format: "cjs",
      sourcemap: true,
    },
    {
      file: "dist/index.esm.js",
      format: "esm",
      sourcemap: true,
    },
  ],
  plugins: [
    peerDepsExternal(),
    resolve(),
    commonjs(),
    typescript({
      tsconfigOverride: {
        compilerOptions: {
          declaration: true,
          declarationDir: './dist'
        }
      },
      rollupCommonJSResolveHack: true,
      clean: true
    }),
    json(),
    terser(),
  ],
};

Rollup의 경우, 기본적으로 CommonJS형식으로 해석한다.
때문에 ES모듈로 사용할 수 있도록 추가적인 설정을 해주었다.

실행

pnpm run storybook

일단 제 컴퓨터에서는 잘 되네여

ㅋㅋ

어플리케이션 세팅

여기는 사실 본인이 원하는 프로젝트를 알아서 세팅하면 된다.
t3-stack으로..

packages 디렉토리에서 아래 CLI를 입력한다.
사실 그전까지 application 디렉토리는 없어야한다 ㅋㅋ

pnpm create t3-app@latest

이후 프로젝트 이름 입력하고, 원하는 스택 (태일윈드 극혐!) 선택하고, 절대경로 alias만 입력하면 끝난다. 나는 보통 @ 이거를 쓴다.

pnpm dev

잘 실행되면 굿

안되면...

디자인 시스템 import

이제 모노레포를 만든 이유인, 디자인 시스템 import를 해보겠다.

common에서

index.ts에서 export 해주겠음
대충 Button 하나만 index.ts에 불러와서 export 해준다

./pacakges/common/stories/index.ts

export { default as Button } from "./Button";

버튼 컴포넌트는 내부는 생략.

application에서

이제 아래 명령어를 이용해서 디자인 시스템 모듈을 어플리케이션으로 끌어오겠다.

pnpm app add @mono-repo/common
import {Button} from "@mono-repo/common"

export default function Home() {
  const hello = api.example.hello.useQuery({ text: "from tRPC" });

  return <><Button label="ㅎㅇ"/></>;
}
...

이런식으로 불러올 수 있다.

참고

pnpm으로 모노레포 환경 구축하기

profile
🧑‍🚀 이사했어요 ⮕ https://99uulog.tistory.com/

1개의 댓글

comment-user-thumbnail
2023년 8월 2일

안녕하세요 글을 읽고 궁금한 점이 있어서 메일로 질문 드렸습니다!
확인 부탁드립니다🙇🏻‍♂️

답글 달기