@tailwind-styled/compiler 2.0.0

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/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@tailwind-styled/compiler",
3
+ "version": "2.0.0",
4
+ "description": "Compiler pipeline for tailwind-styled-v4",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "dependencies": {
18
+ "tailwind-merge": "^3",
19
+ "postcss": "^8"
20
+ },
21
+ "optionalDependencies": {
22
+ "oxc-parser": "^0.118.0"
23
+ },
24
+ "peerDependencies": {
25
+ "tailwindcss": "^4",
26
+ "@tailwindcss/postcss": "^4"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^20",
30
+ "tsup": "^8",
31
+ "typescript": "^5"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup src/index.ts --format cjs,esm --dts --out-dir dist --clean",
35
+ "dev": "tsup src/index.ts --format cjs,esm --dts --out-dir dist --watch"
36
+ }
37
+ }
@@ -0,0 +1,397 @@
1
+ /**
2
+ * tailwind-styled-v4 — astParser
3
+ *
4
+ * UPGRADE RUST: oxc-parser (Rust-based, via napi-rs) menggantikan
5
+ * hand-written bracket-counting tokenizer.
6
+ *
7
+ * Keuntungan oxc-parser:
8
+ * - ~10x lebih cepat dari tokenizer TypeScript
9
+ * - Handles semua edge case TypeScript/JS secara native
10
+ * - Same parser yang dipakai Rolldown, Vite 6, Biome
11
+ * - Zero maintenance — battle-tested di ekosistem besar
12
+ *
13
+ * Strategy: oxc-parser sebagai primary, tokenizer lama sebagai fallback.
14
+ * Jika oxc gagal parse (malformed input), fallback transparan — zero breakage.
15
+ *
16
+ * Compatibility: Next.js, Vite, Rspack — semua fully supported.
17
+ * oxc-parser adalah native Node addon (napi-rs), tidak ada WASM overhead.
18
+ */
19
+
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // Public types
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+
24
+ export interface ParsedComponentConfig {
25
+ base: string
26
+ variants: Record<string, Record<string, string>>
27
+ compounds: Array<{ class: string; [key: string]: any }>
28
+ defaults: Record<string, string>
29
+ }
30
+
31
+ // ─────────────────────────────────────────────────────────────────────────────
32
+ // oxc-parser — Rust AST walker (primary)
33
+ // ─────────────────────────────────────────────────────────────────────────────
34
+
35
+ /** Resolve key from Identifier or Literal node */
36
+ function oxcKey(node: any): string | null {
37
+ if (!node) return null
38
+ if (node.type === "Identifier") return node.name as string
39
+ if (node.type === "Literal" && typeof node.value === "string") return node.value
40
+ return null
41
+ }
42
+
43
+ /** Resolve string value from Literal or no-expression TemplateLiteral */
44
+ function oxcStringVal(node: any): string | null {
45
+ if (!node) return null
46
+ if (node.type === "Literal" && typeof node.value === "string") return node.value
47
+ if (node.type === "TemplateLiteral" && node.expressions?.length === 0) {
48
+ return (node.quasis as any[]).map((q: any) => q.value?.cooked ?? q.value?.raw ?? "").join("")
49
+ }
50
+ return null
51
+ }
52
+
53
+ /** Recursively walk ObjectExpression → plain Record */
54
+ function oxcWalkObject(node: any): Record<string, any> {
55
+ const result: Record<string, any> = {}
56
+ if (node?.type !== "ObjectExpression") return result
57
+
58
+ for (const prop of node.properties ?? []) {
59
+ if (prop.type !== "Property") continue
60
+ const key = oxcKey(prop.key)
61
+ if (!key) continue
62
+
63
+ const val = prop.value
64
+ const strVal = oxcStringVal(val)
65
+
66
+ if (strVal !== null) {
67
+ result[key] = strVal
68
+ } else if (val?.type === "ObjectExpression") {
69
+ result[key] = oxcWalkObject(val)
70
+ } else if (val?.type === "ArrayExpression") {
71
+ result[key] = (val.elements as any[])
72
+ .filter((el: any) => el?.type === "ObjectExpression")
73
+ .map((el: any) => oxcWalkObject(el))
74
+ }
75
+ // skip dynamic expressions, functions, computed props, etc.
76
+ }
77
+ return result
78
+ }
79
+
80
+ /**
81
+ * Parse config object string using oxc-parser (Rust).
82
+ * Wraps string sebagai valid statement agar oxc bisa parse.
83
+ * Returns null jika parse gagal → fallback ke tokenizer.
84
+ */
85
+ function parseWithOxc(objectStr: string): ParsedComponentConfig | null {
86
+ let parseSync: (filename: string, source: string, options?: any) => any
87
+ try {
88
+ // Dynamic require agar tidak crash jika oxc-parser tidak terinstall
89
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
90
+ parseSync = require("oxc-parser").parseSync
91
+ } catch {
92
+ return null // oxc-parser not available → silently fallback
93
+ }
94
+
95
+ try {
96
+ const source = `const __c = ${objectStr}`
97
+ const { program, errors } = parseSync("config.ts", source, { sourceType: "script" })
98
+
99
+ if (errors?.length > 0 || !program?.body?.[0]) return null
100
+
101
+ const varDecl = program.body[0]
102
+ if (varDecl.type !== "VariableDeclaration") return null
103
+
104
+ const init = varDecl.declarations?.[0]?.init
105
+ if (init?.type !== "ObjectExpression") return null
106
+
107
+ const raw = oxcWalkObject(init)
108
+
109
+ // ── base ────────────────────────────────────────────────────────────
110
+ const base = typeof raw.base === "string" ? raw.base.trim() : ""
111
+
112
+ // ── variants ─────────────────────────────────────────────────────────
113
+ const variants: Record<string, Record<string, string>> = {}
114
+ const rawVariants = raw.variants
115
+ if (rawVariants && typeof rawVariants === "object" && !Array.isArray(rawVariants)) {
116
+ for (const [vName, vMap] of Object.entries(rawVariants)) {
117
+ if (vMap && typeof vMap === "object" && !Array.isArray(vMap)) {
118
+ variants[vName] = {}
119
+ for (const [vVal, cls] of Object.entries(vMap as Record<string, any>)) {
120
+ if (typeof cls === "string") variants[vName][vVal] = cls.trim()
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ // ── compoundVariants ─────────────────────────────────────────────────
127
+ const compounds: Array<{ class: string; [key: string]: any }> = []
128
+ const rawCompounds = raw.compoundVariants
129
+ if (Array.isArray(rawCompounds)) {
130
+ for (const item of rawCompounds) {
131
+ if (item && typeof item.class === "string") {
132
+ compounds.push(item as { class: string })
133
+ }
134
+ }
135
+ }
136
+
137
+ // ── defaultVariants ──────────────────────────────────────────────────
138
+ const defaults: Record<string, string> = {}
139
+ const rawDefaults = raw.defaultVariants
140
+ if (rawDefaults && typeof rawDefaults === "object" && !Array.isArray(rawDefaults)) {
141
+ for (const [k, v] of Object.entries(rawDefaults)) {
142
+ if (typeof v === "string") defaults[k] = v
143
+ }
144
+ }
145
+
146
+ return { base, variants, compounds, defaults }
147
+ } catch {
148
+ return null // parse error → fallback
149
+ }
150
+ }
151
+
152
+ // ─────────────────────────────────────────────────────────────────────────────
153
+ // Tokenizer fallback (original implementation — preserved as-is)
154
+ // ─────────────────────────────────────────────────────────────────────────────
155
+
156
+ type TokenType =
157
+ | "string"
158
+ | "key"
159
+ | "colon"
160
+ | "comma"
161
+ | "lbrace"
162
+ | "rbrace"
163
+ | "lbracket"
164
+ | "rbracket"
165
+ | "other"
166
+
167
+ interface Token {
168
+ type: TokenType
169
+ value: string
170
+ pos: number
171
+ }
172
+
173
+ function tokenize(src: string): Token[] {
174
+ const tokens: Token[] = []
175
+ let i = 0
176
+
177
+ while (i < src.length) {
178
+ const ch = src[i]
179
+
180
+ if (/\s/.test(ch)) {
181
+ i++
182
+ continue
183
+ }
184
+
185
+ if (ch === '"' || ch === "'" || ch === "`") {
186
+ const quote = ch
187
+ let j = i + 1
188
+ let str = ch
189
+ while (j < src.length) {
190
+ if (src[j] === "\\" && quote !== "`") {
191
+ str += src[j] + src[j + 1]
192
+ j += 2
193
+ continue
194
+ }
195
+ if (src[j] === "\\" && quote === "`") {
196
+ str += src[j] + src[j + 1]
197
+ j += 2
198
+ continue
199
+ }
200
+ str += src[j]
201
+ if (src[j] === quote) {
202
+ j++
203
+ break
204
+ }
205
+ j++
206
+ }
207
+ tokens.push({ type: "string", value: str.slice(1, -1), pos: i })
208
+ i = j
209
+ continue
210
+ }
211
+
212
+ if (ch === ":") {
213
+ tokens.push({ type: "colon", value: ":", pos: i })
214
+ i++
215
+ continue
216
+ }
217
+ if (ch === ",") {
218
+ tokens.push({ type: "comma", value: ",", pos: i })
219
+ i++
220
+ continue
221
+ }
222
+ if (ch === "{") {
223
+ tokens.push({ type: "lbrace", value: "{", pos: i })
224
+ i++
225
+ continue
226
+ }
227
+ if (ch === "}") {
228
+ tokens.push({ type: "rbrace", value: "}", pos: i })
229
+ i++
230
+ continue
231
+ }
232
+ if (ch === "[") {
233
+ tokens.push({ type: "lbracket", value: "[", pos: i })
234
+ i++
235
+ continue
236
+ }
237
+ if (ch === "]") {
238
+ tokens.push({ type: "rbracket", value: "]", pos: i })
239
+ i++
240
+ continue
241
+ }
242
+
243
+ if (/[\w$]/.test(ch)) {
244
+ let j = i
245
+ while (j < src.length && /[\w$]/.test(src[j])) j++
246
+ tokens.push({ type: "key", value: src.slice(i, j), pos: i })
247
+ i = j
248
+ continue
249
+ }
250
+
251
+ tokens.push({ type: "other", value: ch, pos: i })
252
+ i++
253
+ }
254
+
255
+ return tokens
256
+ }
257
+
258
+ interface ParsedObject {
259
+ [key: string]: string | ParsedObject | Array<ParsedObject>
260
+ }
261
+
262
+ function parseObject(tokens: Token[], startIdx: number): { obj: ParsedObject; endIdx: number } {
263
+ const obj: ParsedObject = {}
264
+ let i = startIdx
265
+
266
+ if (tokens[i]?.type !== "lbrace") return { obj, endIdx: i }
267
+ i++
268
+
269
+ while (i < tokens.length && tokens[i]?.type !== "rbrace") {
270
+ if (tokens[i]?.type === "comma") {
271
+ i++
272
+ continue
273
+ }
274
+
275
+ let key: string | null = null
276
+ if (tokens[i]?.type === "string") {
277
+ key = tokens[i].value
278
+ i++
279
+ } else if (tokens[i]?.type === "key") {
280
+ key = tokens[i].value
281
+ i++
282
+ } else {
283
+ i++
284
+ continue
285
+ }
286
+
287
+ if (tokens[i]?.type !== "colon") continue
288
+ i++
289
+
290
+ if (tokens[i]?.type === "lbrace") {
291
+ const { obj: nested, endIdx } = parseObject(tokens, i)
292
+ obj[key] = nested
293
+ i = endIdx + 1
294
+ } else if (tokens[i]?.type === "lbracket") {
295
+ const { arr, endIdx } = parseArray(tokens, i)
296
+ obj[key] = arr as any
297
+ i = endIdx + 1
298
+ } else if (tokens[i]?.type === "string") {
299
+ obj[key] = tokens[i].value
300
+ i++
301
+ } else if (tokens[i]?.type === "key") {
302
+ obj[key] = tokens[i].value
303
+ i++
304
+ } else {
305
+ i++
306
+ }
307
+ }
308
+
309
+ return { obj, endIdx: i }
310
+ }
311
+
312
+ function parseArray(tokens: Token[], startIdx: number): { arr: ParsedObject[]; endIdx: number } {
313
+ const arr: ParsedObject[] = []
314
+ let i = startIdx
315
+
316
+ if (tokens[i]?.type !== "lbracket") return { arr, endIdx: i }
317
+ i++
318
+
319
+ while (i < tokens.length && tokens[i]?.type !== "rbracket") {
320
+ if (tokens[i]?.type === "comma") {
321
+ i++
322
+ continue
323
+ }
324
+ if (tokens[i]?.type === "lbrace") {
325
+ const { obj, endIdx } = parseObject(tokens, i)
326
+ arr.push(obj)
327
+ i = endIdx + 1
328
+ } else {
329
+ i++
330
+ }
331
+ }
332
+
333
+ return { arr, endIdx: i }
334
+ }
335
+
336
+ function parseComponentConfigFallback(objectStr: string): ParsedComponentConfig {
337
+ const tokens = tokenize(objectStr)
338
+ const { obj } = parseObject(tokens, 0)
339
+
340
+ const base = typeof obj.base === "string" ? obj.base.trim() : ""
341
+
342
+ const variants: Record<string, Record<string, string>> = {}
343
+ const rawVariants = obj.variants
344
+ if (rawVariants && typeof rawVariants === "object" && !Array.isArray(rawVariants)) {
345
+ for (const [variantName, variantValues] of Object.entries(rawVariants as ParsedObject)) {
346
+ if (typeof variantValues === "object" && !Array.isArray(variantValues)) {
347
+ variants[variantName] = {}
348
+ for (const [valueName, cls] of Object.entries(variantValues as ParsedObject)) {
349
+ if (typeof cls === "string") variants[variantName][valueName] = cls.trim()
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+ const compounds: Array<{ class: string; [key: string]: any }> = []
356
+ const rawCompounds = obj.compoundVariants
357
+ if (Array.isArray(rawCompounds)) {
358
+ for (const item of rawCompounds as ParsedObject[]) {
359
+ if (item && typeof item.class === "string") compounds.push(item as any)
360
+ }
361
+ }
362
+
363
+ const defaults: Record<string, string> = {}
364
+ const rawDefaults = obj.defaultVariants
365
+ if (rawDefaults && typeof rawDefaults === "object" && !Array.isArray(rawDefaults)) {
366
+ for (const [k, v] of Object.entries(rawDefaults as ParsedObject)) {
367
+ if (typeof v === "string") defaults[k] = v
368
+ }
369
+ }
370
+
371
+ return { base, variants, compounds, defaults }
372
+ }
373
+
374
+ // ─────────────────────────────────────────────────────────────────────────────
375
+ // Public API — oxc-parser primary, tokenizer fallback
376
+ // ─────────────────────────────────────────────────────────────────────────────
377
+
378
+ /**
379
+ * Parse a ComponentConfig object literal string.
380
+ *
381
+ * PRIMARY: oxc-parser (Rust, napi-rs) — ~10x faster, full TS/JS support.
382
+ * FALLBACK: bracket-counting tokenizer — transparan, zero breakage.
383
+ *
384
+ * @example
385
+ * parseComponentConfig(`{
386
+ * base: "px-4 py-2",
387
+ * variants: { size: { sm: "text-sm", lg: "text-lg" } },
388
+ * defaultVariants: { size: "sm" }
389
+ * }`)
390
+ */
391
+ export function parseComponentConfig(objectStr: string): ParsedComponentConfig {
392
+ const oxcResult = parseWithOxc(objectStr)
393
+ if (oxcResult !== null) return oxcResult
394
+
395
+ // Fallback: original tokenizer
396
+ return parseComponentConfigFallback(objectStr)
397
+ }