테마
UI 라이브러리 개발
마이크로프론트엔드 환경에서 일관된 UX를 보장하는 공유 UI 라이브러리 패키지를 설계하고 빌드하는 방법을 학습한다.
학습 목표
- 모노레포 내
packages/ui공유 패키지의 역할과 구조를 이해한다. - 재사용 가능한 컴포넌트(Button, Icon, Card, Navigation)를 설계하고 구현한다.
- Vite(내부적으로 Rollup)를 활용하여 라이브러리를 ESM/UMD/CJS 형태로 빌드한다.
- 빌드된 라이브러리를 각 마이크로앱에서 빌드타임에 import하여 사용하는 흐름을 파악한다.
- TypeScript 타입 정의 파일(
.d.ts) 자동 생성과 CSS 번들링 전략을 익힌다.
본문
1. UI 라이브러리가 필요한 이유
마이크로프론트엔드 아키텍처에서는 여러 팀이 독립적으로 앱을 개발한다. 이때 버튼, 네비게이션, 카드 같은 공통 UI 요소가 팀마다 제각각이면 사용자 경험이 파편화된다. UI 라이브러리 패키지는 이 문제를 빌드타임 공유 방식으로 해결한다.
런타임 공유(Module Federation의 shared)와 달리 빌드타임 공유는 각 앱이 빌드 시점에 라이브러리를 번들에 포함한다. 버전 충돌이 없고 배포 독립성이 유지되므로 UI 컴포넌트 같은 안정적인 코드에 적합하다.
2. 패키지 생성과 프로젝트 구조
모노레포의 packages/ 디렉토리 아래에 UI 라이브러리 프로젝트를 생성한다.
bash
mkdir -p packages
cd packages
pnpm create vite uikit --template react-swc-ts
cd ..
pnpm install생성 후 프로젝트 구조를 정리한다. 개발 서버용 파일(main.tsx, App.tsx 등)은 제거하고 라이브러리 엔트리 중심으로 재구성한다.
packages/ui/
src/
components/
Button.tsx
Button.module.css
icons/
Home.tsx
UserFriends.tsx
LaptopCode.tsx
Briefcase.tsx
Icons.tsx
global.css # 전역 디자인 토큰
index.ts # 라이브러리 엔트리
dist/ # 빌드 결과물
index.js # ESM
index.umd.cjs # UMD/CJS
index.d.ts # 타입 정의
index.css # 전역 스타일
vite.config.ts
package.json
tsconfig.jsonpackage.json에서 패키지 이름을 스코프 형태로 지정한다.
json
{
"name": "@career-up/uikit",
"version": "1.0.0",
"type": "module",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.umd.cjs",
"import": "./dist/index.js"
},
"./index.css": "./dist/index.css"
},
"types": "./dist/index.d.ts",
"main": "./dist/index.umd.cjs",
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0",
"vite-plugin-dts": "^3.0.0"
}
}핵심 설정 포인트는 다음과 같다.
| 필드 | 역할 |
|---|---|
exports | 모듈 해석 규칙 정의. ESM/CJS/타입 경로를 명시 |
peerDependencies | React를 외부 의존으로 선언하여 번들에 포함하지 않음 |
./index.css export | 전역 스타일을 별도 경로로 제공 |
3. 재사용 가능한 컴포넌트 개발
Button 컴포넌트 -- CSS Module을 사용하여 클래스명이 해시로 변환되고, 다른 마이크로앱 스타일과 충돌하지 않는다. PropsWithChildren으로 children을 받고, onClick, variant, size props를 지원한다.
Icon 컴포넌트 (파사드 패턴) -- 외부 아이콘 라이브러리(react-icons 등)를 직접 노출하지 않고 내부에서 래핑한다. const Icon = { Home, Briefcase, UserFriends, LaptopCode }로 네임스페이스 객체를 만들어 Icon.Home 형태로 사용한다.
라이브러리 엔트리 -- src/index.ts에서 global.css를 import하고 컴포넌트들을 named export한다. Vite가 빌드 시 CSS를 추출하여 dist/index.css로 생성한다.
ts
// src/index.ts
import "./global.css";
export { default as Button } from "./components/Button";
export { default as Icon } from "./components/icons/Icons";4. Vite(Rollup) 라이브러리 빌드 설정
vite.config.ts 설정은 다음과 같다.
ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import dts from "vite-plugin-dts";
export default defineConfig({
plugins: [
react(),
dts({ insertTypesEntry: true }),
],
build: {
outDir: "dist",
lib: {
entry: "src/index.ts",
name: "UIKit",
fileName: "index",
},
rollupOptions: {
external: ["react", "react-dom"],
output: {
globals: {
react: "React",
"react-dom": "ReactDOM",
},
assetFileNames: (assetInfo) => {
if (assetInfo.name === "style.css") return "index.css";
return assetInfo.name ?? "asset";
},
},
},
},
});| 설정 | 설명 |
|---|---|
external | React를 번들에서 제외. 소비자가 자체 React를 사용 |
globals | UMD 빌드 시 전역 변수 매핑 |
dts | .d.ts 자동 생성 및 package.json types 필드 연동 |
assetFileNames | 기본 style.css를 index.css로 변환 |
5. 마이크로앱에서 import하여 사용
각 마이크로앱의 package.json에 "@career-up/uikit": "workspace:*"를 추가하고, Module Federation 앱에서는 shared 설정에 { singleton: true }로 등록한다.
tsx
import { Button, Icon } from "@career-up/uikit";
import "@career-up/uikit/index.css";
function App() {
return (
<div>
<Button variant="primary" onClick={() => alert("Click!")}>시작하기</Button>
<Icon.Home />
</div>
);
}6. 전역 스타일과 디자인 토큰
global.css에서 CSS 커스텀 속성(변수)으로 디자인 토큰을 정의하면 각 마이크로앱이 동일한 색상, 폰트, 간격 값을 참조할 수 있다.
App Shell이 이 CSS를 최상위에서 import하면 :root 변수가 전체 DOM에 전파된다. 각 마이크로앱은 별도로 import할 필요 없이 var(--color-primary)를 자유롭게 사용할 수 있다.
핵심 정리
| 항목 | 내용 |
|---|---|
| 공유 방식 | 빌드타임 공유 (workspace:*). 각 앱이 자체 번들에 포함 |
| 빌드 도구 | Vite (내부 Rollup). ESM, UMD, CJS 3중 포맷 출력 |
| 타입 지원 | vite-plugin-dts로 .d.ts 자동 생성 |
| React 처리 | peerDependencies + external로 번들 제외 |
| CSS 전략 | 컴포넌트: CSS Modules, 전역: CSS 커스텀 속성 |
| 개발 워크플로 | vite build --watch로 변경 감지 자동 빌드 |
| 확장성 | 컴포넌트 추가 시 src/components/에 생성 후 index.ts에서 export |
다음 단계
다음 문서 02-앱쉘과-라우팅.md에서는 App Shell 패키지를 구성하고 React Router를 활용한 최상위 라우팅 시스템을 구현한다. UI 라이브러리의 Navigation 컴포넌트와 결합하여 마이크로앱 간 이동 흐름을 완성하는 과정을 다룬다.