tailwind-styled-v4 5.0.35 → 5.0.36

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 CHANGED
@@ -2,37 +2,34 @@
2
2
 
3
3
  # tailwind-styled-v4
4
4
 
5
- ### ⚡ Rust-powered Tailwind CSS v4 untuk React
6
- **Build-time compiler · Zero runtime overhead · RSC-aware · Next.js / Vite / Rspack**
5
+ ### ⚡ Rust-powered CSS-in-JS untuk React
6
+ **Build-time compiler · Zero runtime overhead · Type-safe variants · RSC-ready**
7
7
 
8
8
  [![npm](https://img.shields.io/npm/v/tailwind-styled-v4?color=blue)](https://npmjs.com/package/tailwind-styled-v4)
9
9
  [![license](https://img.shields.io/npm/l/tailwind-styled-v4)](LICENSE)
10
10
  [![Rust](https://img.shields.io/badge/Rust-1.75+-orange?logo=rust)](https://rust-lang.org)
11
11
  [![Node](https://img.shields.io/badge/Node.js-20+-green?logo=node.js)](https://nodejs.org)
12
- [![test](https://img.shields.io/badge/tests-84%2F86%20passing-brightgreen)](#)
12
+ [![tests](https://img.shields.io/badge/tests-545%2B%20passing-brightgreen)](#)
13
13
  [![bundle](https://img.shields.io/badge/runtime-~4.5kb-green)](https://bundlephobia.com/package/tailwind-styled-v4)
14
14
 
15
15
  </div>
16
16
 
17
17
  ---
18
18
 
19
- ## Apa ini?
19
+ `tailwind-styled-v4` adalah library styling React yang menggabungkan **DX styled-components** dengan **performa Tailwind CSS v4** — dikompilasi oleh engine Rust. Tulis komponen sekali dengan `tw.button({ variants })`, Rust extract dan optimasi seluruh CSS di build time.
20
20
 
21
- `tailwind-styled-v4` adalah library styling untuk React yang menggabungkan **DX styled-components** dengan **performa Tailwind CSS v4** dan **engine berbasis Rust**. Tulis komponen dengan `tw.button` atau `tw.div({ variants })` — compiler extract dan optimasi CSS di build time, bukan runtime.
21
+ **Perbandingan:**
22
22
 
23
- **Perbandingan singkat:**
24
-
25
- | | tailwind-styled-v4 | styled-components | Tailwind CSS biasa |
26
- |---|---|---|---|
27
- | Build-time CSS | ✅ | (runtime inject) | ✅ |
28
- | Runtime overhead | ~0 | ~15KB | ~0 |
29
- | Variants API | ✅ type-safe | terbatas | |
30
- | SSR/RSC support | ✅ zero config | ⚠️ butuh ServerStyleSheet | ✅ manual |
31
- | Hydration mismatch | tidak ada | ⚠️ hash bisa beda | tidak ada |
32
- | DevTools readable | ✅ class name jelas | hash (`sc-abc123`) | ✅ |
33
- | Engine | 🦀 Rust | JS | JS |
34
- | Dark mode | ✅ `dark:` prefix | manual | ✅ |
35
- | TypeScript | ✅ full inference | partial | ✅ |
23
+ | | tailwind-styled-v4 | styled-components | Tailwind biasa | Panda CSS |
24
+ |---|---|---|---|---|
25
+ | Build-time CSS | | runtime inject | | |
26
+ | Runtime JS | ~0 | ~15KB | ~0 | ~0 |
27
+ | Variants API | ✅ type-safe | terbatas | | ✅ |
28
+ | SSR / RSC | zero config | ⚠️ ServerStyleSheet | manual | ✅ |
29
+ | Hydration mismatch | ✅ tidak ada | ⚠️ hash drift | | ✅ |
30
+ | DevTools readable | ✅ | `sc-abc123` | | ✅ |
31
+ | Engine | 🦀 Rust | JS | JS | JS |
32
+ | TypeScript | ✅ full inference | partial | | ✅ |
36
33
 
37
34
  ---
38
35
 
@@ -40,24 +37,19 @@
40
37
 
41
38
  ```bash
42
39
  npm install tailwind-styled-v4
43
-
44
- # Setup otomatis
45
40
  npx tw setup
46
41
  ```
47
42
 
48
- `npx tw setup` akan otomatis:
49
- - Mendeteksi bundler (Next.js / Vite / Rspack)
50
- - Meng-inject plugin ke `next.config.ts` / `vite.config.ts`
51
- - Membuat `tailwind-styled.config.json` dengan CSS entry yang terdeteksi otomatis
52
- - Menambahkan `@import "tailwindcss"` ke CSS entry
53
- - Pre-warming scanner cache supaya dev pertama tidak cache miss
43
+ `npx tw setup` mendeteksi bundler (Next.js / Vite / Rspack), meng-inject plugin ke config, dan membuat `tailwind-styled.config.json` secara otomatis.
54
44
 
55
45
  ---
56
46
 
57
- ## Quick Start
47
+ ## API
58
48
 
59
49
  ### 1. Template Literal
60
50
 
51
+ API paling sederhana — satu tag, satu string kelas.
52
+
61
53
  ```tsx
62
54
  import { tw } from "tailwind-styled-v4"
63
55
 
@@ -70,234 +62,427 @@ const Button = tw.button`
70
62
  <Button onClick={handleClick}>Klik saya</Button>
71
63
  ```
72
64
 
73
- ### 2. Object Config + Variants
65
+ ---
66
+
67
+ ### 2. Object Config *(direkomendasikan)*
68
+
69
+ API utama — mendukung `variants`, `states`, `sub`, `compoundVariants`, `container`, dan lebih. Semua di-resolve Rust di build time.
74
70
 
75
71
  ```tsx
76
72
  const Button = tw.button({
77
- base: "inline-flex items-center rounded-full px-5 py-2 font-medium transition-all",
73
+ base: "inline-flex items-center rounded-lg font-medium transition-all",
78
74
  variants: {
79
75
  intent: {
80
- primary: "bg-foreground text-background hover:bg-[#383838]",
81
- secondary: "bg-white text-gray-900 border border-gray-300 hover:bg-gray-50",
82
- outline: "bg-transparent border-2 border-foreground text-foreground hover:bg-foreground hover:text-background",
83
- ghost: "bg-transparent text-foreground hover:bg-gray-100",
76
+ primary: "bg-indigo-600 text-white hover:bg-indigo-700",
77
+ secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300",
78
+ danger: "bg-red-600 text-white hover:bg-red-700",
79
+ ghost: "text-gray-600 hover:bg-gray-100",
84
80
  },
85
81
  size: {
86
- sm: "h-10 px-4 text-sm rounded-lg",
87
- md: "h-12 px-5 text-base rounded-full",
88
- lg: "h-14 px-6 text-lg rounded-full",
82
+ sm: "px-3 py-1.5 text-xs",
83
+ md: "px-4 py-2 text-sm",
84
+ lg: "px-5 py-2.5 text-base",
89
85
  },
90
86
  },
91
87
  defaultVariants: { intent: "primary", size: "md" },
92
- states: {
93
- loading: "opacity-60 cursor-wait pointer-events-none",
94
- disabled: "opacity-50 cursor-not-allowed",
95
- fullWidth: "w-full",
96
- },
88
+ compoundVariants: [
89
+ // intent=primary + size=lg → tambah shadow
90
+ { intent: "primary", size: "lg", class: "shadow-md shadow-indigo-200" },
91
+ ],
97
92
  })
98
93
 
99
- // TypeScript tahu variant apa yang valid — autocomplete
94
+ // TypeScript tahu props yang valid — autocomplete penuh
100
95
  <Button intent="primary" size="lg">Submit</Button>
101
- <Button intent="ghost">Batal</Button>
102
- <Button intent="outline" size="sm">Edit</Button>
103
- <Button loading>Memproses...</Button>
96
+ <Button intent="danger">Hapus</Button>
97
+ <Button intent="invalid" /> // ❌ Type error
104
98
  ```
105
99
 
106
- ### 3. Sub-components
100
+ ---
101
+
102
+ ### 3. Sub-Components
103
+
104
+ Definisi slot anak langsung di config. Format `"tag:name"` untuk kontrol tag HTML — penting untuk SEO dan aksesibilitas.
107
105
 
108
106
  ```tsx
109
- const Card = tw.div({
110
- base: "rounded-xl bg-white shadow-md overflow-hidden",
107
+ const Card = tw.article({
108
+ base: "rounded-2xl border border-gray-200 bg-white shadow-sm overflow-hidden",
111
109
  sub: {
112
- header: "px-6 py-4 border-b font-semibold",
113
- main: "px-6 py-4",
114
- footer: "px-6 py-4 border-t text-sm text-gray-400",
115
- "div:action": "px-6 py-4 flex gap-3", // render <div>, akses Card.action
116
- },
117
- states: {
118
- selected: "ring-2 ring-blue-500",
119
- disabled: "opacity-50 pointer-events-none",
110
+ // "tag:name" render tag HTML, akses via Card.name
111
+ "header:header": "px-6 pt-5 pb-0 flex items-start justify-between",
112
+ "h2:title": "text-base font-semibold text-gray-900",
113
+ "section:body": "px-6 py-4 text-sm text-gray-500 leading-relaxed",
114
+ "footer:footer": "px-6 pb-5 pt-0 flex items-center gap-2",
115
+ "img:image": "w-full aspect-video object-cover",
116
+ // tanpa tag → render <span> (default)
117
+ badge: "rounded-full px-2.5 py-0.5 text-xs font-semibold bg-indigo-100 text-indigo-700",
120
118
  },
121
119
  })
122
120
 
123
121
  // Penggunaan
124
- <Card selected>
125
- <Card.header>Judul Card</Card.header>
126
- <Card.main>Konten card di sini.</Card.main>
127
- <Card.action>
128
- <Button>Lihat Detail</Button>
129
- <Button intent="ghost">Batal</Button>
130
- </Card.action>
131
- <Card.footer>Updated 2 hours ago</Card.footer>
122
+ <Card>
123
+ <Card.header>
124
+ <Card.title>Judul Card</Card.title>
125
+ <Card.badge>New</Card.badge>
126
+ </Card.header>
127
+ <Card.body>Konten card di sini.</Card.body>
128
+ <Card.footer>
129
+ <Button size="sm">Detail</Button>
130
+ </Card.footer>
132
131
  </Card>
133
132
  ```
134
133
 
135
- Format `"tag:name"` untuk sub-components misalnya `"div:action"` render sebagai `<div>` dengan akses via `Card.action`. TypeScript otomatis strip prefix tag dari type inference.
134
+ Tag prefix di-strip otomatis dari TypeScript inference `Card.title` bukan `Card["h2:title"]`.
135
+
136
+ ---
137
+
138
+ ### 4. `cv()` — Class Variant Function
136
139
 
137
- ### 4. `.extend()`Inheritance
140
+ Untuk styling non-komponen (className string) — berguna di utility functions, dynamic class lists, dll.
138
141
 
139
142
  ```tsx
140
- const PrimaryButton = Button.extend`text-lg px-8`
141
- const DangerButton = Button.extend({
142
- classes: "bg-red-600 hover:bg-red-700",
143
- defaultVariants: { intent: "primary" }
143
+ import { cv } from "tailwind-styled-v4"
144
+
145
+ const badge = cv({
146
+ base: "inline-flex items-center gap-1.5 rounded-full font-medium",
147
+ variants: {
148
+ color: {
149
+ gray: "bg-gray-100 text-gray-700",
150
+ blue: "bg-blue-100 text-blue-700",
151
+ green: "bg-green-100 text-green-700",
152
+ red: "bg-red-100 text-red-700",
153
+ },
154
+ size: {
155
+ sm: "px-2 py-0.5 text-[10px]",
156
+ md: "px-2.5 py-0.5 text-xs",
157
+ lg: "px-3 py-1 text-sm",
158
+ },
159
+ },
160
+ defaultVariants: { color: "gray", size: "md" },
144
161
  })
162
+
163
+ // Returns string className, bukan komponen
164
+ <span className={badge({ color: "blue", size: "lg" })}>Active</span>
165
+
166
+ // Merge className tambahan
167
+ <span className={badge({ color: "red", className: "opacity-75" })}>Error</span>
145
168
  ```
146
169
 
147
- ### 5. States — Boolean Props
170
+ ---
171
+
172
+ ### 5. `states` — Boolean Props
173
+
174
+ Boolean props yang di-resolve via Rust bitmask lookup table. Tidak ada string comparison, tidak ada kondisional di render path.
148
175
 
149
176
  ```tsx
150
- // states di-resolve via Rust bitmask lookup — O(1), tidak ada runtime overhead
151
- const Badge = tw.span({
152
- base: "inline-flex px-2 py-1 rounded text-sm font-medium",
177
+ const Button = tw.button({
178
+ base: "inline-flex items-center px-4 py-2 rounded-lg font-medium",
179
+ variants: {
180
+ intent: { primary: "bg-indigo-600 text-white", ghost: "text-gray-600" },
181
+ },
182
+ defaultVariants: { intent: "primary" },
153
183
  states: {
154
- active: "bg-green-100 text-green-800",
155
- warning: "bg-yellow-100 text-yellow-800",
156
- error: "bg-red-100 text-red-800",
184
+ loading: "opacity-60 cursor-wait pointer-events-none",
185
+ fullWidth: "w-full",
186
+ disabled: "opacity-50 cursor-not-allowed",
157
187
  },
158
188
  })
159
189
 
160
- <Badge active>Online</Badge>
161
- <Badge error>Error</Badge>
190
+ // Boolean props langsung — tidak perlu className kondisional
191
+ <Button loading>Memproses...</Button>
192
+ <Button fullWidth>Submit</Button>
193
+ <Button loading fullWidth>Loading full width</Button>
162
194
  ```
163
195
 
164
- ### 6. Dark Mode
196
+ Maksimal 16 states per komponen (2¹⁶ kombinasi pre-generated di build time).
197
+
198
+ ---
199
+
200
+ ### 6. `state` — CSS Data-Attribute (Zero JS State)
165
201
 
166
- Dark mode bekerja otomatis via `prefers-color-scheme`tidak perlu konfigurasi tambahan:
202
+ Untuk toggle style tanpa React re-rendercocok untuk animasi dan transisi.
203
+
204
+ ```tsx
205
+ const Dropdown = tw.div({
206
+ base: "overflow-hidden transition-all duration-200",
207
+ state: {
208
+ open: {
209
+ true: "max-h-96 opacity-100",
210
+ false: "max-h-0 opacity-0",
211
+ },
212
+ },
213
+ })
214
+
215
+ // Set data attribute langsung — tidak butuh setState
216
+ dropdownRef.current?.setAttribute("data-open", "true")
217
+
218
+ // Atau via React state
219
+ <Dropdown data-open={isOpen.toString()}>
220
+ {children}
221
+ </Dropdown>
222
+ ```
223
+
224
+ ---
225
+
226
+ ### 7. `.extend()` — Inheritance
227
+
228
+ Extend komponen yang sudah ada tanpa duplikasi class.
229
+
230
+ ```tsx
231
+ // Template literal extend
232
+ const PrimaryButton = Button.extend`
233
+ bg-indigo-600 text-white hover:bg-indigo-700
234
+ `
235
+
236
+ // Object config extend — tambah variant sekaligus
237
+ const BigDangerButton = Button.extend({
238
+ classes: "text-lg px-8 shadow-lg",
239
+ variants: { loading: { true: "animate-pulse" } },
240
+ defaultVariants: { intent: "danger" },
241
+ })
242
+ ```
243
+
244
+ ---
245
+
246
+ ### 8. `container` — Container Queries
247
+
248
+ Responsive berdasarkan ukuran container parent, bukan viewport.
167
249
 
168
250
  ```tsx
169
251
  const Card = tw.div({
170
- base: "bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100",
171
- sub: {
172
- header: "border-b border-gray-200 dark:border-gray-700",
252
+ base: "p-4 flex flex-col",
253
+ container: {
254
+ sm: "flex-col", // @container (min-width: 320px)
255
+ md: "flex-row", // @container (min-width: 640px)
256
+ lg: "grid-cols-3",// @container (min-width: 1024px)
173
257
  },
258
+ containerName: "card", // opsional — named container
174
259
  })
260
+
261
+ // Wrapper wajib punya @container
262
+ const CardWrapper = tw.div`@container`
263
+
264
+ <CardWrapper>
265
+ <Card>{/* responsive berdasarkan lebar CardWrapper */}</Card>
266
+ </CardWrapper>
175
267
  ```
176
268
 
177
- ### 7. Compound Variants
269
+ Breakpoint default: `xs=240px`, `sm=320px`, `md=640px`, `lg=1024px`, `xl=1280px`, `2xl=1536px`.
270
+
271
+ ---
272
+
273
+ ### 9. `server.` — Server Components Only
274
+
275
+ Komponen yang di-enforce hanya boleh render di server. Dev warning otomatis jika render di browser.
178
276
 
179
277
  ```tsx
180
- const Button = tw.button({
181
- base: "...",
278
+ import { server } from "tailwind-styled-v4"
279
+
280
+ // Sama persis API-nya dengan tw — tapi compiler enforce server-only
281
+ const PageHeader = server.header({
282
+ base: "w-full border-b px-6 py-4 bg-white",
283
+ sub: {
284
+ "h1:title": "text-2xl font-bold",
285
+ "p:subtitle": "text-sm text-gray-500",
286
+ },
287
+ })
288
+
289
+ const AvatarRoot = server.div({
290
+ base: "relative inline-flex rounded-full overflow-hidden",
182
291
  variants: {
183
- intent: { primary: "...", outline: "..." },
184
- size: { sm: "...", lg: "..." },
292
+ size: {
293
+ sm: "h-8 w-8",
294
+ md: "h-10 w-10",
295
+ lg: "h-14 w-14",
296
+ },
185
297
  },
186
- compoundVariants: [
187
- // Kalau intent=primary AND size=lg → tambah class ini
188
- { intent: "primary", size: "lg", class: "shadow-lg" },
189
- ],
298
+ defaultVariants: { size: "md" },
190
299
  })
191
300
  ```
192
301
 
193
- ### 8. .withSub — Strict TypeScript untuk Template Literals
302
+ ---
303
+
304
+ ### 10. `createStyledSystem()` — Design System Factory
305
+
306
+ Untuk design system dengan token terpusat. Token di-inject sebagai CSS custom properties `--sys-{group}-{name}`.
194
307
 
195
308
  ```tsx
196
- const Button = tw.button`
197
- flex h-12 px-5 rounded-full
198
- icon { flex h-4 w-4 }
199
- badge { absolute -top-1 -right-1 }
200
- `.withSub<"icon" | "badge">()
201
-
202
- Button.icon // ✅ autocomplete
203
- Button.badge // ✅ autocomplete
204
- Button.xyz // ❌ TypeScript error
309
+ import { createStyledSystem } from "tailwind-styled-v4"
310
+
311
+ const ui = createStyledSystem({
312
+ tokens: {
313
+ colors: {
314
+ primary: "#6366f1",
315
+ danger: "#ef4444",
316
+ muted: "#6b7280",
317
+ },
318
+ radius: {
319
+ base: "0.5rem",
320
+ full: "9999px",
321
+ },
322
+ },
323
+ components: {
324
+ button: {
325
+ tag: "button",
326
+ base: "inline-flex items-center font-medium transition-colors",
327
+ variants: {
328
+ intent: {
329
+ primary: "bg-[var(--sys-colors-primary)] text-white",
330
+ danger: "bg-[var(--sys-colors-danger)] text-white",
331
+ ghost: "bg-transparent text-current hover:bg-black/5",
332
+ },
333
+ size: {
334
+ sm: "h-8 px-3 text-sm",
335
+ md: "h-10 px-4 text-base",
336
+ lg: "h-12 px-6 text-lg",
337
+ },
338
+ },
339
+ defaultVariants: { intent: "primary", size: "md" },
340
+ },
341
+ },
342
+ })
343
+
344
+ // Komponen dari sistem
345
+ const Button = ui.button()
346
+
347
+ // Token reference — "var(--sys-colors-primary)"
348
+ const primaryVar = ui.token("colors.primary")
349
+
350
+ // Update token runtime
351
+ ui.setTokens({ colors: { primary: "#8b5cf6" } })
205
352
  ```
206
353
 
207
354
  ---
208
355
 
209
- ## Bagaimana CSS Di-generate?
356
+ ### 11. `liveToken()` — Live Design Tokens
210
357
 
211
- Pipeline baru di v5 tidak lagi pakai empty rules:
358
+ Token yang bisa diupdate runtime dan subscribe ke perubahannya.
212
359
 
213
- ```
214
- 1. withTailwindStyled (Next.js startup)
215
- └─> scanWorkspace() via Rust scanner
216
- └─> ast_extract_classes() per file
217
- └─> extract semua classes dari variants, states, sub, base
218
-
219
- 2. generateCssForClasses(classes, globals.css)
220
- └─> Tailwind JS compile(globals.css, { loadStylesheet })
221
- └─> Tailwind baca @theme inline user (custom colors, fonts, dll)
222
- └─> Generate real CSS untuk semua classes
223
- └─> LightningCSS post-process (production only)
224
- └─> tulis .next/tw-classes/_initial-scan.css
225
-
226
- 3. globals.css: @source "../.next/tw-classes/**"
227
- └─> Tailwind scan class names dari _initial-scan.css
228
- └─> Generate CSS di bundle akhir
229
- ```
360
+ ```tsx
361
+ import { liveToken, tokenVar, createUseTokens } from "tailwind-styled-v4"
230
362
 
231
- Hasilnya `_initial-scan.css` berisi real CSS (bukan empty rules):
363
+ // Deklarasi token
364
+ const tokens = liveToken({
365
+ primary: "#6366f1",
366
+ surface: "#ffffff",
367
+ text: "#111827",
368
+ })
232
369
 
233
- ```css
234
- /* tw-classes: initial scan — auto-generated by withTailwindStyled */
235
- @layer utilities {
236
- .bg-foreground {
237
- background-color: var(--foreground);
238
- }
239
- .text-foreground {
240
- color: var(--foreground);
241
- }
242
- .hover\:bg-foreground {
243
- &:hover {
244
- background-color: var(--foreground);
245
- }
246
- }
247
- /* ... */
370
+ // CSS variable reference — dipakai di className
371
+ const Card = tw.div({
372
+ base: `
373
+ bg-[${tokenVar(tokens.surface)}]
374
+ text-[${tokenVar(tokens.text)}]
375
+ border-[${tokenVar(tokens.primary)}]
376
+ `,
377
+ })
378
+
379
+ // Hook untuk subscribe token di React
380
+ const useTokens = createUseTokens(tokens)
381
+
382
+ function ThemePanel() {
383
+ const { primary } = useTokens()
384
+ return <div style={{ color: primary }}>Current primary: {primary}</div>
248
385
  }
386
+
387
+ // Update token langsung — semua subscriber re-render
388
+ tokens.primary.set("#8b5cf6")
249
389
  ```
250
390
 
251
- Custom colors dari `@theme inline` di `globals.css` otomatis ter-generate — tidak perlu konfigurasi tambahan.
391
+ ---
392
+
393
+ ### 12. `cn()`, `cx()`, `twMerge`
394
+
395
+ Utility untuk merge dan deduplicate Tailwind classes.
396
+
397
+ ```tsx
398
+ import { cn, cx, twMerge } from "tailwind-styled-v4"
399
+
400
+ // cn — merge dengan dedup (alias twMerge)
401
+ cn("px-4 py-2", isActive && "bg-blue-500", className)
402
+
403
+ // cx — conditional class join (tanpa dedup)
404
+ cx("base-class", { "active-class": isActive, "disabled-class": !enabled })
405
+
406
+ // twMerge — eksplisit Tailwind conflict resolution
407
+ twMerge("px-4 px-8") // → "px-8" (konflik di-resolve, yang terakhir menang)
408
+ ```
252
409
 
253
410
  ---
254
411
 
255
- ## Setup Next.js
412
+ ## Setup
256
413
 
257
- ### next.config.ts
414
+ ### Next.js
258
415
 
416
+ **`next.config.ts`:**
259
417
  ```ts
260
418
  import { withTailwindStyled } from "tailwind-styled-v4/next"
261
419
  import type { NextConfig } from "next"
262
420
 
263
421
  const nextConfig: NextConfig = {}
264
422
 
265
- export default withTailwindStyled({ verbose: true })(nextConfig)
423
+ export default withTailwindStyled({
424
+ // routeCss: true — generate css-manifest.json yang dibutuhkan TwCssInjector.
425
+ // Tanpa ini, TwCssInjector diam-diam return kosong (manifest tidak ada).
426
+ routeCss: true,
427
+ })(nextConfig)
266
428
  ```
267
429
 
268
- ### globals.css
430
+ **`layout.tsx`:**
431
+ ```tsx
432
+ import { TwCssInjector } from "tailwind-styled-v4/runtime-css"
433
+
434
+ export default function RootLayout({ children }) {
435
+ return (
436
+ <html lang="id">
437
+ <head>
438
+ {/*
439
+ * TwCssInjector — opsional tapi direkomendasikan untuk production.
440
+ *
441
+ * Cara kerja:
442
+ * 1. withTailwindStyled({ routeCss: true }) emit css-manifest.json
443
+ * ke .next/static/css/tw/ saat build
444
+ * 2. Per request, TwCssInjector baca manifest di server dan inject CSS
445
+ * route-specific langsung sebagai <style> inline di HTML
446
+ *
447
+ * Tanpa TwCssInjector:
448
+ * CSS tetap jalan via globals.css — semua route dapat satu bundle
449
+ * CSS gabungan yang di-load browser via <link>.
450
+ *
451
+ * Dengan TwCssInjector:
452
+ * Hanya CSS yang dipakai route itu yang di-inline di HTML →
453
+ * tidak ada extra HTTP request, tidak ada FOUC, streaming-friendly.
454
+ *
455
+ * Kalau manifest belum ada (dev cold start), komponen ini
456
+ * diam-diam return kosong — tidak breaking.
457
+ */}
458
+ <TwCssInjector />
459
+ </head>
460
+ <body>{children}</body>
461
+ </html>
462
+ )
463
+ }
464
+ ```
269
465
 
466
+ **`globals.css`:**
270
467
  ```css
271
468
  @import "tailwindcss";
272
- @source "../.next/tw-classes/**";
273
469
 
274
470
  :root {
275
- --background: #ffffff;
276
- --foreground: #171717;
471
+ --background: #f5f7fb;
472
+ --foreground: #111827;
277
473
  }
278
474
 
279
475
  @theme inline {
280
476
  --color-background: var(--background);
281
477
  --color-foreground: var(--foreground);
282
478
  --font-sans: var(--font-geist-sans);
283
- --font-mono: var(--font-geist-mono);
284
- }
285
-
286
- @media (prefers-color-scheme: dark) {
287
- :root {
288
- --background: #0a0a0a;
289
- --foreground: #ededed;
290
- }
291
479
  }
292
480
  ```
293
481
 
294
- ---
295
-
296
- ## Bundler Integration
297
-
298
482
  ### Vite
299
483
 
300
484
  ```ts
485
+ // vite.config.ts
301
486
  import { defineConfig } from "vite"
302
487
  import react from "@vitejs/plugin-react"
303
488
  import { tailwindStyled } from "tailwind-styled-v4/vite"
@@ -310,13 +495,12 @@ export default defineConfig({
310
495
  ### Rspack
311
496
 
312
497
  ```js
313
- import { defineConfig } from "@rspack/cli"
498
+ // rspack.config.js
314
499
  import { tailwindStyled } from "tailwind-styled-v4/rspack"
315
500
 
316
- export default defineConfig({
317
- entry: "./src/index.ts",
501
+ export default {
318
502
  plugins: [tailwindStyled()],
319
- })
503
+ }
320
504
  ```
321
505
 
322
506
  ---
@@ -324,213 +508,149 @@ export default defineConfig({
324
508
  ## CLI
325
509
 
326
510
  ```bash
327
- # Setup otomatis (detect bundler, patch config, pre-warm cache)
328
- npx tw setup
329
-
330
- # Verifikasi setup
331
- npx tw preflight
332
-
333
- # Analisis workspace
334
- npx tw audit
335
-
336
- # Benchmark performa
337
- npx tw benchmark
511
+ npx tw setup # Setup otomatis: detect bundler, patch config, pre-warm cache
512
+ npx tw preflight # Verifikasi setup
513
+ npx tw audit # Analisis workspace — unused classes, missing variants
514
+ npx tw benchmark # Benchmark performa scanner + compiler
338
515
  ```
339
516
 
340
517
  ---
341
518
 
342
- ## Kenapa Bukan styled-components?
343
-
344
- styled-components inject `<style>` tag ke DOM saat runtime — setiap component punya hash class (`sc-abc123 dEfGhI`) yang di-generate di browser. Masalahnya:
345
-
346
- - **Runtime overhead** — ~15KB JS untuk generate + inject CSS
347
- - **SSR mismatch** — hash bisa berbeda antara server dan client → hydration warning
348
- - **DevTools susah dibaca** — `sc-abc123` tidak informatif
349
- - **Butuh setup khusus** — `ServerStyleSheet`, `StyledEngineProvider`, dll untuk Next.js App Router
350
-
351
- `tailwind-styled-v4` tidak punya masalah ini karena CSS sudah di-bundle sebelum browser buka halaman. Class name readable, SSR dan CSR identik, tidak ada runtime overhead.
352
-
353
- ---
354
-
355
- ## Benchmark
356
-
357
- Diukur di Node.js 22, Rust 1.75.
358
-
359
- | Operasi | tailwind-styled-v4 | Tailwind CSS (JS) | Speedup |
360
- |---|---|---|---|
361
- | Scan 1000 file | **0.8 ms** | ~340 ms | **~425×** |
362
- | Compile 500 class | **0.02 ms** | ~1.2 ms | **~60×** |
363
- | Parse class string | **0.010 ms** | ~0.8 ms | **~80×** |
364
- | Cache read/write | **0.009 ms** | ~0.5 ms | **~55×** |
365
- | Watch mode rebuild | **< 5 ms** | ~85 ms | **~17×** |
366
-
367
- ---
368
-
369
- ## Arsitektur
519
+ ## DevTools
370
520
 
371
- ```
372
- tailwind-styled-v4/
373
- ├── native/ # Rust engine (NAPI-RS)
374
- │ ├── src/application/
375
- │ │ ├── ast_extract.rs # Extract Tailwind classes dari source files
376
- │ │ ├── variant_resolver.rs # Variant resolution with precedence
377
- │ │ ├── variant_system.rs # Variant composition system
378
- │ │ └── theme_resolver_pool.rs # Multi-tier caching
379
- │ ├── src/domain/
380
- │ │ ├── variants.rs # Variant resolution (props override defaults)
381
- │ │ ├── variant_precedence.rs # Precedence calculation
382
- │ │ └── transform.rs # Transform object config → JS component
383
- │ └── src/infrastructure/
384
- │ ├── napi_bridge_*.rs # Modularized NAPI bridges
385
- │ └── cache_*.rs # Multi-tier cache backends
386
-
387
- ├── packages/
388
- │ ├── domain/
389
- │ │ ├── core/ # tw, cx, cv, cn — core API + createComponent
390
- │ │ ├── compiler/ # Tailwind JS + LightningCSS pipeline
391
- │ │ └── scanner/ # File scanner (Rust-backed)
392
- │ ├── presentation/
393
- │ │ ├── next/ # Next.js plugin (withTailwindStyled)
394
- │ │ ├── vite/ # Vite plugin
395
- │ │ └── rspack/ # Rspack plugin
396
- │ └── infrastructure/
397
- │ └── cli/ # CLI (tw setup, tw audit, dll)
398
-
399
- ├── config/ # Configuration files (centralized)
400
- │ ├── biome.json
401
- │ ├── tsconfig.base.json
402
- │ ├── turbo.json
403
- │ └── ...
404
-
405
- ├── docs/
406
- │ ├── archive/ # Phase docs, session summaries, reference
407
- │ ├── phase-4/, phase-5/, phase-6/ # Phase-specific documentation
408
- │ └── api/ # API reference
521
+ ```tsx
522
+ // Tambahkan ke layout untuk inspeksi komponen di browser
523
+ import { TwDevTools } from "tailwind-styled-v4/devtools"
524
+
525
+ // Atau pakai dynamic import untuk Next.js (ssr: false wajib)
526
+ import dynamic from "next/dynamic"
527
+ const DevTools = dynamic(
528
+ () => import("tailwind-styled-v4/devtools").then(m => ({ default: m.TwDevTools })),
529
+ { ssr: false }
530
+ )
409
531
  ```
410
532
 
411
- **New Structure (Phase 7):**
412
- - Configuration files centralized in `config/` directory
413
- - Documentation archived in `docs/archive/` for cleaner root
414
- - Modularized NAPI bridges (`napi_bridge_*.rs`) for better maintainability
415
- - Phase-specific docs in dedicated directories
533
+ DevTools menampilkan: daftar komponen terdaftar, resolved classes per variant, state registry, container registry, dan live token values.
416
534
 
417
535
  ---
418
536
 
419
537
  ## TypeScript
420
538
 
421
- Library ini fully typed — tidak ada `any` di public API:
539
+ Semua API fully typed — tidak ada `any` di public API.
422
540
 
423
541
  ```tsx
424
- // Type inference otomatis dari config
542
+ // Variant type inference otomatis dari config
425
543
  const Button = tw.button({
426
544
  variants: {
427
- intent: { primary: "...", ghost: "...", outline: "..." },
428
- size: { sm: "...", md: "...", lg: "..." },
545
+ intent: { primary: "...", ghost: "...", danger: "..." },
546
+ size: { sm: "...", md: "...", lg: "..." },
429
547
  },
430
548
  defaultVariants: { intent: "primary", size: "md" },
431
549
  })
432
550
 
433
- // TypeScript tahu props yang valid
434
- <Button intent="invalid" /> // Type error
435
- <Button intent="primary" /> // ✅
551
+ type ButtonProps = React.ComponentProps<typeof Button>
552
+ // → { intent?: "primary" | "ghost" | "danger", size?: "sm" | "md" | "lg", ... }
436
553
 
437
- // Sub-componentsExtractSubName type inference
438
- const Card = tw.div({
554
+ // Sub-component inference tag prefix otomatis di-strip
555
+ const Card = tw.article({
439
556
  sub: {
440
- header: "font-bold",
441
- "div:action": "flex gap-3", // → Card.action (tag prefix di-strip otomatis)
557
+ "header:header": "...", // → Card.header (renders <header>)
558
+ "h2:title": "...", // → Card.title (renders <h2>)
559
+ "section:body": "...", // → Card.body (renders <section>)
560
+ badge: "...", // → Card.badge (renders <span>)
442
561
  },
443
562
  })
444
563
 
445
- Card.action // ✅ autocomplete
564
+ Card.header // ✅ autocomplete
565
+ Card.title // ✅
446
566
  Card.xyz // ❌ TypeScript error
567
+
568
+ // .withSub<>() untuk template literal — strict mode manual
569
+ const Nav = tw.nav`
570
+ flex items-center gap-4
571
+ `.withSub<"logo" | "links" | "actions">()
572
+
573
+ Nav.logo // ✅
574
+ Nav.unknown // ❌ TypeScript error
447
575
  ```
448
576
 
449
577
  ---
450
578
 
579
+ ## Benchmark
580
+
581
+ Diukur di Node.js 22, Rust 1.75, M1 MacBook Pro.
582
+
583
+ | Operasi | tailwind-styled-v4 | Tailwind CSS (JS) | Speedup |
584
+ |---|---|---|---|
585
+ | Scan 1000 file | **0.8 ms** | ~340 ms | **~425×** |
586
+ | Compile 500 class | **0.02 ms** | ~1.2 ms | **~60×** |
587
+ | Parse class string | **0.010 ms** | ~0.8 ms | **~80×** |
588
+ | Cache read/write | **0.009 ms** | ~0.5 ms | **~55×** |
589
+ | Watch mode rebuild | **< 5 ms** | ~85 ms | **~17×** |
590
+
591
+ ---
592
+
451
593
  ## Environment Variables
452
594
 
453
595
  | Variable | Default | Deskripsi |
454
596
  |---|---|---|
455
597
  | `TWS_LOG_LEVEL` | `info` | `debug\|info\|warn\|error\|silent` |
456
598
  | `TWS_DEBUG_SCANNER` | `0` | `1` = aktifkan scanner debug logs |
457
- | `STUDIO_PORT` | `3030` | Port studio server |
599
+ | `TWS_NO_NATIVE` | — | `1` = disable native module (fallback JS) |
600
+ | `TWS_NO_RUST` | — | `1` = disable Rust, gunakan JS fallback |
458
601
 
459
602
  ---
460
603
 
461
- ## Architecture Updates (Phase 7)
462
-
463
- ### R1-R6: Parser Consolidation through Resolver Caching ✅ Completed 2026-06-12
464
-
465
- Recent Phase 7 updates span multiple refactoring rounds:
466
-
467
- **R1: Parser Consolidation** ✅
468
- - Single unified production parser (v2-based)
469
- - ~5% binary size reduction
470
- - 100% backward compatible, all 545+ tests passing
471
-
472
- **R2-R3: Infrastructure Modularization** ✅
473
- - NAPI bridge modularized into specialized modules
474
- - Comprehensive integration tests covering all layers
475
- - Cache backend infrastructure refactored
476
-
477
- **R4: Property Testing Framework** ✅
478
- - 6 core properties verified across 53 test cases
479
- - Parser determinism property testing
480
- - Round-trip parsing validation
481
- - Cache consistency & eviction properties
482
-
483
- **R5: Variant Precedence System** ✅
484
- - Native variant resolution with precedence handling
485
- - Compound variant support
486
- - Theme-aware variant composition
604
+ ## Arsitektur
487
605
 
488
- **R6: Resolver Caching** ✅
489
- - Multi-tier caching for theme resolver
490
- - Performance optimization verified
606
+ ```
607
+ tailwind-styled-v4/
608
+
609
+ ├── native/ # 🦀 Rust engine (NAPI-RS)
610
+ │ ├── src/domain/ # Core logic: variants, CSS generation, theme
611
+ │ ├── src/application/ # Parser, scanner, resolver, variant system
612
+ │ └── src/infrastructure/ # 11 NAPI bridge modules, cache backends
613
+
614
+ ├── packages/
615
+ │ ├── domain/
616
+ │ │ ├── core/ # tw, cv, cn, cx — core API
617
+ │ │ ├── compiler/ # Tailwind v4 + LightningCSS pipeline
618
+ │ │ ├── scanner/ # File scanner (Rust-backed, ~425× faster)
619
+ │ │ ├── theme/ # Theme token resolution
620
+ │ │ ├── shared/ # Types, utilities, generated schemas
621
+ │ │ └── runtime-css/ # Browser-safe CSS runtime (batched inject)
622
+ │ │
623
+ │ ├── presentation/
624
+ │ │ ├── next/ # Next.js plugin (withTailwindStyled)
625
+ │ │ ├── vite/ # Vite plugin
626
+ │ │ └── rspack/ # Rspack plugin
627
+ │ │
628
+ │ └── infrastructure/
629
+ │ └── cli/ # CLI (tw setup, audit, benchmark)
630
+
631
+ └── examples/
632
+ └── next-js-app/ # Demo app: Next.js 16 + React 19
633
+ ```
491
634
 
492
- For architecture details and improvements roadmap, see:
493
- - [Phase 7 Architecture Design](.kiro/specs/phase-7-architecture/design.md)
494
- - [R4 Property Tests Design](.kiro/specs/phase-7-architecture/R4_PROPERTY_TESTS_DESIGN.md)
495
- - [R5 Variant Precedence Design](.kiro/specs/phase-7-architecture/R5_VARIANT_PRECEDENCE_DESIGN.md)
496
- - [Full spec directory](.kiro/specs/phase-7-architecture/)
635
+ **NAPI Bridge Modules (11 modul terpisah):**
636
+
637
+ | Module | Fungsi |
638
+ |---|---|
639
+ | `napi_bridge_parsing.rs` | Class parsing (6 fungsi) |
640
+ | `napi_bridge_css.rs` | CSS generation (7 fungsi) |
641
+ | `napi_bridge_theme.rs` | Theme resolution (7 fungsi) |
642
+ | `napi_bridge_cache.rs` | Cache management (6 fungsi) |
643
+ | `napi_bridge_redis.rs` | Redis distributed cache (17 fungsi) |
644
+ | `napi_bridge_analysis.rs` | Performance metrics (5 fungsi) |
645
+ | `napi_bridge_watch.rs` | File watching (9 fungsi) |
646
+ | `napi_bridge_types.rs` | Type definitions |
647
+ | `napi_bridge_marshalling.rs` | JSON I/O |
648
+ | `napi_bridge_errors.rs` | Error handling |
649
+ | `napi_bridge.rs` | Facade (re-export semua) |
497
650
 
498
651
  ---
499
652
 
500
- ## Recent Changes (82 commits, not yet pushed)
501
-
502
- ### Infrastructure & Organization 🏗️
503
- - **Config centralization** — moved 6 root config files to `config/` directory
504
- - **Docs reorganization** — 42 summary/reference files moved to `docs/archive/`
505
- - **Benchmarking suite** — added comprehensive performance benchmarking (`native/benches/`)
506
-
507
- ### Rust Engine Enhancements 🦀
508
- - **Modularized NAPI bridges** — split monolithic bridge into specialized modules:
509
- - `napi_bridge_cache.rs` — caching layer
510
- - `napi_bridge_redis.rs` — distributed cache
511
- - `napi_bridge_theme.rs` — theme resolution
512
- - `napi_bridge_variants.rs` — variant composition
513
- - And 5 more specialized modules
514
- - **Theme resolver pool** — multi-tier caching with adaptive strategies
515
- - **Variant system improvements** — precedence calculation, compound variant support
516
-
517
- ### Testing & Validation ✅
518
- - **Property testing** — 6 core properties across 53 test cases
519
- - **Integration tests** — comprehensive NAPI module tests (1000+ tests added)
520
- - **Variant precedence tests** — 493+ test cases for variant resolution
521
- - **Performance benchmarks** — week8, week9 scale testing suites
522
-
523
- ### TypeScript/JS Improvements
524
- - **Native bridge refactor** — simplified async/sync patterns, improved error handling
525
- - **Cache integration** — unified cache interface across Redis, LRU, and file-based backends
526
- - **Stream support** — added streaming CSS compilation
527
-
528
- ### Documentation 📚
529
- - **Phase 7 specs** — comprehensive design docs in `.kiro/specs/phase-7-architecture/`
530
- - **NAPI module guide** — `MIGRATION_GUIDE_PHASE_7_3.md`
531
- - **Architecture docs** — modular bridge patterns and integration guide
532
-
533
- ---
653
+ ## Development
534
654
 
535
655
  ```bash
536
656
  git clone https://github.com/Dictionar32/tailwind-styled-v4.git
@@ -538,39 +658,38 @@ cd tailwind-styled-v4
538
658
 
539
659
  npm install
540
660
 
541
- # Build Rust binary + semua packages
542
- npm run build
543
-
544
- # Build Rust only
661
+ # Build Rust binary dulu, baru packages
545
662
  npm run build:rust
663
+ npm run build:packages
664
+
665
+ # Full build
666
+ npm run build
546
667
 
547
668
  # Test
548
- npm run test
669
+ npm run test:all
549
670
 
550
- # Dev mode
671
+ # Dev mode (watch)
551
672
  npm run dev
552
673
 
553
674
  # Benchmark
554
675
  npm run bench
555
676
  ```
556
677
 
557
- **Requirements:**
558
- - Node.js 20+
559
- - Rust 1.75+ (untuk build dari source)
678
+ **Requirements:** Node.js 20+, Rust 1.75+ (untuk build dari source)
560
679
 
561
680
  ---
562
681
 
563
682
  ## Contributing
564
683
 
565
- PR dan issue sangat welcome!
684
+ PR dan issue sangat welcome. Prioritas saat ini:
566
685
 
567
- Prioritas saat ini:
568
- - [ ] macOS & Windows pre-built binary
686
+ - [ ] Pre-built binary untuk macOS arm64, x64, Linux, Windows
569
687
  - [ ] Docs website (VitePress)
570
- - [ ] More bundler adapters
688
+ - [ ] Vue & Svelte adapter yang lebih matang
689
+ - [ ] Plugin API public docs
571
690
 
572
691
  ---
573
692
 
574
693
  ## License
575
694
 
576
- [MIT](LICENSE) © Dictionar32
695
+ [MIT](LICENSE) © Dictionar32