tailwind-styled-v4 5.0.10 → 5.0.11
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 +245 -373
- package/dist/analyzer.js +75 -22
- package/dist/analyzer.js.map +1 -1
- package/dist/analyzer.mjs +74 -21
- package/dist/analyzer.mjs.map +1 -1
- package/dist/animate.js +4 -2
- package/dist/animate.js.map +1 -1
- package/dist/animate.mjs +4 -2
- package/dist/animate.mjs.map +1 -1
- package/dist/atomic.js +20 -5
- package/dist/atomic.js.map +1 -1
- package/dist/atomic.mjs +20 -5
- package/dist/atomic.mjs.map +1 -1
- package/dist/cli.js +174 -67
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +171 -64
- package/dist/cli.mjs.map +1 -1
- package/dist/compiler.d.mts +7 -1
- package/dist/compiler.d.ts +7 -1
- package/dist/compiler.js +53 -27
- package/dist/compiler.js.map +1 -1
- package/dist/compiler.mjs +53 -27
- package/dist/compiler.mjs.map +1 -1
- package/dist/devtools.js.map +1 -1
- package/dist/devtools.mjs.map +1 -1
- package/dist/engine.js +159 -61
- package/dist/engine.js.map +1 -1
- package/dist/engine.mjs +159 -61
- package/dist/engine.mjs.map +1 -1
- package/dist/index.browser.mjs +1512 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +94 -12
- package/dist/index.d.ts +94 -12
- package/dist/index.js +436 -106
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +436 -106
- package/dist/index.mjs.map +1 -1
- package/dist/next.js +1946 -47
- package/dist/next.js.map +1 -1
- package/dist/next.mjs +1929 -44
- package/dist/next.mjs.map +1 -1
- package/dist/plugin-api.js.map +1 -1
- package/dist/plugin-api.mjs.map +1 -1
- package/dist/plugin-registry.js +23 -10
- package/dist/plugin-registry.js.map +1 -1
- package/dist/plugin-registry.mjs +23 -11
- package/dist/plugin-registry.mjs.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rspack.js.map +1 -1
- package/dist/rspack.mjs.map +1 -1
- package/dist/scanner.js +72 -19
- package/dist/scanner.js.map +1 -1
- package/dist/scanner.mjs +71 -18
- package/dist/scanner.mjs.map +1 -1
- package/dist/shared.js +32 -15
- package/dist/shared.js.map +1 -1
- package/dist/shared.mjs +32 -15
- package/dist/shared.mjs.map +1 -1
- package/dist/svelte.js +38 -12
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +38 -12
- package/dist/svelte.mjs.map +1 -1
- package/dist/syntax.js +17 -5
- package/dist/syntax.js.map +1 -1
- package/dist/syntax.mjs +17 -5
- package/dist/syntax.mjs.map +1 -1
- package/dist/theme.js +4 -2
- package/dist/theme.js.map +1 -1
- package/dist/theme.mjs +4 -2
- package/dist/theme.mjs.map +1 -1
- package/dist/turbopackLoader.js +87 -33
- package/dist/turbopackLoader.js.map +1 -1
- package/dist/turbopackLoader.mjs +87 -33
- package/dist/turbopackLoader.mjs.map +1 -1
- package/dist/tw.js +174 -67
- package/dist/tw.js.map +1 -1
- package/dist/tw.mjs +171 -64
- package/dist/tw.mjs.map +1 -1
- package/dist/vite.js +145 -63
- package/dist/vite.js.map +1 -1
- package/dist/vite.mjs +145 -63
- package/dist/vite.mjs.map +1 -1
- package/dist/vue.js +38 -12
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +38 -12
- package/dist/vue.mjs.map +1 -1
- package/dist/webpackLoader.js +20 -5
- package/dist/webpackLoader.js.map +1 -1
- package/dist/webpackLoader.mjs +20 -5
- package/dist/webpackLoader.mjs.map +1 -1
- package/native/tailwind-styled-native.node +0 -0
- package/package.json +29 -24
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# tailwind-styled-v4
|
|
4
4
|
|
|
5
|
-
### ⚡ Rust-powered Tailwind CSS untuk React
|
|
5
|
+
### ⚡ Rust-powered Tailwind CSS v4 untuk React
|
|
6
6
|
**Build-time compiler · Zero runtime overhead · RSC-aware · Next.js / Vite / Rspack**
|
|
7
7
|
|
|
8
8
|
[](https://npmjs.com/package/tailwind-styled-v4)
|
|
@@ -18,35 +18,39 @@
|
|
|
18
18
|
|
|
19
19
|
## Apa ini?
|
|
20
20
|
|
|
21
|
-
`tailwind-styled-v4` adalah library styling untuk React yang menggabungkan **
|
|
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.
|
|
22
22
|
|
|
23
23
|
**Perbandingan singkat:**
|
|
24
24
|
|
|
25
25
|
| | tailwind-styled-v4 | styled-components | Tailwind CSS biasa |
|
|
26
26
|
|---|---|---|---|
|
|
27
|
-
| Build-time CSS | ✅ | ❌ | ✅ |
|
|
27
|
+
| Build-time CSS | ✅ | ❌ (runtime inject) | ✅ |
|
|
28
28
|
| Runtime overhead | ~0 | ~15KB | ~0 |
|
|
29
|
-
| Variants API | ✅ | terbatas | ❌ |
|
|
30
|
-
| RSC support | ✅
|
|
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`) | ✅ |
|
|
31
33
|
| Engine | 🦀 Rust | JS | JS |
|
|
34
|
+
| Dark mode | ✅ `dark:` prefix | manual | ✅ |
|
|
35
|
+
| TypeScript | ✅ full inference | partial | ✅ |
|
|
32
36
|
|
|
33
37
|
---
|
|
34
38
|
|
|
35
39
|
## Instalasi
|
|
36
40
|
|
|
37
41
|
```bash
|
|
38
|
-
# npm
|
|
39
42
|
npm install tailwind-styled-v4
|
|
40
43
|
|
|
41
|
-
#
|
|
44
|
+
# Setup otomatis
|
|
42
45
|
npx tw setup
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
`npx tw setup` akan otomatis:
|
|
46
49
|
- Mendeteksi bundler (Next.js / Vite / Rspack)
|
|
47
50
|
- Meng-inject plugin ke `next.config.ts` / `vite.config.ts`
|
|
48
|
-
- Membuat `tailwind-styled.config.json`
|
|
51
|
+
- Membuat `tailwind-styled.config.json` dengan CSS entry yang terdeteksi otomatis
|
|
49
52
|
- Menambahkan `@import "tailwindcss"` ke CSS entry
|
|
53
|
+
- Pre-warming scanner cache supaya dev pertama tidak cache miss
|
|
50
54
|
|
|
51
55
|
---
|
|
52
56
|
|
|
@@ -63,7 +67,6 @@ const Button = tw.button`
|
|
|
63
67
|
hover:bg-blue-700 transition
|
|
64
68
|
`
|
|
65
69
|
|
|
66
|
-
// Pakai seperti komponen biasa
|
|
67
70
|
<Button onClick={handleClick}>Klik saya</Button>
|
|
68
71
|
```
|
|
69
72
|
|
|
@@ -71,302 +74,233 @@ const Button = tw.button`
|
|
|
71
74
|
|
|
72
75
|
```tsx
|
|
73
76
|
const Button = tw.button({
|
|
74
|
-
base: "inline-flex items-center rounded-
|
|
77
|
+
base: "inline-flex items-center rounded-full px-5 py-2 font-medium transition-all",
|
|
75
78
|
variants: {
|
|
76
79
|
intent: {
|
|
77
|
-
primary: "bg-
|
|
78
|
-
secondary: "bg-
|
|
79
|
-
|
|
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",
|
|
80
84
|
},
|
|
81
85
|
size: {
|
|
82
|
-
sm: "
|
|
83
|
-
md: "
|
|
84
|
-
lg: "
|
|
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",
|
|
85
89
|
},
|
|
86
90
|
},
|
|
87
91
|
defaultVariants: { intent: "primary", size: "md" },
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<Button intent="danger">Hapus</Button>
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### 3. `.extend()` — Inheritance
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
// Turunan dari Button, tambah styling tanpa override base
|
|
99
|
-
const IconButton = Button.extend`
|
|
100
|
-
aspect-square justify-center rounded-full p-2
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
const LoadingButton = Button.extend`
|
|
104
|
-
opacity-60 cursor-wait
|
|
105
|
-
`
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### 4. `cx()` — Conditional Class Merge
|
|
109
|
-
|
|
110
|
-
```tsx
|
|
111
|
-
import { cx } from "tailwind-styled-v4"
|
|
112
|
-
|
|
113
|
-
function StatusDot({ online }: { online: boolean }) {
|
|
114
|
-
return (
|
|
115
|
-
<span className={cx(
|
|
116
|
-
"h-2.5 w-2.5 rounded-full",
|
|
117
|
-
online ? "bg-green-500" : "bg-gray-300"
|
|
118
|
-
)} />
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### 5. `cv()` — Class Variants (headless)
|
|
124
|
-
|
|
125
|
-
```tsx
|
|
126
|
-
import { cv } from "tailwind-styled-v4"
|
|
127
|
-
|
|
128
|
-
// Tanpa element HTML — untuk komponen custom
|
|
129
|
-
const alertStyles = cv({
|
|
130
|
-
base: "rounded-lg border p-4 text-sm",
|
|
131
|
-
variants: {
|
|
132
|
-
type: {
|
|
133
|
-
info: "border-blue-200 bg-blue-50 text-blue-800",
|
|
134
|
-
success: "border-green-200 bg-green-50 text-green-800",
|
|
135
|
-
error: "border-red-200 bg-red-50 text-red-800",
|
|
136
|
-
},
|
|
92
|
+
states: {
|
|
93
|
+
loading: "opacity-60 cursor-wait pointer-events-none",
|
|
94
|
+
disabled: "opacity-50 cursor-not-allowed",
|
|
95
|
+
fullWidth: "w-full",
|
|
137
96
|
},
|
|
138
97
|
})
|
|
139
98
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
99
|
+
// TypeScript tahu variant apa yang valid — autocomplete ✅
|
|
100
|
+
<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>
|
|
143
104
|
```
|
|
144
105
|
|
|
145
|
-
###
|
|
146
|
-
|
|
147
|
-
Ada **dua cara** mendefinisikan sub-components:
|
|
148
|
-
|
|
149
|
-
#### A. Config Object — Direkomendasikan (Autocomplete + Type Safe)
|
|
106
|
+
### 3. Sub-components
|
|
150
107
|
|
|
151
108
|
```tsx
|
|
152
109
|
const Card = tw.div({
|
|
153
|
-
base: "
|
|
110
|
+
base: "rounded-xl bg-white shadow-md overflow-hidden",
|
|
154
111
|
sub: {
|
|
155
|
-
header:
|
|
156
|
-
|
|
157
|
-
footer:
|
|
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",
|
|
158
120
|
},
|
|
159
121
|
})
|
|
160
122
|
|
|
161
|
-
//
|
|
162
|
-
<Card>
|
|
163
|
-
<Card.header>Judul</Card.header>
|
|
164
|
-
<Card.
|
|
165
|
-
<Card.
|
|
166
|
-
|
|
123
|
+
// 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>
|
|
167
132
|
</Card>
|
|
168
133
|
```
|
|
169
134
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
```tsx
|
|
173
|
-
const Card = tw.div`
|
|
174
|
-
flex flex-col p-4 rounded-xl bg-white shadow
|
|
175
|
-
[header] { font-bold text-lg border-b pb-2 }
|
|
176
|
-
[body] { text-gray-600 py-2 }
|
|
177
|
-
[footer] { border-t pt-2 text-sm text-gray-400 }
|
|
178
|
-
`
|
|
179
|
-
|
|
180
|
-
// Runtime benar, tapi TypeScript tidak bisa infer nama dari multiline template —
|
|
181
|
-
// ini limitasi TypeScript, bukan bug library. Gunakan config object untuk type safety.
|
|
182
|
-
<Card>
|
|
183
|
-
<Card.header>Judul</Card.header>
|
|
184
|
-
<Card.body>Konten</Card.body>
|
|
185
|
-
<Card.footer>Footer</Card.footer>
|
|
186
|
-
</Card>
|
|
187
|
-
```
|
|
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.
|
|
188
136
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
> **Sub-component tidak terdefinisi** tidak akan crash — library otomatis fallback ke `<span>` passthrough. Tapi tetap gunakan config object untuk catch typo di TypeScript.
|
|
192
|
-
|
|
193
|
-
### 7. State Engine — Zero-JS State Management
|
|
137
|
+
### 4. `.extend()` — Inheritance
|
|
194
138
|
|
|
195
139
|
```tsx
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
active: "bg-blue-600 scale-95",
|
|
201
|
-
loading: "opacity-70 cursor-wait",
|
|
202
|
-
disabled: "opacity-50 cursor-not-allowed",
|
|
203
|
-
},
|
|
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" }
|
|
204
144
|
})
|
|
205
|
-
// Usage: <Button data-active="true">Click</Button>
|
|
206
|
-
// Ketika data-active="true", style aktif otomatis tanpa re-render
|
|
207
145
|
```
|
|
208
146
|
|
|
209
|
-
###
|
|
147
|
+
### 5. States — Boolean Props
|
|
210
148
|
|
|
211
149
|
```tsx
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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",
|
|
153
|
+
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",
|
|
217
157
|
},
|
|
218
158
|
})
|
|
219
|
-
// Auto-generate container queries, tidak perlu tulis @container sendiri
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### 9. Live Token Engine — Dynamic CSS Variables
|
|
223
159
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const tokens = liveToken({
|
|
228
|
-
primary: "#3b82f6",
|
|
229
|
-
secondary: "#64748b",
|
|
230
|
-
radius: "0.5rem",
|
|
231
|
-
})
|
|
232
|
-
// Auto-generate CSS variables: --tw-primary, --tw-secondary, dll
|
|
233
|
-
// Subscribe perubahan: tokens.subscribe(callback)
|
|
160
|
+
<Badge active>Online</Badge>
|
|
161
|
+
<Badge error>Error</Badge>
|
|
234
162
|
```
|
|
235
163
|
|
|
236
|
-
###
|
|
164
|
+
### 6. Dark Mode
|
|
237
165
|
|
|
238
|
-
|
|
239
|
-
// tw.server adalah namespace terpisah — pakai tw.server.tagname
|
|
240
|
-
const Avatar = tw.server.img`rounded-full object-cover`
|
|
241
|
-
const Hero = tw.server.section`py-24 text-center`
|
|
242
|
-
|
|
243
|
-
// Bisa pakai sub-components juga
|
|
244
|
-
const Card = tw.server.div`
|
|
245
|
-
p-4 rounded-xl shadow
|
|
246
|
-
[header] { font-bold text-lg }
|
|
247
|
-
[body] { text-gray-600 }
|
|
248
|
-
`
|
|
249
|
-
|
|
250
|
-
// Di browser (dev): otomatis log warning kalau ter-render di client
|
|
251
|
-
// Di production: silent, tidak ada overhead
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### 11. Component Wrapping — tw(ExistingComponent)
|
|
255
|
-
|
|
256
|
-
```tsx
|
|
257
|
-
// Wrap komponen manapun dengan styling tambahan
|
|
258
|
-
const StyledCard = tw(Card)`shadow-lg border`
|
|
259
|
-
const BigButton = tw(Button)`text-xl px-8 py-4`
|
|
260
|
-
// Bisa juga pakai .extend():
|
|
261
|
-
const IconButton = Button.extend`p-2 rounded-full`
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
---
|
|
265
|
-
|
|
266
|
-
### Pattern Sub-components
|
|
267
|
-
|
|
268
|
-
**Pattern A: Config Object — Direkomendasikan (autocomplete + type safe)**
|
|
166
|
+
Dark mode bekerja otomatis via `prefers-color-scheme` — tidak perlu konfigurasi tambahan:
|
|
269
167
|
|
|
270
168
|
```tsx
|
|
271
169
|
const Card = tw.div({
|
|
272
|
-
base: "
|
|
170
|
+
base: "bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100",
|
|
273
171
|
sub: {
|
|
274
|
-
header: "
|
|
275
|
-
body: "text-gray-600 py-2",
|
|
276
|
-
footer: "border-t pt-2 text-sm",
|
|
172
|
+
header: "border-b border-gray-200 dark:border-gray-700",
|
|
277
173
|
},
|
|
278
174
|
})
|
|
279
|
-
// TypeScript infer: Card.header, Card.body, Card.footer ✅ autocomplete
|
|
280
|
-
// Card.xyz → TypeScript error ✅
|
|
281
175
|
```
|
|
282
176
|
|
|
283
|
-
|
|
177
|
+
### 7. Compound Variants
|
|
284
178
|
|
|
285
179
|
```tsx
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
180
|
+
const Button = tw.button({
|
|
181
|
+
base: "...",
|
|
182
|
+
variants: {
|
|
183
|
+
intent: { primary: "...", outline: "..." },
|
|
184
|
+
size: { sm: "...", lg: "..." },
|
|
185
|
+
},
|
|
186
|
+
compoundVariants: [
|
|
187
|
+
// Kalau intent=primary AND size=lg → tambah class ini
|
|
188
|
+
{ intent: "primary", size: "lg", class: "shadow-lg" },
|
|
189
|
+
],
|
|
190
|
+
})
|
|
293
191
|
```
|
|
294
192
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
Pakai ini kalau sub-components butuh semua style dari parent:
|
|
193
|
+
### 8. .withSub — Strict TypeScript untuk Template Literals
|
|
298
194
|
|
|
299
195
|
```tsx
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
304
205
|
```
|
|
305
206
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Mirip `.extend()` tapi untuk wrapping komponen yang sudah ada:
|
|
207
|
+
---
|
|
309
208
|
|
|
310
|
-
|
|
311
|
-
const Card = tw.div`flex flex-col p-4 bg-white shadow`
|
|
312
|
-
const CardHeader = tw(Card)`font-bold text-lg`
|
|
313
|
-
const CardBody = tw(Card)`text-gray-600`
|
|
314
|
-
const CardFooter = tw(Card)`border-t pt-4`
|
|
315
|
-
```
|
|
209
|
+
## Bagaimana CSS Di-generate?
|
|
316
210
|
|
|
317
|
-
|
|
211
|
+
Pipeline baru di v5 — tidak lagi pakai empty rules:
|
|
318
212
|
|
|
319
|
-
|
|
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
|
+
```
|
|
320
230
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
231
|
+
Hasilnya `_initial-scan.css` berisi real CSS (bukan empty rules):
|
|
232
|
+
|
|
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
|
+
/* ... */
|
|
248
|
+
}
|
|
326
249
|
```
|
|
327
250
|
|
|
251
|
+
Custom colors dari `@theme inline` di `globals.css` otomatis ter-generate — tidak perlu konfigurasi tambahan.
|
|
252
|
+
|
|
328
253
|
---
|
|
329
254
|
|
|
330
|
-
##
|
|
255
|
+
## Setup Next.js
|
|
331
256
|
|
|
332
|
-
###
|
|
257
|
+
### next.config.ts
|
|
333
258
|
|
|
334
259
|
```ts
|
|
335
|
-
|
|
260
|
+
import { withTailwindStyled } from "tailwind-styled-v4/next"
|
|
336
261
|
import type { NextConfig } from "next"
|
|
337
|
-
import { withTailwindStyled } from "@tailwind-styled/next"
|
|
338
262
|
|
|
339
|
-
const nextConfig: NextConfig = {
|
|
340
|
-
reactStrictMode: true,
|
|
341
|
-
}
|
|
263
|
+
const nextConfig: NextConfig = {}
|
|
342
264
|
|
|
343
|
-
export default withTailwindStyled(nextConfig)
|
|
265
|
+
export default withTailwindStyled({ verbose: true })(nextConfig)
|
|
344
266
|
```
|
|
345
267
|
|
|
346
|
-
|
|
347
|
-
// app/page.tsx — Server Component, tidak perlu 'use client'
|
|
348
|
-
import { tw } from "tailwind-styled-v4"
|
|
268
|
+
### globals.css
|
|
349
269
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
270
|
+
```css
|
|
271
|
+
@import "tailwindcss";
|
|
272
|
+
@source "../.next/tw-classes/**";
|
|
353
273
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
274
|
+
:root {
|
|
275
|
+
--background: #ffffff;
|
|
276
|
+
--foreground: #171717;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@theme inline {
|
|
280
|
+
--color-background: var(--background);
|
|
281
|
+
--color-foreground: var(--foreground);
|
|
282
|
+
--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
|
+
}
|
|
360
291
|
}
|
|
361
292
|
```
|
|
362
293
|
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Bundler Integration
|
|
297
|
+
|
|
363
298
|
### Vite
|
|
364
299
|
|
|
365
300
|
```ts
|
|
366
|
-
// vite.config.ts
|
|
367
301
|
import { defineConfig } from "vite"
|
|
368
302
|
import react from "@vitejs/plugin-react"
|
|
369
|
-
import { tailwindStyled } from "
|
|
303
|
+
import { tailwindStyled } from "tailwind-styled-v4/vite"
|
|
370
304
|
|
|
371
305
|
export default defineConfig({
|
|
372
306
|
plugins: [react(), tailwindStyled()],
|
|
@@ -376,9 +310,8 @@ export default defineConfig({
|
|
|
376
310
|
### Rspack
|
|
377
311
|
|
|
378
312
|
```js
|
|
379
|
-
// rspack.config.mjs
|
|
380
313
|
import { defineConfig } from "@rspack/cli"
|
|
381
|
-
import { tailwindStyled } from "
|
|
314
|
+
import { tailwindStyled } from "tailwind-styled-v4/rspack"
|
|
382
315
|
|
|
383
316
|
export default defineConfig({
|
|
384
317
|
entry: "./src/index.ts",
|
|
@@ -391,7 +324,7 @@ export default defineConfig({
|
|
|
391
324
|
## CLI
|
|
392
325
|
|
|
393
326
|
```bash
|
|
394
|
-
# Setup otomatis (
|
|
327
|
+
# Setup otomatis (detect bundler, patch config, pre-warm cache)
|
|
395
328
|
npx tw setup
|
|
396
329
|
|
|
397
330
|
# Verifikasi setup
|
|
@@ -406,9 +339,22 @@ npx tw benchmark
|
|
|
406
339
|
|
|
407
340
|
---
|
|
408
341
|
|
|
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
|
+
|
|
409
355
|
## Benchmark
|
|
410
356
|
|
|
411
|
-
Diukur di
|
|
357
|
+
Diukur di Node.js 22, Rust 1.75.
|
|
412
358
|
|
|
413
359
|
| Operasi | tailwind-styled-v4 | Tailwind CSS (JS) | Speedup |
|
|
414
360
|
|---|---|---|---|
|
|
@@ -418,59 +364,94 @@ Diukur di Debian Linux, Node.js 22, Rust 1.75.
|
|
|
418
364
|
| Cache read/write | **0.009 ms** | ~0.5 ms | **~55×** |
|
|
419
365
|
| Watch mode rebuild | **< 5 ms** | ~85 ms | **~17×** |
|
|
420
366
|
|
|
421
|
-
*Benchmark dijalankan via `npm run bench` (scripts/benchmark/run.mjs)*
|
|
422
|
-
|
|
423
367
|
---
|
|
424
368
|
|
|
425
369
|
## Arsitektur
|
|
426
370
|
|
|
427
371
|
```
|
|
428
372
|
tailwind-styled-v4/
|
|
429
|
-
├── native/ # Rust engine (
|
|
430
|
-
│ ├── src/
|
|
431
|
-
│
|
|
432
|
-
│ ├── src/
|
|
433
|
-
│
|
|
373
|
+
├── native/ # Rust engine (NAPI-RS)
|
|
374
|
+
│ ├── src/application/
|
|
375
|
+
│ │ └── ast_extract.rs # Extract Tailwind classes dari source files
|
|
376
|
+
│ ├── src/domain/
|
|
377
|
+
│ │ ├── variants.rs # Variant resolution (props override defaults)
|
|
378
|
+
│ │ └── transform.rs # Transform object config → JS component
|
|
379
|
+
│ └── src/infrastructure/
|
|
380
|
+
│ └── cache_store.rs # Persistent cache dengan bracket-aware parser
|
|
434
381
|
│
|
|
435
382
|
├── packages/
|
|
436
|
-
│ ├──
|
|
437
|
-
│ ├──
|
|
438
|
-
│ ├──
|
|
439
|
-
│
|
|
440
|
-
│ ├──
|
|
441
|
-
│
|
|
442
|
-
│
|
|
443
|
-
│
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
383
|
+
│ ├── domain/
|
|
384
|
+
│ │ ├── core/ # tw, cx, cv, cn — core API + createComponent
|
|
385
|
+
│ │ ├── compiler/ # Tailwind JS + LightningCSS pipeline
|
|
386
|
+
│ │ └── scanner/ # File scanner (Rust-backed)
|
|
387
|
+
│ ├── presentation/
|
|
388
|
+
│ │ └── next/ # Next.js plugin (withTailwindStyled)
|
|
389
|
+
│ └── infrastructure/
|
|
390
|
+
│ └── cli/ # CLI (tw setup, tw audit, dll)
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## TypeScript
|
|
396
|
+
|
|
397
|
+
Library ini fully typed — tidak ada `any` di public API:
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
// Type inference otomatis dari config
|
|
401
|
+
const Button = tw.button({
|
|
402
|
+
variants: {
|
|
403
|
+
intent: { primary: "...", ghost: "...", outline: "..." },
|
|
404
|
+
size: { sm: "...", md: "...", lg: "..." },
|
|
405
|
+
},
|
|
406
|
+
defaultVariants: { intent: "primary", size: "md" },
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
// TypeScript tahu props yang valid
|
|
410
|
+
<Button intent="invalid" /> // ❌ Type error
|
|
411
|
+
<Button intent="primary" /> // ✅
|
|
412
|
+
|
|
413
|
+
// Sub-components — ExtractSubName type inference
|
|
414
|
+
const Card = tw.div({
|
|
415
|
+
sub: {
|
|
416
|
+
header: "font-bold",
|
|
417
|
+
"div:action": "flex gap-3", // → Card.action (tag prefix di-strip otomatis)
|
|
418
|
+
},
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
Card.action // ✅ autocomplete
|
|
422
|
+
Card.xyz // ❌ TypeScript error
|
|
453
423
|
```
|
|
454
424
|
|
|
455
425
|
---
|
|
456
426
|
|
|
427
|
+
## Environment Variables
|
|
428
|
+
|
|
429
|
+
| Variable | Default | Deskripsi |
|
|
430
|
+
|---|---|---|
|
|
431
|
+
| `TWS_LOG_LEVEL` | `info` | `debug\|info\|warn\|error\|silent` |
|
|
432
|
+
| `TWS_DEBUG_SCANNER` | `0` | `1` = aktifkan scanner debug logs |
|
|
433
|
+
| `STUDIO_PORT` | `3030` | Port studio server |
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
457
437
|
## Development
|
|
458
438
|
|
|
459
439
|
```bash
|
|
460
|
-
# Clone
|
|
461
440
|
git clone https://github.com/Dictionar32/tailwind-styled-v4.git
|
|
462
441
|
cd tailwind-styled-v4
|
|
463
442
|
|
|
464
|
-
# Install dependencies
|
|
465
443
|
npm install
|
|
466
444
|
|
|
467
445
|
# Build Rust binary + semua packages
|
|
468
446
|
npm run build
|
|
469
447
|
|
|
470
|
-
#
|
|
448
|
+
# Build Rust only
|
|
449
|
+
npm run build:rust
|
|
450
|
+
|
|
451
|
+
# Test
|
|
471
452
|
npm run test
|
|
472
453
|
|
|
473
|
-
# Dev mode
|
|
454
|
+
# Dev mode
|
|
474
455
|
npm run dev
|
|
475
456
|
|
|
476
457
|
# Benchmark
|
|
@@ -480,129 +461,20 @@ npm run bench
|
|
|
480
461
|
**Requirements:**
|
|
481
462
|
- Node.js 20+
|
|
482
463
|
- Rust 1.75+ (untuk build dari source)
|
|
483
|
-
- Pre-built binary sudah disertakan untuk Linux x64
|
|
484
464
|
|
|
485
465
|
---
|
|
486
466
|
|
|
487
467
|
## Contributing
|
|
488
468
|
|
|
489
|
-
PR dan issue sangat welcome!
|
|
469
|
+
PR dan issue sangat welcome!
|
|
490
470
|
|
|
491
471
|
Prioritas saat ini:
|
|
492
|
-
- [
|
|
493
|
-
- [
|
|
472
|
+
- [ ] macOS & Windows pre-built binary
|
|
473
|
+
- [ ] Docs website (VitePress)
|
|
474
|
+
- [ ] More bundler adapters
|
|
494
475
|
|
|
495
476
|
---
|
|
496
477
|
|
|
497
478
|
## License
|
|
498
479
|
|
|
499
|
-
[MIT](LICENSE) © Dictionar32
|
|
500
|
-
|
|
501
|
-
---
|
|
502
|
-
|
|
503
|
-
## Engine Architecture (v4.5 Platform Overhaul)
|
|
504
|
-
|
|
505
|
-
Sprint 6–10 membawa perombakan arsitektur besar pada engine. Berikut ringkasannya.
|
|
506
|
-
|
|
507
|
-
### 🦀 Rust-backed pipeline
|
|
508
|
-
|
|
509
|
-
| Komponen | Implementasi | Fallback |
|
|
510
|
-
|---|---|---|
|
|
511
|
-
| Class scanner | `scan_workspace` (Rust) | JS `scanWorkspace` |
|
|
512
|
-
| Persistent cache | `cache_read/write` (Rust) | JS `ScanCache` |
|
|
513
|
-
| Incremental diff | `process_file_change` + DashMap | JS class set diff |
|
|
514
|
-
| File watcher | `notify` crate via polling IPC | Node.js `fs.watch` |
|
|
515
|
-
| Class analyzer | `analyze_classes` (Rust) | — |
|
|
516
|
-
|
|
517
|
-
### ⚡ Performance
|
|
518
|
-
|
|
519
|
-
- Cold start scan: **<10ms** (was >100ms) via persistent Rust cache
|
|
520
|
-
- Incremental update: **~0ms** untuk unchanged files
|
|
521
|
-
- Watch idle CPU: **~66% reduction** (500ms poll interval)
|
|
522
|
-
- Cache hit rate: typically **>95%** pada incremental dev
|
|
523
|
-
|
|
524
|
-
### 🔌 Platform Adapters
|
|
525
|
-
|
|
526
|
-
Semua adapter (Next.js, Vite, Rspack) sekarang **bundle compiler secara inline** — user tidak perlu menginstall `@tailwind-styled/compiler` secara terpisah.
|
|
527
|
-
|
|
528
|
-
```ts
|
|
529
|
-
// next.config.ts
|
|
530
|
-
import { withTailwindStyled } from 'tailwind-styled-v4/next'
|
|
531
|
-
export default withTailwindStyled()(nextConfig)
|
|
532
|
-
|
|
533
|
-
// vite.config.ts
|
|
534
|
-
import { tailwindStyledPlugin } from 'tailwind-styled-v4/vite'
|
|
535
|
-
export default defineConfig({ plugins: [tailwindStyledPlugin()] })
|
|
536
|
-
```
|
|
537
|
-
|
|
538
|
-
`preserveImports: true` diset di semua loader — `cv`, `cx`, `cn` tetap tersedia di output.
|
|
539
|
-
|
|
540
|
-
### 📊 Developer Tooling
|
|
541
|
-
|
|
542
|
-
```bash
|
|
543
|
-
# Analisis workspace (Rust engine)
|
|
544
|
-
tw analyze .
|
|
545
|
-
tw stats .
|
|
546
|
-
|
|
547
|
-
# Dashboard metrics real-time
|
|
548
|
-
tw dashboard
|
|
549
|
-
|
|
550
|
-
# Watch mode incremental
|
|
551
|
-
tw watch .
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
DevTools overlay (`Ctrl+Shift+D`) menampilkan:
|
|
555
|
-
- **Inspector** — hover element → lihat variant props & classes
|
|
556
|
-
- **State** — reactive state components
|
|
557
|
-
- **Container** — container query breakpoints aktif
|
|
558
|
-
- **Tokens** — live token editor (perubahan instan)
|
|
559
|
-
- **Analyzer** — DOM scan + engine metrics dari dashboard
|
|
560
|
-
|
|
561
|
-
### 🖥️ Studio Desktop (Electron)
|
|
562
|
-
|
|
563
|
-
```bash
|
|
564
|
-
# Dev mode
|
|
565
|
-
npm run dev -w @tailwind-styled/studio-desktop
|
|
566
|
-
|
|
567
|
-
# Build distribusi
|
|
568
|
-
npm run build:mac # macOS DMG + ZIP
|
|
569
|
-
npm run build:win # Windows NSIS installer
|
|
570
|
-
npm run build:linux # AppImage + DEB
|
|
571
|
-
npm run build:all # Semua platform sekaligus
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
Engine tersedia langsung dari renderer via `window.studioDesktop`:
|
|
575
|
-
|
|
576
|
-
```js
|
|
577
|
-
const result = await window.studioDesktop.engineBuild()
|
|
578
|
-
// { ok: true, totalFiles: 42, uniqueClasses: 312, cssLength: 18540 }
|
|
579
|
-
|
|
580
|
-
window.studioDesktop.onEngineEvent((event) => {
|
|
581
|
-
if (event.type === 'change') console.log('Rebuilt:', event.result.css.length, 'bytes')
|
|
582
|
-
})
|
|
583
|
-
await window.studioDesktop.engineWatchStart()
|
|
584
|
-
```
|
|
585
|
-
|
|
586
|
-
### 🧪 Testing
|
|
587
|
-
|
|
588
|
-
```ts
|
|
589
|
-
import { expectEngineMetrics, toHaveEngineMetrics, tailwindMatchersWithMetrics } from '@tailwind-styled/testing'
|
|
590
|
-
|
|
591
|
-
// Assertion langsung
|
|
592
|
-
expectEngineMetrics(metrics, { minFiles: 10, maxBuildTimeMs: 500, cacheHitRateMin: 0.9 })
|
|
593
|
-
|
|
594
|
-
// Vitest/Jest custom matcher
|
|
595
|
-
expect.extend(tailwindMatchersWithMetrics)
|
|
596
|
-
expect(metrics).toHaveEngineMetrics({ minFiles: 5 })
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### 🔧 Environment Variables
|
|
600
|
-
|
|
601
|
-
| Variable | Default | Deskripsi |
|
|
602
|
-
|---|---|---|
|
|
603
|
-
| `TWS_LOG_LEVEL` | `info` | `debug\|info\|warn\|error\|silent` |
|
|
604
|
-
| `TWS_NO_NATIVE` | `0` | `1` = paksa JS fallback |
|
|
605
|
-
| `TWS_NO_RUST` | `0` | Alias untuk `TWS_NO_NATIVE` |
|
|
606
|
-
| `TWS_DEBUG_SCANNER` | `0` | `1` = aktifkan scanner debug logs |
|
|
607
|
-
| `STUDIO_PORT` | `3030` | Port studio server |
|
|
608
|
-
| `STUDIO_VERBOSE` | tidak ada | Tampilkan stdout/stderr studio server |
|
|
480
|
+
[MIT](LICENSE) © Dictionar32
|