@mandujs/mcp 0.9.19 → 0.9.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -0
- package/package.json +1 -1
- package/src/activity-monitor.ts +847 -231
- package/src/resources/handlers.ts +244 -0
- package/src/resources/skills/guides.ts +1136 -0
- package/src/resources/skills/index.ts +12 -0
- package/src/resources/skills/loader.ts +218 -0
- package/src/resources/skills/mandu-composition/SKILL.md +91 -0
- package/src/resources/skills/mandu-composition/metadata.json +13 -0
- package/src/resources/skills/mandu-composition/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-composition/rules/_template.md +77 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-avoid-boolean-props.md +146 -0
- package/src/resources/skills/mandu-composition/rules/comp-arch-compound-components.md +164 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-event.md +161 -0
- package/src/resources/skills/mandu-composition/rules/comp-island-slot-split.md +167 -0
- package/src/resources/skills/mandu-composition/rules/comp-pattern-children.md +149 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-context-interface.md +148 -0
- package/src/resources/skills/mandu-composition/rules/comp-state-lift-state.md +150 -0
- package/src/resources/skills/mandu-deployment/SKILL.md +92 -0
- package/src/resources/skills/mandu-deployment/_sections.md +41 -0
- package/src/resources/skills/mandu-deployment/_template.md +38 -0
- package/src/resources/skills/mandu-deployment/metadata.json +13 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-bun.md +109 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-build-output.md +115 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-cicd-github.md +219 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-bun.md +150 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-docker-compose.md +223 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-fly.md +152 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-render.md +179 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-supabase.md +323 -0
- package/src/resources/skills/mandu-deployment/rules/deploy-platform-vercel.md +140 -0
- package/src/resources/skills/mandu-fs-routes/SKILL.md +82 -0
- package/src/resources/skills/mandu-fs-routes/metadata.json +12 -0
- package/src/resources/skills/mandu-fs-routes/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-fs-routes/rules/_template.md +69 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-api-methods.md +65 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-dynamic-param.md +93 -0
- package/src/resources/skills/mandu-fs-routes/rules/routes-naming-page.md +55 -0
- package/src/resources/skills/mandu-guard/SKILL.md +129 -0
- package/src/resources/skills/mandu-guard/metadata.json +12 -0
- package/src/resources/skills/mandu-guard/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-guard/rules/_template.md +82 -0
- package/src/resources/skills/mandu-guard/rules/guard-config-rules.md +100 -0
- package/src/resources/skills/mandu-guard/rules/guard-layer-direction.md +76 -0
- package/src/resources/skills/mandu-guard/rules/guard-preset-mandu.md +81 -0
- package/src/resources/skills/mandu-guard/rules/guard-validate-import.md +80 -0
- package/src/resources/skills/mandu-hydration/SKILL.md +91 -0
- package/src/resources/skills/mandu-hydration/metadata.json +12 -0
- package/src/resources/skills/mandu-hydration/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-hydration/rules/_template.md +72 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-data-event.md +109 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-directive-use-client.md +55 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-island-setup.md +113 -0
- package/src/resources/skills/mandu-hydration/rules/hydration-priority-visible.md +68 -0
- package/src/resources/skills/mandu-performance/SKILL.md +85 -0
- package/src/resources/skills/mandu-performance/metadata.json +14 -0
- package/src/resources/skills/mandu-performance/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-performance/rules/_template.md +64 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-defer-await.md +103 -0
- package/src/resources/skills/mandu-performance/rules/perf-async-parallel.md +95 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-file.md +124 -0
- package/src/resources/skills/mandu-performance/rules/perf-bun-serve.md +125 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-imports.md +80 -0
- package/src/resources/skills/mandu-performance/rules/perf-bundle-island-lazy.md +145 -0
- package/src/resources/skills/mandu-performance/rules/perf-cache-react.md +98 -0
- package/src/resources/skills/mandu-performance/rules/perf-render-transitions.md +154 -0
- package/src/resources/skills/mandu-security/SKILL.md +87 -0
- package/src/resources/skills/mandu-security/metadata.json +13 -0
- package/src/resources/skills/mandu-security/rules/_sections.md +31 -0
- package/src/resources/skills/mandu-security/rules/_template.md +74 -0
- package/src/resources/skills/mandu-security/rules/sec-auth-guard.md +127 -0
- package/src/resources/skills/mandu-security/rules/sec-env-management.md +133 -0
- package/src/resources/skills/mandu-security/rules/sec-input-validate.md +148 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-csrf.md +146 -0
- package/src/resources/skills/mandu-security/rules/sec-protect-headers.md +138 -0
- package/src/resources/skills/mandu-slot/SKILL.md +85 -0
- package/src/resources/skills/mandu-slot/metadata.json +12 -0
- package/src/resources/skills/mandu-slot/rules/_sections.md +36 -0
- package/src/resources/skills/mandu-slot/rules/_template.md +63 -0
- package/src/resources/skills/mandu-slot/rules/slot-basic-structure.md +38 -0
- package/src/resources/skills/mandu-slot/rules/slot-ctx-response.md +56 -0
- package/src/resources/skills/mandu-slot/rules/slot-guard-auth.md +59 -0
- package/src/resources/skills/mandu-slot/rules/slot-http-methods.md +64 -0
- package/src/resources/skills/mandu-styling/SKILL.md +118 -0
- package/src/resources/skills/mandu-styling/_sections.md +36 -0
- package/src/resources/skills/mandu-styling/_template.md +32 -0
- package/src/resources/skills/mandu-styling/metadata.json +13 -0
- package/src/resources/skills/mandu-styling/rules/style-component-compound.md +235 -0
- package/src/resources/skills/mandu-styling/rules/style-component-slots.md +255 -0
- package/src/resources/skills/mandu-styling/rules/style-component-tokens.md +205 -0
- package/src/resources/skills/mandu-styling/rules/style-island-animations.md +272 -0
- package/src/resources/skills/mandu-styling/rules/style-island-scoping.md +167 -0
- package/src/resources/skills/mandu-styling/rules/style-island-variants.md +221 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-critical.md +209 -0
- package/src/resources/skills/mandu-styling/rules/style-perf-purge.md +192 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-modules.md +162 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-panda.md +164 -0
- package/src/resources/skills/mandu-styling/rules/style-setup-tailwind.md +161 -0
- package/src/resources/skills/mandu-styling/rules/style-theme-darkmode.md +229 -0
- package/src/resources/skills/mandu-testing/SKILL.md +99 -0
- package/src/resources/skills/mandu-testing/metadata.json +13 -0
- package/src/resources/skills/mandu-testing/rules/_sections.md +26 -0
- package/src/resources/skills/mandu-testing/rules/_template.md +65 -0
- package/src/resources/skills/mandu-testing/rules/test-component-island.md +195 -0
- package/src/resources/skills/mandu-testing/rules/test-e2e-playwright.md +196 -0
- package/src/resources/skills/mandu-testing/rules/test-mock-fetch.md +219 -0
- package/src/resources/skills/mandu-testing/rules/test-slot-unit.md +192 -0
- package/src/resources/skills/mandu-ui/SKILL.md +117 -0
- package/src/resources/skills/mandu-ui/_sections.md +23 -0
- package/src/resources/skills/mandu-ui/_template.md +32 -0
- package/src/resources/skills/mandu-ui/metadata.json +13 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-aria.md +232 -0
- package/src/resources/skills/mandu-ui/rules/ui-accessibility-focus.md +238 -0
- package/src/resources/skills/mandu-ui/rules/ui-composition-patterns.md +259 -0
- package/src/resources/skills/mandu-ui/rules/ui-island-integration.md +258 -0
- package/src/resources/skills/mandu-ui/rules/ui-radix-patterns.md +213 -0
- package/src/resources/skills/mandu-ui/rules/ui-shadcn-setup.md +209 -0
- package/src/resources/skills/recipes.ts +932 -0
- package/src/server.ts +3 -0
- package/src/tools/hydration.ts +8 -8
- package/src/tools/index.ts +1 -0
- package/src/tools/seo.ts +417 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Critical CSS Extraction
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Improves First Contentful Paint
|
|
5
|
+
tags: styling, performance, critical, fcp
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Critical CSS Extraction
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (Improves First Contentful Paint)**
|
|
11
|
+
|
|
12
|
+
초기 렌더링에 필요한 CSS만 인라인으로 포함하여 FCP를 개선하세요.
|
|
13
|
+
|
|
14
|
+
## Tailwind의 자동 최적화
|
|
15
|
+
|
|
16
|
+
Tailwind CSS는 JIT 컴파일로 사용된 클래스만 생성합니다.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// tailwind.config.ts
|
|
20
|
+
export default {
|
|
21
|
+
content: [
|
|
22
|
+
"./app/**/*.{ts,tsx}", // 실제 사용되는 파일만 스캔
|
|
23
|
+
],
|
|
24
|
+
// ...
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Above-the-fold CSS 분리
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// app/layout.tsx
|
|
32
|
+
export default function RootLayout({ children }) {
|
|
33
|
+
return (
|
|
34
|
+
<html>
|
|
35
|
+
<head>
|
|
36
|
+
{/* Critical CSS: 인라인 */}
|
|
37
|
+
<style
|
|
38
|
+
dangerouslySetInnerHTML={{
|
|
39
|
+
__html: `
|
|
40
|
+
/* Above-the-fold 스타일만 */
|
|
41
|
+
body { margin: 0; font-family: system-ui; }
|
|
42
|
+
.header { height: 64px; background: white; }
|
|
43
|
+
.hero { min-height: 50vh; }
|
|
44
|
+
`,
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
|
|
48
|
+
{/* Non-critical CSS: 비동기 로드 */}
|
|
49
|
+
<link
|
|
50
|
+
rel="preload"
|
|
51
|
+
href="/styles/main.css"
|
|
52
|
+
as="style"
|
|
53
|
+
onLoad="this.onload=null;this.rel='stylesheet'"
|
|
54
|
+
/>
|
|
55
|
+
<noscript>
|
|
56
|
+
<link rel="stylesheet" href="/styles/main.css" />
|
|
57
|
+
</noscript>
|
|
58
|
+
</head>
|
|
59
|
+
<body>{children}</body>
|
|
60
|
+
</html>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Lightning CSS 통합
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bun add -d lightningcss
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// scripts/build-css.ts
|
|
73
|
+
import { transform, browserslistToTargets } from "lightningcss";
|
|
74
|
+
import browserslist from "browserslist";
|
|
75
|
+
|
|
76
|
+
const targets = browserslistToTargets(browserslist(">= 0.25%"));
|
|
77
|
+
|
|
78
|
+
const css = await Bun.file("app/globals.css").text();
|
|
79
|
+
|
|
80
|
+
const { code } = transform({
|
|
81
|
+
filename: "globals.css",
|
|
82
|
+
code: Buffer.from(css),
|
|
83
|
+
minify: true,
|
|
84
|
+
targets,
|
|
85
|
+
// Critical CSS 추출
|
|
86
|
+
analyzeDependencies: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await Bun.write("dist/styles.css", code);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## PostCSS 설정
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// postcss.config.js
|
|
96
|
+
export default {
|
|
97
|
+
plugins: {
|
|
98
|
+
tailwindcss: {},
|
|
99
|
+
autoprefixer: {},
|
|
100
|
+
...(process.env.NODE_ENV === "production"
|
|
101
|
+
? {
|
|
102
|
+
cssnano: {
|
|
103
|
+
preset: [
|
|
104
|
+
"default",
|
|
105
|
+
{
|
|
106
|
+
discardComments: { removeAll: true },
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## CSS Layer 우선순위
|
|
117
|
+
|
|
118
|
+
```css
|
|
119
|
+
/* globals.css */
|
|
120
|
+
@layer reset, base, components, utilities;
|
|
121
|
+
|
|
122
|
+
/* Critical: reset과 base만 인라인 */
|
|
123
|
+
@layer reset {
|
|
124
|
+
*,
|
|
125
|
+
*::before,
|
|
126
|
+
*::after {
|
|
127
|
+
box-sizing: border-box;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@layer base {
|
|
132
|
+
body {
|
|
133
|
+
@apply bg-background text-foreground;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Non-critical: 별도 파일 */
|
|
138
|
+
@layer components {
|
|
139
|
+
/* ... */
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@layer utilities {
|
|
143
|
+
/* ... */
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Font 최적화
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// 폰트 preload로 FOIT/FOUT 방지
|
|
151
|
+
<link
|
|
152
|
+
rel="preload"
|
|
153
|
+
href="/fonts/inter-var.woff2"
|
|
154
|
+
as="font"
|
|
155
|
+
type="font/woff2"
|
|
156
|
+
crossOrigin="anonymous"
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
// 폰트 display 설정
|
|
160
|
+
<style>
|
|
161
|
+
{`
|
|
162
|
+
@font-face {
|
|
163
|
+
font-family: 'Inter';
|
|
164
|
+
src: url('/fonts/inter-var.woff2') format('woff2');
|
|
165
|
+
font-display: swap;
|
|
166
|
+
}
|
|
167
|
+
`}
|
|
168
|
+
</style>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 성능 측정
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// 빌드 후 CSS 크기 확인
|
|
175
|
+
import { gzipSync } from "bun";
|
|
176
|
+
|
|
177
|
+
const css = await Bun.file("dist/styles.css").text();
|
|
178
|
+
const gzipped = gzipSync(Buffer.from(css));
|
|
179
|
+
|
|
180
|
+
console.log("CSS size:", css.length, "bytes");
|
|
181
|
+
console.log("Gzipped:", gzipped.length, "bytes");
|
|
182
|
+
|
|
183
|
+
// 목표: < 14KB (TCP 첫 라운드트립)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Inline Critical + Lazy Load 패턴
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
// components/StyleLoader.tsx
|
|
190
|
+
"use client";
|
|
191
|
+
|
|
192
|
+
import { useEffect } from "react";
|
|
193
|
+
|
|
194
|
+
export function StyleLoader({ href }: { href: string }) {
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
const link = document.createElement("link");
|
|
197
|
+
link.rel = "stylesheet";
|
|
198
|
+
link.href = href;
|
|
199
|
+
document.head.appendChild(link);
|
|
200
|
+
}, [href]);
|
|
201
|
+
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 사용
|
|
206
|
+
<StyleLoader href="/styles/non-critical.css" />
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Reference: [web.dev Critical CSS](https://web.dev/articles/extract-critical-css)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CSS Purge and Tree Shaking
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Removes unused CSS for smaller bundles
|
|
5
|
+
tags: styling, performance, purge, tree-shaking
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## CSS Purge and Tree Shaking
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (Removes unused CSS for smaller bundles)**
|
|
11
|
+
|
|
12
|
+
미사용 CSS를 제거하여 번들 크기를 최소화하세요.
|
|
13
|
+
|
|
14
|
+
## Tailwind 자동 Purge
|
|
15
|
+
|
|
16
|
+
Tailwind v3+는 JIT 컴파일로 사용된 클래스만 생성합니다.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// tailwind.config.ts
|
|
20
|
+
export default {
|
|
21
|
+
// content 경로가 정확해야 함
|
|
22
|
+
content: [
|
|
23
|
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
24
|
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
25
|
+
"./lib/**/*.{js,ts,jsx,tsx,mdx}",
|
|
26
|
+
],
|
|
27
|
+
// ...
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Safelist 관리
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// tailwind.config.ts
|
|
35
|
+
export default {
|
|
36
|
+
content: ["./app/**/*.{ts,tsx}"],
|
|
37
|
+
|
|
38
|
+
// 동적으로 생성되는 클래스는 safelist에 추가
|
|
39
|
+
safelist: [
|
|
40
|
+
// 정적 패턴
|
|
41
|
+
"bg-red-500",
|
|
42
|
+
"bg-green-500",
|
|
43
|
+
"bg-blue-500",
|
|
44
|
+
|
|
45
|
+
// 정규식 패턴
|
|
46
|
+
{
|
|
47
|
+
pattern: /bg-(red|green|blue)-(100|500|900)/,
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
// 변형 포함
|
|
51
|
+
{
|
|
52
|
+
pattern: /text-(red|green|blue)-500/,
|
|
53
|
+
variants: ["hover", "dark"],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 동적 클래스 문제 방지
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// ❌ Tailwind가 감지 못함
|
|
63
|
+
const color = "blue";
|
|
64
|
+
<div className={`bg-${color}-500`} />
|
|
65
|
+
|
|
66
|
+
// ✅ 전체 클래스명 사용
|
|
67
|
+
const colorClasses = {
|
|
68
|
+
blue: "bg-blue-500",
|
|
69
|
+
red: "bg-red-500",
|
|
70
|
+
green: "bg-green-500",
|
|
71
|
+
};
|
|
72
|
+
<div className={colorClasses[color]} />
|
|
73
|
+
|
|
74
|
+
// ✅ cva 사용
|
|
75
|
+
const badge = cva("px-2 py-1 rounded", {
|
|
76
|
+
variants: {
|
|
77
|
+
color: {
|
|
78
|
+
blue: "bg-blue-500",
|
|
79
|
+
red: "bg-red-500",
|
|
80
|
+
green: "bg-green-500",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## PurgeCSS 수동 설정
|
|
87
|
+
|
|
88
|
+
Tailwind 외 CSS 파일용:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
bun add -d purgecss
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// purgecss.config.js
|
|
96
|
+
export default {
|
|
97
|
+
content: ["./app/**/*.tsx", "./components/**/*.tsx"],
|
|
98
|
+
css: ["./styles/legacy.css"],
|
|
99
|
+
output: "./dist/styles/",
|
|
100
|
+
|
|
101
|
+
// 유지할 선택자
|
|
102
|
+
safelist: {
|
|
103
|
+
standard: [/^data-/],
|
|
104
|
+
deep: [/^modal/],
|
|
105
|
+
greedy: [/tooltip/],
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// 커스텀 추출기
|
|
109
|
+
extractors: [
|
|
110
|
+
{
|
|
111
|
+
extractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
|
|
112
|
+
extensions: ["tsx", "ts"],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
bunx purgecss --config purgecss.config.js
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## CSS Modules 최적화
|
|
123
|
+
|
|
124
|
+
CSS Modules는 자동으로 스코프되지만, 미사용 클래스는 제거되지 않음:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
bun add -d postcss-modules
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// postcss.config.js
|
|
132
|
+
export default {
|
|
133
|
+
plugins: {
|
|
134
|
+
"postcss-modules": {
|
|
135
|
+
generateScopedName: "[name]__[local]___[hash:base64:5]",
|
|
136
|
+
// 미사용 export 경고
|
|
137
|
+
getJSON: (cssFileName, json) => {
|
|
138
|
+
// 사용되지 않은 클래스 로깅
|
|
139
|
+
console.log(`CSS Modules in ${cssFileName}:`, Object.keys(json));
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 번들 분석
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# CSS 번들 크기 분석
|
|
150
|
+
bun add -d source-map-explorer
|
|
151
|
+
|
|
152
|
+
# 분석 실행
|
|
153
|
+
bunx source-map-explorer dist/styles.css --html report.html
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 빌드 스크립트
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// scripts/analyze-css.ts
|
|
160
|
+
import { Glob } from "bun";
|
|
161
|
+
|
|
162
|
+
const cssGlob = new Glob("dist/**/*.css");
|
|
163
|
+
let totalSize = 0;
|
|
164
|
+
|
|
165
|
+
for await (const file of cssGlob.scan()) {
|
|
166
|
+
const stat = await Bun.file(file).size;
|
|
167
|
+
console.log(`${file}: ${(stat / 1024).toFixed(2)} KB`);
|
|
168
|
+
totalSize += stat;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(`\nTotal CSS: ${(totalSize / 1024).toFixed(2)} KB`);
|
|
172
|
+
|
|
173
|
+
// 경고 임계값
|
|
174
|
+
if (totalSize > 50 * 1024) {
|
|
175
|
+
console.warn("⚠️ CSS bundle exceeds 50KB");
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## CI에서 크기 체크
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
# .github/workflows/ci.yml
|
|
183
|
+
- name: Check CSS size
|
|
184
|
+
run: |
|
|
185
|
+
SIZE=$(stat -f%z dist/styles.css 2>/dev/null || stat -c%s dist/styles.css)
|
|
186
|
+
if [ $SIZE -gt 51200 ]; then
|
|
187
|
+
echo "CSS size ($SIZE bytes) exceeds 50KB limit"
|
|
188
|
+
exit 1
|
|
189
|
+
fi
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Reference: [Tailwind Optimizing for Production](https://tailwindcss.com/docs/optimizing-for-production)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: CSS Modules Setup
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Zero-dependency styling with native Bun support
|
|
5
|
+
tags: styling, css-modules, setup, native
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## CSS Modules Setup
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (Zero-dependency styling with native Bun support)**
|
|
11
|
+
|
|
12
|
+
외부 의존성 없이 스코프된 CSS가 필요할 때 CSS Modules를 사용하세요.
|
|
13
|
+
|
|
14
|
+
**Bun 설정:**
|
|
15
|
+
|
|
16
|
+
Bun은 CSS Modules를 네이티브로 지원합니다. 별도 설정 불필요.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
// bunfig.toml (선택적 최적화)
|
|
20
|
+
[build]
|
|
21
|
+
target = "browser"
|
|
22
|
+
minify = true
|
|
23
|
+
|
|
24
|
+
[build.loader]
|
|
25
|
+
".module.css" = "css"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 사용 예시
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
/* app/button/button.module.css */
|
|
32
|
+
.button {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
border-radius: 0.5rem;
|
|
37
|
+
font-weight: 500;
|
|
38
|
+
transition: all 0.2s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.default {
|
|
42
|
+
background-color: var(--mandu-primary);
|
|
43
|
+
color: white;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.default:hover {
|
|
47
|
+
background-color: var(--mandu-primary-dark);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.outline {
|
|
51
|
+
border: 1px solid var(--mandu-primary);
|
|
52
|
+
color: var(--mandu-primary);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.outline:hover {
|
|
56
|
+
background-color: var(--mandu-primary-light);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.sm { height: 2rem; padding: 0 0.75rem; font-size: 0.875rem; }
|
|
60
|
+
.md { height: 2.5rem; padding: 0 1rem; }
|
|
61
|
+
.lg { height: 3rem; padding: 0 1.5rem; font-size: 1.125rem; }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// app/button/client.tsx
|
|
66
|
+
"use client";
|
|
67
|
+
|
|
68
|
+
import styles from "./button.module.css";
|
|
69
|
+
|
|
70
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
71
|
+
variant?: "default" | "outline";
|
|
72
|
+
size?: "sm" | "md" | "lg";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function ButtonIsland({
|
|
76
|
+
variant = "default",
|
|
77
|
+
size = "md",
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
80
|
+
}: ButtonProps) {
|
|
81
|
+
const classes = [
|
|
82
|
+
styles.button,
|
|
83
|
+
styles[variant],
|
|
84
|
+
styles[size],
|
|
85
|
+
className,
|
|
86
|
+
].filter(Boolean).join(" ");
|
|
87
|
+
|
|
88
|
+
return <button className={classes} {...props} />;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## CSS Variables 활용
|
|
93
|
+
|
|
94
|
+
```css
|
|
95
|
+
/* app/globals.css */
|
|
96
|
+
:root {
|
|
97
|
+
--mandu-primary: #3b82f6;
|
|
98
|
+
--mandu-primary-dark: #2563eb;
|
|
99
|
+
--mandu-primary-light: rgba(59, 130, 246, 0.1);
|
|
100
|
+
--mandu-secondary: #64748b;
|
|
101
|
+
--mandu-radius: 0.5rem;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.dark {
|
|
105
|
+
--mandu-primary: #60a5fa;
|
|
106
|
+
--mandu-primary-dark: #3b82f6;
|
|
107
|
+
--mandu-background: #0f172a;
|
|
108
|
+
--mandu-foreground: #f8fafc;
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## clsx 조합
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
bun add clsx
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import clsx from "clsx";
|
|
120
|
+
import styles from "./card.module.css";
|
|
121
|
+
|
|
122
|
+
export function CardIsland({ highlighted }: { highlighted?: boolean }) {
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
className={clsx(styles.card, {
|
|
126
|
+
[styles.highlighted]: highlighted,
|
|
127
|
+
})}
|
|
128
|
+
>
|
|
129
|
+
Content
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## TypeScript 타입 정의
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// types/css.d.ts
|
|
139
|
+
declare module "*.module.css" {
|
|
140
|
+
const classes: { [key: string]: string };
|
|
141
|
+
export default classes;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 장단점
|
|
146
|
+
|
|
147
|
+
**장점:**
|
|
148
|
+
- 의존성 없음 (Bun 네이티브)
|
|
149
|
+
- 스코프 자동 격리
|
|
150
|
+
- 작은 번들 크기
|
|
151
|
+
|
|
152
|
+
**단점:**
|
|
153
|
+
- 유틸리티 클래스 없음
|
|
154
|
+
- 반복 코드 발생 가능
|
|
155
|
+
- Tailwind 생태계 미호환
|
|
156
|
+
|
|
157
|
+
**추천 사용 케이스:**
|
|
158
|
+
- 최소 의존성 프로젝트
|
|
159
|
+
- 레거시 CSS 마이그레이션
|
|
160
|
+
- 특정 컴포넌트 격리
|
|
161
|
+
|
|
162
|
+
Reference: [Bun CSS Support](https://bun.sh/docs/bundler/loaders#css)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Panda CSS Setup
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Type-safe alternative CSS framework
|
|
5
|
+
tags: styling, panda, setup, type-safe
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Panda CSS Setup
|
|
9
|
+
|
|
10
|
+
**Impact: CRITICAL (Type-safe alternative CSS framework)**
|
|
11
|
+
|
|
12
|
+
Type-safe CSS-in-JS가 필요할 때 Panda CSS를 사용하세요. Zero-runtime으로 Island와 완벽 호환됩니다.
|
|
13
|
+
|
|
14
|
+
**설치:**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun add -d @pandacss/dev
|
|
18
|
+
bunx panda init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**panda.config.ts:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { defineConfig } from "@pandacss/dev";
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
preflight: true,
|
|
28
|
+
include: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
|
|
29
|
+
exclude: [],
|
|
30
|
+
|
|
31
|
+
theme: {
|
|
32
|
+
extend: {
|
|
33
|
+
tokens: {
|
|
34
|
+
colors: {
|
|
35
|
+
mandu: {
|
|
36
|
+
primary: { value: "#3b82f6" },
|
|
37
|
+
secondary: { value: "#64748b" },
|
|
38
|
+
accent: { value: "#f59e0b" },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
radii: {
|
|
42
|
+
mandu: { value: "0.5rem" },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
semanticTokens: {
|
|
46
|
+
colors: {
|
|
47
|
+
background: {
|
|
48
|
+
value: { base: "{colors.white}", _dark: "{colors.zinc.900}" },
|
|
49
|
+
},
|
|
50
|
+
foreground: {
|
|
51
|
+
value: { base: "{colors.zinc.900}", _dark: "{colors.white}" },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
outdir: "styled-system",
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**package.json 스크립트:**
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"scripts": {
|
|
67
|
+
"prepare": "panda codegen",
|
|
68
|
+
"dev": "panda --watch & bun run serve"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 사용 예시
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// app/button/client.tsx
|
|
77
|
+
"use client";
|
|
78
|
+
|
|
79
|
+
import { css, cva } from "@/styled-system/css";
|
|
80
|
+
|
|
81
|
+
const button = cva({
|
|
82
|
+
base: {
|
|
83
|
+
display: "inline-flex",
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
justifyContent: "center",
|
|
86
|
+
borderRadius: "mandu",
|
|
87
|
+
fontWeight: "medium",
|
|
88
|
+
transition: "colors",
|
|
89
|
+
cursor: "pointer",
|
|
90
|
+
},
|
|
91
|
+
variants: {
|
|
92
|
+
variant: {
|
|
93
|
+
default: {
|
|
94
|
+
bg: "mandu.primary",
|
|
95
|
+
color: "white",
|
|
96
|
+
_hover: { bg: "mandu.primary/90" },
|
|
97
|
+
},
|
|
98
|
+
outline: {
|
|
99
|
+
border: "1px solid",
|
|
100
|
+
borderColor: "mandu.primary",
|
|
101
|
+
color: "mandu.primary",
|
|
102
|
+
_hover: { bg: "mandu.primary/10" },
|
|
103
|
+
},
|
|
104
|
+
ghost: {
|
|
105
|
+
_hover: { bg: "mandu.secondary/20" },
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
size: {
|
|
109
|
+
sm: { h: "8", px: "3", fontSize: "sm" },
|
|
110
|
+
md: { h: "10", px: "4" },
|
|
111
|
+
lg: { h: "12", px: "6", fontSize: "lg" },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
defaultVariants: {
|
|
115
|
+
variant: "default",
|
|
116
|
+
size: "md",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
121
|
+
variant?: "default" | "outline" | "ghost";
|
|
122
|
+
size?: "sm" | "md" | "lg";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function ButtonIsland({ variant, size, className, ...props }: ButtonProps) {
|
|
126
|
+
return (
|
|
127
|
+
<button className={button({ variant, size })} {...props} />
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Inline Styles
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import { css } from "@/styled-system/css";
|
|
136
|
+
|
|
137
|
+
export function CardIsland() {
|
|
138
|
+
return (
|
|
139
|
+
<div
|
|
140
|
+
className={css({
|
|
141
|
+
p: "4",
|
|
142
|
+
rounded: "mandu",
|
|
143
|
+
bg: "background",
|
|
144
|
+
shadow: "md",
|
|
145
|
+
_hover: { shadow: "lg" },
|
|
146
|
+
})}
|
|
147
|
+
>
|
|
148
|
+
Content
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Tailwind와의 비교
|
|
155
|
+
|
|
156
|
+
| 기능 | Tailwind | Panda |
|
|
157
|
+
|------|----------|-------|
|
|
158
|
+
| Type-safe | ❌ | ✅ |
|
|
159
|
+
| Zero-runtime | ✅ | ✅ |
|
|
160
|
+
| 생태계 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
|
161
|
+
| DX | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
|
162
|
+
| 학습 곡선 | 낮음 | 중간 |
|
|
163
|
+
|
|
164
|
+
Reference: [Panda CSS Documentation](https://panda-css.com/docs)
|