Skip to content

Commit c889b34

Browse files
authored
Merge pull request #1445 from Moadong/docs/#1444-design-sys-doc-MOA-814
[docs] Design System Toolkit 설계 문서
2 parents 9d14af0 + 562d1e9 commit c889b34

1 file changed

Lines changed: 361 additions & 0 deletions

File tree

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
# Design System Toolkit — 설계 문서
2+
3+
**날짜**: 2026-04-14
4+
**작성자**: seongwon seo
5+
**상태**: 승인됨
6+
7+
---
8+
9+
## 배경 및 목표
10+
11+
### 문제
12+
13+
- 코드베이스 전반에 하드코딩된 색상값(`#3B82F6`, `rgb(...)` 등)이 산재해 있어 UI 일관성 유지가 어렵다.
14+
- 디자인 토큰이 TypeScript 파일로만 관리되어 단일 진실 공급원(single source of truth)이 불명확하다.
15+
- 토큰 위반을 감지하는 자동화 수단이 없어 코드 리뷰에서 사람이 직접 확인해야 한다.
16+
17+
### 목표
18+
19+
1. 디자인 토큰을 JSON 파일로 중앙 관리하고, 빌드 시 TypeScript 파일로 자동 변환한다.
20+
2. ESLint 커스텀 룰로 하드코딩된 값을 개발 중 실시간 감지한다.
21+
3. GitHub Actions로 PR마다 토큰 위반을 자동으로 리포트한다.
22+
23+
### 범위 외 (이번 버전)
24+
25+
- Figma API 직접 연동 (향후 확장)
26+
- AI 기반 컴포넌트 코드 자동 생성 (향후 확장)
27+
- 간격(spacing), 그림자(shadow) 등 색상 외 토큰의 ESLint 룰 (향후 확장)
28+
29+
---
30+
31+
## 아키텍처
32+
33+
```
34+
[tokens/] ← 단일 진실 공급원 (JSON)
35+
├── colors.json
36+
├── typography.json
37+
└── spacing.json
38+
39+
[Style Dictionary] ← 빌드 타임 변환 (npm run ds:build)
40+
41+
[src/styles/theme/] ← 자동 생성 (직접 편집 금지)
42+
├── colors.ts ✦ generated
43+
├── typography.ts ✦ generated
44+
└── index.ts
45+
46+
[ESLint custom rule] ← 개발 중 실시간 위반 감지
47+
48+
[GitHub Actions] ← PR마다 audit 결과 코멘트
49+
```
50+
51+
**핵심 원칙**:
52+
53+
- `tokens/`가 단일 진실 공급원. 토큰 수정 시 반드시 JSON 파일만 수정.
54+
- 기존 styled-components + ThemeProvider 사용 방식은 변경 없음.
55+
- 3단계를 독립적으로 배포 가능 — 1단계만 완료해도 팀에 가치가 있음.
56+
57+
---
58+
59+
## 1단계: 토큰 파이프라인 (Style Dictionary)
60+
61+
### 파일 구조
62+
63+
```
64+
tokens/
65+
├── colors.json
66+
├── typography.json
67+
└── spacing.json
68+
69+
config/
70+
└── style-dictionary.config.js
71+
72+
src/styles/theme/ ← Style Dictionary 출력 (기존 경로 유지)
73+
├── colors.ts ← ⚠️ AUTO-GENERATED. Edit tokens/ instead.
74+
├── typography.ts ← ⚠️ AUTO-GENERATED. Edit tokens/ instead.
75+
├── transitions.ts ← 수동 관리 (애니메이션은 토큰화 범위 외)
76+
└── index.ts
77+
```
78+
79+
### 토큰 JSON 형식
80+
81+
```json
82+
// tokens/colors.json
83+
{
84+
"color": {
85+
"primary": { "value": "#3B82F6", "type": "color" },
86+
"primary-hover": { "value": "#2563EB", "type": "color" },
87+
"text-default": { "value": "#111827", "type": "color" },
88+
"text-muted": { "value": "#6B7280", "type": "color" },
89+
"background": { "value": "#FFFFFF", "type": "color" },
90+
"border": { "value": "#E5E7EB", "type": "color" }
91+
}
92+
}
93+
```
94+
95+
### Style Dictionary 설정
96+
97+
```js
98+
// config/style-dictionary.config.js
99+
module.exports = {
100+
source: ['tokens/**/*.json'],
101+
platforms: {
102+
ts: {
103+
transformGroup: 'js',
104+
buildPath: 'src/styles/theme/',
105+
files: [
106+
{
107+
destination: 'colors.ts',
108+
format: 'javascript/es6',
109+
filter: { type: 'color' },
110+
},
111+
{
112+
destination: 'typography.ts',
113+
format: 'javascript/es6',
114+
filter: { type: 'typography' },
115+
},
116+
],
117+
},
118+
},
119+
};
120+
```
121+
122+
### npm 스크립트
123+
124+
```json
125+
{
126+
"ds:build": "style-dictionary build --config config/style-dictionary.config.js",
127+
"ds:watch": "style-dictionary build --watch --config config/style-dictionary.config.js",
128+
"prebuild": "npm run ds:build"
129+
}
130+
```
131+
132+
`prebuild` 훅으로 `npm run build` 전에 자동 실행. (기존 `prebuild` 훅 없음 — 충돌 없이 추가 가능)
133+
134+
### 마이그레이션 전략
135+
136+
1. 기존 `src/styles/theme/colors.ts` 값을 `tokens/colors.json`으로 이관
137+
2. Style Dictionary로 동일한 `colors.ts` 재생성 (출력 검증)
138+
3. 기존 파일 상단에 `// ⚠️ AUTO-GENERATED. Edit tokens/ instead.` 주석 추가
139+
4. `.gitignore`에 추가하지 않음 — 생성 파일도 git에 포함해 CI 없이도 팀이 바로 사용 가능
140+
141+
---
142+
143+
## 2단계: ESLint 커스텀 룰
144+
145+
### 감지 대상
146+
147+
styled-components 템플릿 리터럴 내부의 하드코딩된 색상값:
148+
149+
- HEX: `#rgb`, `#rrggbb`, `#rrggbbaa`
150+
- RGB/RGBA: `rgb(...)`, `rgba(...)`
151+
- HSL/HSLA: `hsl(...)`, `hsla(...)`
152+
- Named colors: `red`, `blue` 등 CSS 색상 키워드
153+
154+
```tsx
155+
// ❌ 위반 — ESLint 경고
156+
const Button = styled.button`
157+
color: #3b82f6;
158+
background: rgb(0, 0, 0);
159+
`;
160+
161+
// ✅ 통과
162+
const Button = styled.button`
163+
color: ${({ theme }) => theme.colors.primary};
164+
`;
165+
```
166+
167+
### 구현
168+
169+
```
170+
src/eslint-rules/
171+
└── no-hardcoded-design-tokens.js
172+
```
173+
174+
- AST에서 `TaggedTemplateExpression` (styled-components) 탐지
175+
- 템플릿 리터럴 문자열에서 색상 패턴 정규식 매칭
176+
- 위반 시 토큰 역매핑으로 자동 제안 (`#3B82F6``theme.colors.primary`)
177+
- `tokens/colors.json`을 읽어 제안 목록 동적 생성
178+
179+
### ESLint 설정
180+
181+
```js
182+
// eslint.config.mjs ← 프로젝트 기존 파일에 추가
183+
import noHardcodedTokens from './src/eslint-rules/no-hardcoded-design-tokens.js';
184+
185+
export default [
186+
{
187+
plugins: {
188+
'design-system': { rules: { 'no-hardcoded-tokens': noHardcodedTokens } },
189+
},
190+
rules: {
191+
'design-system/no-hardcoded-tokens': 'warn', // 안정화 후 'error'로 승격
192+
},
193+
},
194+
];
195+
```
196+
197+
### 에러 메시지 형식
198+
199+
```
200+
Hardcoded color '#3B82F6' found. Use theme token instead: theme.colors.primary
201+
```
202+
203+
---
204+
205+
## 3단계: GitHub Actions CI
206+
207+
### 워크플로우
208+
209+
```yaml
210+
# .github/workflows/design-audit.yml
211+
name: Design System Audit
212+
213+
on:
214+
pull_request:
215+
paths:
216+
- 'src/**'
217+
- 'tokens/**'
218+
219+
jobs:
220+
design-audit:
221+
runs-on: ubuntu-latest
222+
permissions:
223+
pull-requests: write
224+
225+
steps:
226+
- uses: actions/checkout@v4
227+
- uses: actions/setup-node@v4
228+
with:
229+
node-version: '20'
230+
cache: 'npm'
231+
232+
- run: npm ci
233+
- run: npm run ds:build
234+
235+
- name: Run ESLint (design tokens audit)
236+
id: lint
237+
run: npm run lint -- --format json --output-file lint-results.json || true
238+
239+
- name: Post PR comment
240+
uses: actions/github-script@v7
241+
with:
242+
script: |
243+
const fs = require('fs');
244+
const results = JSON.parse(fs.readFileSync('lint-results.json', 'utf8'));
245+
const violations = results
246+
.flatMap(r => r.messages
247+
.filter(m => m.ruleId === 'design-system/no-hardcoded-tokens')
248+
.map(m => ` ${r.filePath.replace(process.cwd(), '')}:${m.line} ${m.message}`)
249+
);
250+
251+
const body = violations.length === 0
252+
? '✅ **Design System Audit**: No hardcoded token violations found.'
253+
: `🎨 **Design System Audit**: ${violations.length} violation(s) found.\n\n\`\`\`\n${violations.join('\n')}\n\`\`\``;
254+
255+
github.rest.issues.createComment({
256+
issue_number: context.issue.number,
257+
owner: context.repo.owner,
258+
repo: context.repo.repo,
259+
body
260+
});
261+
```
262+
263+
### PR 코멘트 예시
264+
265+
```
266+
🎨 Design System Audit: 3 violation(s) found.
267+
268+
/src/pages/MainPage/components/ClubCard/ClubCard.styles.ts:12 Hardcoded color '#3B82F6'. Use theme.colors.primary
269+
/src/components/common/Modal/Modal.styles.ts:34 Hardcoded color '#111827'. Use theme.colors.text-default
270+
/src/pages/AdminPage/components/SideBar/SideBar.styles.ts:8 Hardcoded color '#E5E7EB'. Use theme.colors.border
271+
```
272+
273+
---
274+
275+
## 구현 순서
276+
277+
```
278+
Phase 1: 토큰 파이프라인 ~3일
279+
├── tokens/ 디렉토리 + JSON 파일 작성
280+
├── Style Dictionary 설치 및 설정
281+
├── 기존 theme 파일 마이그레이션
282+
└── npm 스크립트 연결 + prebuild 훅
283+
284+
Phase 2: ESLint 커스텀 룰 ~3일
285+
├── AST 기반 룰 작성
286+
├── 토큰 역매핑 제안 로직
287+
├── ESLint config 연결
288+
└── 기존 위반 목록 확인 (warn으로 시작)
289+
290+
Phase 3: GitHub Actions ~1일
291+
├── 워크플로우 파일 작성
292+
├── PR 코멘트 스크립트 작성
293+
└── 테스트 PR로 동작 검증
294+
```
295+
296+
---
297+
298+
## Figma 연동 전략
299+
300+
토큰 파이프라인은 입력이 JSON이면 출처에 무관하게 동작하도록 설계되어 있다. Figma 연동은 파이프라인의 전제조건이 아니며, 단계적으로 도입한다.
301+
302+
### 현재 상태 진단 기준
303+
304+
| Figma 상태 | 대응 방법 |
305+
| ----------------------- | -------------------------------------- |
306+
| Variables/Styles 미정의 | 코드에서 역추출 → `tokens/*.json` 작성 |
307+
| Styles만 있음 | Tokens Studio 플러그인으로 내보내기 |
308+
| Variables까지 있음 | Figma Variables API 자동 동기화 |
309+
310+
### 단계별 전략
311+
312+
**Phase 0 (지금)**: 코드 역추출
313+
314+
현재 `src/styles/theme/colors.ts`, `typography.ts`에 이미 정의된 값을 `tokens/*.json`으로 이관. Figma 연동 없이 파이프라인 먼저 구축.
315+
316+
```
317+
코드(theme/*.ts) → tokens/*.json → Style Dictionary → theme/*.ts (자동 생성)
318+
```
319+
320+
**Phase 1 (파이프라인 안정화 후)**: Tokens Studio 반자동 연동
321+
322+
디자이너가 Figma에 Styles/Variables를 정의하면, [Tokens Studio](https://tokens.studio/) 플러그인으로 `tokens.json` 내보내기. 수동이지만 디자이너 주도로 동기화 가능.
323+
324+
```
325+
Figma (Tokens Studio) → tokens/*.json export → PR → ds:build
326+
```
327+
328+
**Phase 2 (선택)**: Figma Variables API 자동화
329+
330+
Figma Professional 플랜 이상에서 Variables API 사용 가능. `scripts/sync-figma-tokens.js` 스크립트로 완전 자동화.
331+
332+
```bash
333+
# Personal Access Token 필요
334+
FIGMA_TOKEN=xxx FILE_ID=yyy npm run ds:figma-sync
335+
```
336+
337+
```
338+
Figma Variables API → scripts/sync-figma-tokens.js → tokens/*.json → ds:build
339+
```
340+
341+
### 핵심 원칙
342+
343+
- **파이프라인 입력 포맷(JSON)은 고정** — Figma 연동 방식이 바뀌어도 이후 과정은 변경 없음
344+
- **디자이너와의 싱크 시점**: 파이프라인이 작동하기 시작하면 디자이너에게 Figma Styles/Variables 정의 요청. 코드 기준 토큰을 Figma에 역으로 반영하는 것도 가능.
345+
346+
---
347+
348+
## 향후 확장 가능성
349+
350+
- **AI 코드 생성**: Claude API로 토큰 스펙 기반 컴포넌트 초안 생성
351+
- **spacing/shadow 룰**: ESLint 룰을 색상 외 토큰으로 확장
352+
- **토큰 사용 리포트**: 각 토큰이 몇 곳에서 쓰이는지 통계 생성
353+
354+
---
355+
356+
## 성공 기준
357+
358+
- [ ] `npm run ds:build` 실행 시 `tokens/*.json``src/styles/theme/*.ts` 변환 성공
359+
- [ ] 하드코딩된 색상값에 ESLint 경고가 표시됨
360+
- [ ] PR 오픈 시 GitHub Actions가 자동으로 위반 목록을 코멘트로 남김
361+
- [ ] 기존 `ThemeProvider` 기반 코드가 마이그레이션 후에도 정상 동작

0 commit comments

Comments
 (0)