프로젝트 규모가 크다면, 모노레포 도입을 고려해볼만 하다.
특히 디자인 시스템을 분리하기 위해선 사실상 필수적인데, 레퍼런스가 생각보다 없다고 느껴졌다.
나도 Rollup.js
를 활용해서 npm 배포를 한 적은 있지만, 모노레포 구성을 처음부터 직접 해본 경험은 없었기에...
이번에 제대로 정리해보았다.
아래 레포 전략과 동일하다거나 유사하다면 이 글이 도움이 될지도
└── project
└── packages
├── common
│ └── 디자인 시스템
└── application
└── 어플리케이션
pnpm
yarn berry
많이 쓰이는거 아는데, 이번 프로젝트에서 pnpm
이 쓰였다.Storybook
emotion
react
typescript
t3-stack
└── project (레포 이름)
└── packages
├── common (디자인 시스템 이름 | 보통 common이라고 많이함)
└── application (프로젝트명)
./pnp-workspace.yaml
packages:
- 'packages/*'
./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
해주어도 된다.
내부 디렉토리까지 세팅하고 해줘도 문제는 없다.
./packages/common/package.json
{
"name": "@mono-repo/common",
"main": "src/index.ts",
...
}
./packages/application/package.json
{
"name": "@mono-repo/application",
...
}
사실 내부 application과 common의 package.json은 직접 작성하기보다 프레임워크(혹은 라이브러리) 세팅 후 수정하는 방향으로 가면 된다.
세팅은 지금부터 고고
Storybook
typescript
react
Rollup.js
emotion/css
emotion/styled
CRA
나 vite template
는 사용하지 않았다.
디자인 시스템은 최대한 컴팩트하게 가는게 목표다.
사실 처음에 Vite
기반으로 디자인 시스템을 설정하다가,
Vite
는 개발 서버를 위한 번들러라는 것을 알게 되고... 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을 설정할거니까 위와 같이 입력했다.
처음 피어디펜던시 설정할 때에는 모듈화 생각하면서 설정했는데,,,
생각해보니까 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
를 추가해주면 된다.
어? 스토리북 쓴다면서 갸는 왜 추가 안하는지라고 묻는다면, 걔는 다른 프로젝트에 가져다 쓸 때 의존되지 않기 때문이다.
쉽게 말해서 여기서만 쓰는애들은 피어디펜던시가 아님.
해당 프로젝트의 핵심인 스토리북
여러가지 애드온이나 설정이 필요하지만, 우선 여기서는 이정도만!
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
를 해보겠다.
index.ts에서 export 해주겠음
대충 Button 하나만 index.ts에 불러와서 export 해준다
./pacakges/common/stories/index.ts
export { default as Button } from "./Button";
버튼 컴포넌트는 내부는 생략.
이제 아래 명령어를 이용해서 디자인 시스템 모듈을 어플리케이션으로 끌어오겠다.
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="ㅎㅇ"/></>;
}
...
이런식으로 불러올 수 있다.
안녕하세요 글을 읽고 궁금한 점이 있어서 메일로 질문 드렸습니다!
확인 부탁드립니다🙇🏻♂️