@csszyx/unplugin 0.6.2 → 0.8.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.
@@ -1,9 +1,312 @@
1
- import {
2
- mangleCSSSync
3
- } from "./chunk-4M7CPGP7.js";
1
+ import * as fs from 'node:fs';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { dirname } from 'node:path';
5
+ import { transformSourceCode, transformOxc, transform } from '@csszyx/compiler';
6
+ import { encode, compute_mangle_checksum } from '@csszyx/core';
7
+ import { preprocess as preprocess$1 } from '@csszyx/svelte-adapter';
8
+ import { preprocess } from '@csszyx/vue-adapter';
9
+ import { createUnplugin } from 'unplugin';
10
+ import { mangleCSSSync } from '../css-mangler.mjs';
11
+ import { createHash } from 'node:crypto';
4
12
 
5
- // src/theme-scanner.ts
6
- var EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
13
+ const SERVER_DIRECTIVE_RE = /^['"]use server['"];?$/;
14
+ const CLIENT_DIRECTIVE_RE = /^['"]use client['"];?$/;
15
+ const RUNTIME_MODULES = /* @__PURE__ */ new Set([
16
+ "@csszyx/runtime",
17
+ "@csszyx/runtime/lite",
18
+ "csszyx",
19
+ "csszyx/lite"
20
+ ]);
21
+ const FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
22
+ "_sz",
23
+ "_sz2",
24
+ "_sz3",
25
+ "_szIf",
26
+ "_szMerge",
27
+ "_szSwitch",
28
+ "__csszyx_runtime__"
29
+ ]);
30
+ function hasUseServerDirective(code) {
31
+ for (const statement of readDirectivePrologue(code)) {
32
+ if (SERVER_DIRECTIVE_RE.test(statement)) {
33
+ return true;
34
+ }
35
+ if (CLIENT_DIRECTIVE_RE.test(statement)) {
36
+ return false;
37
+ }
38
+ }
39
+ return false;
40
+ }
41
+ function hasUseClientDirective(code) {
42
+ for (const statement of readDirectivePrologue(code)) {
43
+ if (CLIENT_DIRECTIVE_RE.test(statement)) {
44
+ return true;
45
+ }
46
+ if (SERVER_DIRECTIVE_RE.test(statement)) {
47
+ return false;
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+ function isRSCServerModule(code, id) {
53
+ if (hasUseServerDirective(code)) {
54
+ return true;
55
+ }
56
+ if (hasUseClientDirective(code)) {
57
+ return false;
58
+ }
59
+ return isNextAppRouterEntry(id);
60
+ }
61
+ function findRSCBoundaryViolation(code, id) {
62
+ if (!isRSCServerModule(code, id)) {
63
+ return null;
64
+ }
65
+ for (const imported of findRuntimeImports(code)) {
66
+ for (const symbol of imported.symbols) {
67
+ if (FORBIDDEN_SYMBOLS.has(symbol)) {
68
+ return {
69
+ symbol,
70
+ path: id,
71
+ importChain: [id, imported.source]
72
+ };
73
+ }
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ function createRSCModuleRecord(code, id) {
79
+ const normalized = normalizeModuleId(id);
80
+ return {
81
+ id: normalized,
82
+ isServer: isRSCServerModule(code, normalized),
83
+ isClient: hasUseClientDirective(code),
84
+ imports: findLocalImportSources(code).map((source) => resolveLocalModule(normalized, source)).filter((resolved) => resolved !== null),
85
+ runtimeImports: findRuntimeImports(code).filter(
86
+ (imported) => imported.symbols.some((symbol) => FORBIDDEN_SYMBOLS.has(symbol))
87
+ )
88
+ };
89
+ }
90
+ function findRSCGraphViolation(records) {
91
+ for (const root of records.values()) {
92
+ if (!root.isServer) {
93
+ continue;
94
+ }
95
+ const violation = walkRSCGraph(root, records, [root.id], /* @__PURE__ */ new Set([root.id]));
96
+ if (violation) {
97
+ return {
98
+ symbol: violation.symbol,
99
+ path: root.id,
100
+ importChain: violation.importChain
101
+ };
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ function assertNoRSCGraphViolation(records) {
107
+ const violation = findRSCGraphViolation(records);
108
+ if (!violation) {
109
+ return;
110
+ }
111
+ throw new Error(formatRSCViolation(violation));
112
+ }
113
+ function isNextAppRouterEntry(id) {
114
+ const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
115
+ return /(^|\/)app\/.*\/?(?:page|layout|template|loading|error|not-found|global-error|default|route)\.[cm]?[tj]sx?$/.test(
116
+ clean
117
+ );
118
+ }
119
+ function assertNoRSCBoundaryViolation(code, id) {
120
+ const violation = findRSCBoundaryViolation(code, id);
121
+ if (!violation) {
122
+ return;
123
+ }
124
+ throw new Error(formatRSCViolation(violation));
125
+ }
126
+ function formatRSCViolation(violation) {
127
+ return `csszyxRSCViolation: ${violation.symbol} imported in Server Component ${violation.path}
128
+ Import chain: ${violation.importChain.join(" -> ")}`;
129
+ }
130
+ function walkRSCGraph(current, records, chain, seen) {
131
+ if (current.isClient) {
132
+ return null;
133
+ }
134
+ const runtime = current.runtimeImports[0];
135
+ const symbol = runtime?.symbols.find((s) => FORBIDDEN_SYMBOLS.has(s));
136
+ if (runtime && symbol) {
137
+ return {
138
+ symbol,
139
+ path: chain[0] ?? current.id,
140
+ importChain: [...chain, runtime.source]
141
+ };
142
+ }
143
+ for (const importedId of current.imports) {
144
+ if (seen.has(importedId)) {
145
+ continue;
146
+ }
147
+ const next = records.get(importedId);
148
+ if (!next) {
149
+ continue;
150
+ }
151
+ seen.add(importedId);
152
+ const violation = walkRSCGraph(next, records, [...chain, importedId], seen);
153
+ if (violation) {
154
+ return violation;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ function readDirectivePrologue(code) {
160
+ const out = [];
161
+ let i = code.charCodeAt(0) === 65279 ? 1 : 0;
162
+ while (i < code.length) {
163
+ i = skipWhitespaceAndComments(code, i);
164
+ const quote = code[i];
165
+ if (quote !== '"' && quote !== "'") {
166
+ break;
167
+ }
168
+ let j = i + 1;
169
+ let escaped = false;
170
+ while (j < code.length) {
171
+ const ch = code[j];
172
+ if (escaped) {
173
+ escaped = false;
174
+ } else if (ch === "\\") {
175
+ escaped = true;
176
+ } else if (ch === quote) {
177
+ break;
178
+ }
179
+ j++;
180
+ }
181
+ if (j >= code.length) {
182
+ break;
183
+ }
184
+ let end = j + 1;
185
+ while (end < code.length && /[ \t\r\n]/.test(code[end])) {
186
+ end++;
187
+ }
188
+ if (code[end] === ";") {
189
+ end++;
190
+ }
191
+ out.push(code.slice(i, end).trim());
192
+ i = end;
193
+ }
194
+ return out;
195
+ }
196
+ function skipWhitespaceAndComments(code, start) {
197
+ let i = start;
198
+ while (i < code.length) {
199
+ while (i < code.length && /\s/.test(code[i])) {
200
+ i++;
201
+ }
202
+ if (code.startsWith("//", i)) {
203
+ const next = code.indexOf("\n", i + 2);
204
+ i = next === -1 ? code.length : next + 1;
205
+ continue;
206
+ }
207
+ if (code.startsWith("/*", i)) {
208
+ const next = code.indexOf("*/", i + 2);
209
+ i = next === -1 ? code.length : next + 2;
210
+ continue;
211
+ }
212
+ break;
213
+ }
214
+ return i;
215
+ }
216
+ function findRuntimeImports(code) {
217
+ const imports = [];
218
+ const staticImportRe = /import\s+(?!type\b)([\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
219
+ const sideEffectImportRe = /import\s+['"]([^'"]+)['"]/g;
220
+ const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
221
+ for (const match of code.matchAll(staticImportRe)) {
222
+ const clause = match[1];
223
+ const source = match[2];
224
+ if (!RUNTIME_MODULES.has(source)) {
225
+ continue;
226
+ }
227
+ imports.push({ source, symbols: readImportedSymbols(clause) });
228
+ }
229
+ for (const match of code.matchAll(sideEffectImportRe)) {
230
+ const source = match[1];
231
+ if (RUNTIME_MODULES.has(source)) {
232
+ imports.push({ source, symbols: [] });
233
+ }
234
+ }
235
+ for (const match of code.matchAll(dynamicImportRe)) {
236
+ const source = match[1];
237
+ if (RUNTIME_MODULES.has(source)) {
238
+ imports.push({ source, symbols: Array.from(FORBIDDEN_SYMBOLS) });
239
+ }
240
+ }
241
+ return imports;
242
+ }
243
+ function findLocalImportSources(code) {
244
+ const out = [];
245
+ const staticImportRe = /import\s+(?!type\b)(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
246
+ const exportFromRe = /export\s+(?!type\b)(?:[\s\S]*?)\s+from\s+['"]([^'"]+)['"]/g;
247
+ const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
248
+ for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
249
+ for (const match of code.matchAll(re)) {
250
+ const source = match[1];
251
+ if (source.startsWith(".") || source.startsWith("/")) {
252
+ out.push(source);
253
+ }
254
+ }
255
+ }
256
+ return out;
257
+ }
258
+ function normalizeModuleId(id) {
259
+ const clean = id.split("?")[0] ?? id;
260
+ try {
261
+ return fs.realpathSync.native(clean).replace(/\\/g, "/");
262
+ } catch {
263
+ return path.resolve(clean).replace(/\\/g, "/");
264
+ }
265
+ }
266
+ function resolveLocalModule(importer, source) {
267
+ const base = source.startsWith("/") ? source : path.resolve(path.dirname(importer), source);
268
+ const candidates = [
269
+ base,
270
+ `${base}.tsx`,
271
+ `${base}.ts`,
272
+ `${base}.jsx`,
273
+ `${base}.js`,
274
+ `${base}.mjs`,
275
+ `${base}.cjs`,
276
+ path.join(base, "index.tsx"),
277
+ path.join(base, "index.ts"),
278
+ path.join(base, "index.jsx"),
279
+ path.join(base, "index.js")
280
+ ];
281
+ for (const candidate of candidates) {
282
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
283
+ return normalizeModuleId(candidate);
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+ function readImportedSymbols(clause) {
289
+ const symbols = [];
290
+ const named = clause.match(/\{([\s\S]*?)\}/);
291
+ if (named) {
292
+ for (const part of named[1].split(",")) {
293
+ const trimmed = part.trim();
294
+ if (!trimmed || trimmed.startsWith("type ")) {
295
+ continue;
296
+ }
297
+ const sourceName = trimmed.replace(/^type\s+/, "").split(/\s+as\s+/)[0]?.trim();
298
+ if (sourceName) {
299
+ symbols.push(sourceName);
300
+ }
301
+ }
302
+ }
303
+ if (/\*\s+as\s+\w+/.test(clause)) {
304
+ symbols.push(...FORBIDDEN_SYMBOLS);
305
+ }
306
+ return symbols;
307
+ }
308
+
309
+ const EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
7
310
  function stripLayerWrappers(css) {
8
311
  let result = "";
9
312
  let i = 0;
@@ -45,8 +348,7 @@ function stripLayerWrappers(css) {
45
348
  function extractThemeBlocks(css) {
46
349
  const blocks = [];
47
350
  const themeStart = /@theme\s+(?:inline\s+)?\{|@theme\{/g;
48
- let match;
49
- while ((match = themeStart.exec(css)) !== null) {
351
+ for (const match of css.matchAll(themeStart)) {
50
352
  const openPos = css.indexOf("{", match.index);
51
353
  let depth = 0;
52
354
  let j = openPos;
@@ -97,14 +399,12 @@ function parseThemeBlocks(cssContent) {
97
399
  const blocks = extractThemeBlocks(stripped);
98
400
  const propPattern = /--([a-z][a-z0-9-]*)(?:\s*:[^;]+)?;/g;
99
401
  for (const block of blocks) {
100
- let match;
101
- while ((match = propPattern.exec(block)) !== null) {
402
+ for (const match of block.matchAll(propPattern)) {
102
403
  const categorized = categorizeProperty(match[1]);
103
404
  if (categorized) {
104
405
  result[categorized.category].add(categorized.token);
105
406
  }
106
407
  }
107
- propPattern.lastIndex = 0;
108
408
  }
109
409
  return {
110
410
  colors: [...result.colors].sort(),
@@ -144,17 +444,122 @@ function hasTokens(theme) {
144
444
  return Object.values(theme).some((arr) => arr.length > 0);
145
445
  }
146
446
 
147
- // src/unplugin.ts
148
- import * as fs from "fs";
149
- import * as path from "path";
150
- import { transform, transformSourceCode } from "@csszyx/compiler";
151
- import { compute_mangle_checksum, encode } from "@csszyx/core";
152
- import { preprocess as sveltePreprocess } from "@csszyx/svelte-adapter";
153
- import { preprocess as vuePreprocess } from "@csszyx/vue-adapter";
154
- import { createUnplugin } from "unplugin";
447
+ const GLOB_MAGIC_RE = /[*?[\]{}]/;
448
+ const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", ".turbo", "dist", "build"]);
449
+ function normalizeFileId(id) {
450
+ return id.split(/[?#]/, 1)[0].replace(/\\/g, "/");
451
+ }
452
+ function normalizeRoot(rootDir) {
453
+ return path.resolve(rootDir).replace(/\\/g, "/");
454
+ }
455
+ function hasGlobMagic(pattern) {
456
+ return GLOB_MAGIC_RE.test(pattern);
457
+ }
458
+ function escapeRegExp(ch) {
459
+ return /[|\\{}()[\]^$+?.]/.test(ch) ? `\\${ch}` : ch;
460
+ }
461
+ function globToRegExp(pattern) {
462
+ const normalized = pattern.replace(/\\/g, "/");
463
+ let out = "^";
464
+ for (let i = 0; i < normalized.length; i++) {
465
+ const ch = normalized[i];
466
+ if (ch === "*") {
467
+ if (normalized[i + 1] === "*") {
468
+ out += ".*";
469
+ i++;
470
+ } else {
471
+ out += "[^/]*";
472
+ }
473
+ } else if (ch === "?") {
474
+ out += "[^/]";
475
+ } else {
476
+ out += escapeRegExp(ch);
477
+ }
478
+ }
479
+ out += "$";
480
+ return new RegExp(out);
481
+ }
482
+ function relativeToRoot(file, rootDir) {
483
+ const root = normalizeRoot(rootDir);
484
+ const normalizedFile = normalizeFileId(file);
485
+ return path.posix.relative(root, normalizedFile).replace(/\\/g, "/");
486
+ }
487
+ function matchesPattern(id, pattern, rootDir) {
488
+ const file = normalizeFileId(id);
489
+ const relative = relativeToRoot(file, rootDir);
490
+ if (pattern instanceof RegExp) {
491
+ pattern.lastIndex = 0;
492
+ return pattern.test(file) || pattern.test(relative);
493
+ }
494
+ const normalizedPattern = pattern.replace(/\\/g, "/");
495
+ if (hasGlobMagic(normalizedPattern)) {
496
+ const re = globToRegExp(normalizedPattern);
497
+ return re.test(file) || re.test(relative);
498
+ }
499
+ const absolutePattern = path.isAbsolute(normalizedPattern) ? normalizeFileId(normalizedPattern) : normalizeFileId(path.join(rootDir, normalizedPattern));
500
+ return file === absolutePattern || relative === normalizedPattern;
501
+ }
502
+ function matchesAnyPattern(id, patterns, rootDir) {
503
+ if (!patterns) {
504
+ return false;
505
+ }
506
+ const list = Array.isArray(patterns) ? patterns : [patterns];
507
+ return list.some((pattern) => matchesPattern(id, pattern, rootDir));
508
+ }
509
+ function expandFilePatterns(rootDir, patterns) {
510
+ const list = Array.isArray(patterns) ? patterns : [patterns];
511
+ const files = /* @__PURE__ */ new Set();
512
+ const walk = (dir) => {
513
+ let entries;
514
+ try {
515
+ entries = fs.readdirSync(dir, { withFileTypes: true });
516
+ } catch {
517
+ return;
518
+ }
519
+ for (const entry of entries) {
520
+ const full = path.join(dir, entry.name);
521
+ if (entry.isDirectory()) {
522
+ if (!DEFAULT_IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
523
+ walk(full);
524
+ }
525
+ } else {
526
+ files.add(path.resolve(full));
527
+ }
528
+ }
529
+ };
530
+ let needsWalk = false;
531
+ for (const pattern of list) {
532
+ const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
533
+ if (hasGlobMagic(pattern)) {
534
+ needsWalk = true;
535
+ continue;
536
+ }
537
+ if (fs.existsSync(resolved)) {
538
+ files.add(path.resolve(resolved));
539
+ }
540
+ }
541
+ if (needsWalk) {
542
+ walk(rootDir);
543
+ for (const file of Array.from(files)) {
544
+ if (!list.some(
545
+ (pattern) => hasGlobMagic(pattern) && matchesPattern(file, pattern, rootDir)
546
+ )) {
547
+ const isLiteralMatch = list.some((pattern) => {
548
+ if (hasGlobMagic(pattern)) {
549
+ return false;
550
+ }
551
+ const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
552
+ return normalizeFileId(path.resolve(resolved)) === normalizeFileId(file);
553
+ });
554
+ if (!isLiteralMatch) {
555
+ files.delete(file);
556
+ }
557
+ }
558
+ }
559
+ }
560
+ return Array.from(files).sort();
561
+ }
155
562
 
156
- // src/html-transformer.ts
157
- import { createHash } from "crypto";
158
563
  function injectChecksum(html, checksum, minify = false) {
159
564
  const attrName = minify ? "data-sz-cs" : "data-sz-checksum";
160
565
  const htmlTagPattern = /<html([^>]*)>/i;
@@ -164,16 +569,13 @@ function injectChecksum(html, checksum, minify = false) {
164
569
  }
165
570
  const existingAttrs = match[1];
166
571
  const checksumAttr = ` ${attrName}="${checksum}"`;
167
- return html.replace(
168
- htmlTagPattern,
169
- `<html${checksumAttr}${existingAttrs}>`
170
- );
572
+ return html.replace(htmlTagPattern, `<html${checksumAttr}${existingAttrs}>`);
171
573
  }
172
574
  function injectMangleMapScript(html, mangleMap, options = {}) {
173
575
  const { prettyPrint = false } = options;
174
576
  const jsonContent = prettyPrint ? JSON.stringify(mangleMap, null, 2) : JSON.stringify(mangleMap);
175
- const scriptTag = `<script id="__CSSZYX_MANGLE_MAP__" type="application/json">${jsonContent}</script>`;
176
- const debugScript = `<script>(function(){var m=${jsonContent};var r={};for(var k in m)r[m[k]]=k;var cs=document.documentElement.getAttribute("data-sz-checksum")||"";window.__csszyx={mangleMap:m,checksum:cs,decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()</script>`;
577
+ const scriptTag = `<script id="__CSSZYX_MANGLE_MAP__" type="application/json">${jsonContent}<\/script>`;
578
+ const debugScript = `<script>(function(){var m=${jsonContent};var r={};for(var k in m)r[m[k]]=k;var cs=document.documentElement.getAttribute("data-sz-checksum")||"";window.__csszyx={mangleMap:m,checksum:cs,decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()<\/script>`;
177
579
  const combined = `${scriptTag}
178
580
  ${debugScript}`;
179
581
  if (html.includes("</head>")) {
@@ -214,7 +616,7 @@ function injectHydrationData(html, mangleMap, checksum, options = {}) {
214
616
  function transformIndexHtml(html, mangleMap, checksum, options = {}) {
215
617
  return injectHydrationData(html, mangleMap, checksum, options);
216
618
  }
217
- function buildRecoveryManifest(tokens, options = {}) {
619
+ function buildRecoveryManifest(tokens, options) {
218
620
  const stripped = options.production === true;
219
621
  const strippedDevOnlyPaths = [];
220
622
  const sorted = {};
@@ -235,7 +637,7 @@ function buildRecoveryManifest(tokens, options = {}) {
235
637
  const checksum = fullChecksum.substring(0, 16);
236
638
  const buildId = `${Date.now().toString(36)}-${fullChecksum.substring(0, 6)}`;
237
639
  return {
238
- manifest: { buildId, checksum, tokens: sorted },
640
+ manifest: { buildId, checksum, mangleChecksum: options.mangleChecksum, tokens: sorted },
239
641
  strippedDevOnlyPaths
240
642
  };
241
643
  }
@@ -244,7 +646,7 @@ function injectRecoveryManifest(html, manifest) {
244
646
  return html;
245
647
  }
246
648
  const json = JSON.stringify(manifest);
247
- const scriptTag = `<script id="__SZ_RECOVERY_MANIFEST__" type="application/json">${json}</script>`;
649
+ const scriptTag = `<script id="__SZ_RECOVERY_MANIFEST__" type="application/json">${json}<\/script>`;
248
650
  if (html.includes("</head>")) {
249
651
  return html.replace("</head>", `${scriptTag}
250
652
  </head>`);
@@ -255,9 +657,6 @@ function injectRecoveryManifest(html, manifest) {
255
657
  return html + scriptTag;
256
658
  }
257
659
 
258
- // src/theme-type-writer.ts
259
- import { mkdirSync, writeFileSync } from "fs";
260
- import { dirname } from "path";
261
660
  function generateThemeDts(opts) {
262
661
  const { theme, sourceFiles } = opts;
263
662
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -304,11 +703,10 @@ function writeThemeDts(opts) {
304
703
  writeFileSync(opts.outputPath, content, "utf-8");
305
704
  }
306
705
 
307
- // src/virtual-modules.ts
308
- var VIRTUAL_MODULE_ID = "virtual:csszyx/mangle-map";
309
- var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
310
- var VIRTUAL_CHECKSUM_ID = "virtual:csszyx/checksum";
311
- var RESOLVED_VIRTUAL_CHECKSUM_ID = "\0" + VIRTUAL_CHECKSUM_ID;
706
+ const VIRTUAL_MODULE_ID = "virtual:csszyx/mangle-map";
707
+ const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
708
+ const VIRTUAL_CHECKSUM_ID = "virtual:csszyx/checksum";
709
+ const RESOLVED_VIRTUAL_CHECKSUM_ID = `\0${VIRTUAL_CHECKSUM_ID}`;
312
710
  function createMangleMapModule(mangleMap, checksum) {
313
711
  return `/**
314
712
  * Auto-generated mangle map for csszyx.
@@ -353,22 +751,14 @@ function resolveVirtualModule(id) {
353
751
  return void 0;
354
752
  }
355
753
 
356
- // src/unplugin.ts
357
- var CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
358
- var MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
359
- var _hasWarnedTsConfig = false;
754
+ const CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
755
+ const MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
756
+ let _hasWarnedTsConfig = false;
360
757
  function runThemeScan(rootDir, scanCss) {
361
758
  if (!scanCss) {
362
759
  return;
363
760
  }
364
- const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
365
- const sourceFiles = [];
366
- for (const pattern of patterns) {
367
- const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
368
- if (fs.existsSync(resolved)) {
369
- sourceFiles.push(resolved);
370
- }
371
- }
761
+ const sourceFiles = expandFilePatterns(rootDir, scanCss).filter((file) => file.endsWith(".css"));
372
762
  if (sourceFiles.length === 0) {
373
763
  return;
374
764
  }
@@ -389,9 +779,11 @@ function runThemeScan(rootDir, scanCss) {
389
779
  if (fs.existsSync(cfgPath)) {
390
780
  const content = fs.readFileSync(cfgPath, "utf-8");
391
781
  if (!content.includes(".csszyx")) {
392
- console.warn(`
782
+ console.warn(
783
+ `
393
784
  \x1B[33m\u26A0\uFE0F CSSzyx: Theme Auto-Scan enabled, but TypeScript isn't configured. Run "npx @csszyx/cli init" to fix.\x1B[0m
394
- `);
785
+ `
786
+ );
395
787
  }
396
788
  return true;
397
789
  }
@@ -483,12 +875,12 @@ function mangleCodeClassesSync(code, mangleMap) {
483
875
  return qm;
484
876
  }
485
877
  changed = true;
486
- return '"' + m + '"';
878
+ return `"${m}"`;
487
879
  });
488
- out += "${" + mangledInner + "}";
880
+ out += `\${${mangledInner}}`;
489
881
  i = j;
490
882
  }
491
- return changed ? "className:`" + out + "`" : fullMatch;
883
+ return changed ? `className:\`${out}\`` : fullMatch;
492
884
  });
493
885
  {
494
886
  const marker = "className:";
@@ -543,7 +935,7 @@ function mangleCodeClassesSync(code, mangleMap) {
543
935
  const mangledStr = parts.map((p) => mangleMap[p] || p).join(" ");
544
936
  if (mangledStr !== inner) {
545
937
  changed = true;
546
- return '"' + mangledStr + '"';
938
+ return `"${mangledStr}"`;
547
939
  }
548
940
  return qm;
549
941
  });
@@ -572,24 +964,58 @@ function mangleCodeClassesSync(code, mangleMap) {
572
964
  if (!changed) {
573
965
  return match;
574
966
  }
575
- return '"' + mangled.join(" ") + '"';
967
+ return `"${mangled.join(" ")}"`;
576
968
  });
577
969
  return result;
578
970
  }
579
971
  function createCsszyxPlugins(options = {}) {
580
972
  const manglingEnabled = options.production?.mangle !== false;
581
973
  const astBudgetOverride = options.build?.astBudgetLimit;
974
+ const parserOverride = process.env.CSSZYX_PARSER;
975
+ const parserMode = parserOverride === "babel" || parserOverride === "oxc" ? parserOverride : options.build?.parser ?? "oxc";
582
976
  const state = {
583
977
  classes: /* @__PURE__ */ new Set(),
584
978
  mangleMap: {},
585
979
  checksum: "",
586
980
  finalized: false,
587
981
  rootDir: process.cwd(),
588
- recoveryTokens: /* @__PURE__ */ new Map()
982
+ recoveryTokens: /* @__PURE__ */ new Map(),
983
+ rscModules: /* @__PURE__ */ new Map()
589
984
  };
590
985
  const SAFELIST_FILENAME = "csszyx-classes.html";
591
986
  const SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
592
987
  const IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "dist", "build", ".turbo"]);
988
+ function isUserExcluded(id) {
989
+ return matchesAnyPattern(id, options.exclude, state.rootDir);
990
+ }
991
+ function isUserIncluded(id) {
992
+ return !options.include || matchesAnyPattern(id, options.include, state.rootDir);
993
+ }
994
+ function isHardIgnored(id) {
995
+ return id.includes("node_modules") || id.includes("/packages/") || id.includes(".next") && !id.includes("static");
996
+ }
997
+ function shouldProcessSource(id) {
998
+ return !isHardIgnored(id) && !isUserExcluded(id) && isUserIncluded(id) && (/\.[tj]sx?(\?.*)?$/.test(id) || id.endsWith(".vue") || id.endsWith(".svelte"));
999
+ }
1000
+ function shouldProcessCss(id) {
1001
+ return !isHardIgnored(id) && !isUserExcluded(id) && /\.css(\?.*)?$/.test(id);
1002
+ }
1003
+ function transformConfiguredSource(source, filename) {
1004
+ const compilerOptions = { astBudget: astBudgetOverride };
1005
+ if (parserMode !== "oxc") {
1006
+ return transformSourceCode(source, filename, compilerOptions);
1007
+ }
1008
+ try {
1009
+ return transformOxc(source, filename, compilerOptions);
1010
+ } catch (err) {
1011
+ const result = transformSourceCode(source, filename, compilerOptions);
1012
+ const reason = err instanceof Error ? err.message : String(err);
1013
+ result.diagnostics.push(
1014
+ `[csszyx] oxc parser fell back to Babel for ${filename}: ${reason}`
1015
+ );
1016
+ return result;
1017
+ }
1018
+ }
593
1019
  function writeSafelistFile(classes) {
594
1020
  if (classes.size === 0) {
595
1021
  return;
@@ -625,12 +1051,15 @@ function createCsszyxPlugins(options = {}) {
625
1051
  }
626
1052
  } else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
627
1053
  const filePath = path.join(dir, entry.name);
1054
+ if (!shouldProcessSource(filePath)) {
1055
+ continue;
1056
+ }
628
1057
  try {
629
1058
  const content = fs.readFileSync(filePath, "utf-8");
630
1059
  if (!content.includes("sz=") && !content.includes("sz:")) {
631
1060
  continue;
632
1061
  }
633
- const result = transformSourceCode(content, filePath, { astBudget: astBudgetOverride });
1062
+ const result = transformConfiguredSource(content, filePath);
634
1063
  if (!result.transformed) {
635
1064
  continue;
636
1065
  }
@@ -645,10 +1074,9 @@ function createCsszyxPlugins(options = {}) {
645
1074
  }
646
1075
  if (result.usesRuntime) {
647
1076
  const szCallRe = /_sz\(\s*\{/g;
648
- let szMatch;
649
- while ((szMatch = szCallRe.exec(result.code)) !== null) {
1077
+ for (const szMatch of result.code.matchAll(szCallRe)) {
650
1078
  let depth = 1;
651
- let idx = szMatch.index + szMatch[0].length;
1079
+ let idx = (szMatch.index ?? 0) + szMatch[0].length;
652
1080
  while (idx < result.code.length && depth > 0) {
653
1081
  if (result.code[idx] === "{") {
654
1082
  depth++;
@@ -657,10 +1085,12 @@ function createCsszyxPlugins(options = {}) {
657
1085
  }
658
1086
  idx++;
659
1087
  }
660
- const objStr = result.code.slice(szMatch.index + szMatch[0].length, idx - 1);
1088
+ const objStr = result.code.slice(
1089
+ (szMatch.index ?? 0) + szMatch[0].length,
1090
+ idx - 1
1091
+ );
661
1092
  const strKv = /(\w+)\s*:\s*(?:"([^"]*)"|'([^']*)')/g;
662
- let kv;
663
- while ((kv = strKv.exec(objStr)) !== null) {
1093
+ for (const kv of objStr.matchAll(strKv)) {
664
1094
  try {
665
1095
  const val = kv[2] ?? kv[3];
666
1096
  const r = transform({ [kv[1]]: val });
@@ -671,7 +1101,7 @@ function createCsszyxPlugins(options = {}) {
671
1101
  }
672
1102
  }
673
1103
  const numKv = /(\w+)\s*:\s*(-?\d+(?:\.\d+)?)\s*(?=[,}\n])/g;
674
- while ((kv = numKv.exec(objStr)) !== null) {
1104
+ for (const kv of objStr.matchAll(numKv)) {
675
1105
  try {
676
1106
  const r = transform({ [kv[1]]: parseFloat(kv[2]) });
677
1107
  for (const c of r.className.split(/\s+/).filter(Boolean)) {
@@ -681,7 +1111,7 @@ function createCsszyxPlugins(options = {}) {
681
1111
  }
682
1112
  }
683
1113
  const boolKv = /(\w+)\s*:\s*(true|false)\s*(?=[,}\n])/g;
684
- while ((kv = boolKv.exec(objStr)) !== null) {
1114
+ for (const kv of objStr.matchAll(boolKv)) {
685
1115
  try {
686
1116
  const r = transform({ [kv[1]]: kv[2] === "true" });
687
1117
  for (const c of r.className.split(/\s+/).filter(Boolean)) {
@@ -707,9 +1137,8 @@ function createCsszyxPlugins(options = {}) {
707
1137
  function extractClasses(code) {
708
1138
  const dqPattern = /(?:class(?:Name)?|sz)[:=]\s*"([^"]*)"/g;
709
1139
  const sqPattern = /(?:class(?:Name)?|sz)[:=]\s*'([^']*)'/g;
710
- let match;
711
1140
  for (const classPattern of [dqPattern, sqPattern]) {
712
- while ((match = classPattern.exec(code)) !== null) {
1141
+ for (const match of code.matchAll(classPattern)) {
713
1142
  const classes = match[1].split(/\s+/).filter(Boolean);
714
1143
  for (const cls of classes) {
715
1144
  state.classes.add(cls);
@@ -717,9 +1146,9 @@ function createCsszyxPlugins(options = {}) {
717
1146
  }
718
1147
  }
719
1148
  const exprStart = /className=\{/g;
720
- while ((match = exprStart.exec(code)) !== null) {
1149
+ for (const match of code.matchAll(exprStart)) {
721
1150
  let depth = 1;
722
- let i = match.index + match[0].length;
1151
+ let i = (match.index ?? 0) + match[0].length;
723
1152
  while (i < code.length && depth > 0) {
724
1153
  if (code[i] === "{") {
725
1154
  depth++;
@@ -728,10 +1157,9 @@ function createCsszyxPlugins(options = {}) {
728
1157
  }
729
1158
  i++;
730
1159
  }
731
- const expr = code.slice(match.index + match[0].length, i - 1);
1160
+ const expr = code.slice((match.index ?? 0) + match[0].length, i - 1);
732
1161
  const strPattern = /"([^"]+)"|'([^']+)'/g;
733
- let strMatch;
734
- while ((strMatch = strPattern.exec(expr)) !== null) {
1162
+ for (const strMatch of expr.matchAll(strPattern)) {
735
1163
  const str = strMatch[1] || strMatch[2];
736
1164
  const classes = str.split(/\s+/).filter(Boolean);
737
1165
  for (const cls of classes) {
@@ -765,313 +1193,327 @@ function createCsszyxPlugins(options = {}) {
765
1193
  }
766
1194
  return result;
767
1195
  }
768
- const prePlugin = createUnplugin((_pluginOptions) => ({
769
- name: "csszyx:pre",
770
- enforce: "pre",
771
- /**
772
- * Resolves virtual module IDs for csszyx mangle-map and checksum modules.
773
- * @param id - the module ID to resolve
774
- * @returns resolved ID if virtual, null otherwise
775
- */
776
- resolveId(id) {
777
- if (isVirtualModule(id)) {
778
- return resolveVirtualModule(id);
779
- }
780
- return null;
781
- },
782
- /**
783
- * Loads virtual module content — generates mangle map or checksum module code.
784
- * @param id - the resolved module ID to load
785
- * @returns generated module source if virtual, null otherwise
786
- */
787
- load(id) {
788
- if (id === RESOLVED_VIRTUAL_MODULE_ID) {
789
- finalizeMangleMap();
790
- return createMangleMapModule(state.mangleMap, state.checksum);
791
- }
792
- if (id === RESOLVED_VIRTUAL_CHECKSUM_ID) {
793
- finalizeMangleMap();
794
- return createChecksumModule(state.checksum);
795
- }
796
- return null;
797
- },
798
- /**
799
- * Filters files for the pre-transform phase — source files plus CSS files.
800
- * CSS files need special handling to inject @source inline() for Tailwind class discovery.
801
- * @param id - the file path to check for inclusion
802
- * @returns true if the file should be transformed, false otherwise
803
- */
804
- transformInclude(id) {
805
- if (id.includes("node_modules") || id.includes("/packages/") || id.includes(".next") && !id.includes("static")) {
806
- return false;
807
- }
808
- if (/\.css(\?.*)?$/.test(id)) {
809
- return true;
810
- }
811
- return /\.[tj]sx?$/.test(id) || id.endsWith(".vue") || id.endsWith(".svelte");
812
- },
813
- /**
814
- * Core transform: detects sz prop, compiles to className, injects runtime, collects classes.
815
- * For CSS files: injects @source inline() so Tailwind generates CSS for sz-derived classes.
816
- * @param code - the source code to transform
817
- * @param id - the file path of the module being transformed
818
- * @returns transformed code with source map, or null if no changes were made
819
- */
820
- transform(code, id) {
821
- if (/\.css(\?.*)?$/.test(id)) {
822
- const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
823
- if (hasTailwindImport && state.classes.size > 0) {
824
- const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
825
- if (candidates) {
826
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
827
- const cssDir = path.dirname(id).replace(/\\/g, "/");
828
- let relPath = path.posix.relative(cssDir, safelistPath);
829
- if (!relPath.startsWith(".")) {
830
- relPath = "./" + relPath;
831
- }
832
- const sourceDirective = `@source "${relPath}";
1196
+ const prePlugin = createUnplugin(
1197
+ (_pluginOptions) => ({
1198
+ name: "csszyx:pre",
1199
+ enforce: "pre",
1200
+ /**
1201
+ * Resolves virtual module IDs for csszyx mangle-map and checksum modules.
1202
+ * @param id - the module ID to resolve
1203
+ * @returns resolved ID if virtual, null otherwise
1204
+ */
1205
+ resolveId(id) {
1206
+ if (isVirtualModule(id)) {
1207
+ return resolveVirtualModule(id);
1208
+ }
1209
+ return null;
1210
+ },
1211
+ /**
1212
+ * Loads virtual module content generates mangle map or checksum module code.
1213
+ * @param id - the resolved module ID to load
1214
+ * @returns generated module source if virtual, null otherwise
1215
+ */
1216
+ load(id) {
1217
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
1218
+ finalizeMangleMap();
1219
+ return createMangleMapModule(state.mangleMap, state.checksum);
1220
+ }
1221
+ if (id === RESOLVED_VIRTUAL_CHECKSUM_ID) {
1222
+ finalizeMangleMap();
1223
+ return createChecksumModule(state.checksum);
1224
+ }
1225
+ return null;
1226
+ },
1227
+ /**
1228
+ * Filters files for the pre-transform phase source files plus CSS files.
1229
+ * CSS files need special handling to inject @source inline() for Tailwind class discovery.
1230
+ * @param id - the file path to check for inclusion
1231
+ * @returns true if the file should be transformed, false otherwise
1232
+ */
1233
+ transformInclude(id) {
1234
+ if (shouldProcessCss(id)) {
1235
+ return true;
1236
+ }
1237
+ return shouldProcessSource(id);
1238
+ },
1239
+ /**
1240
+ * Core transform: detects sz prop, compiles to className, injects runtime, collects classes.
1241
+ * For CSS files: injects @source inline() so Tailwind generates CSS for sz-derived classes.
1242
+ * @param code - the source code to transform
1243
+ * @param id - the file path of the module being transformed
1244
+ * @returns transformed code with source map, or null if no changes were made
1245
+ */
1246
+ transform(code, id) {
1247
+ if (!shouldProcessCss(id) && !shouldProcessSource(id)) {
1248
+ return null;
1249
+ }
1250
+ if (/\.[tj]sx?(\?.*)?$/.test(id)) {
1251
+ assertNoRSCBoundaryViolation(code, id);
1252
+ }
1253
+ if (/\.css(\?.*)?$/.test(id)) {
1254
+ const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
1255
+ if (hasTailwindImport && state.classes.size > 0) {
1256
+ const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
1257
+ if (candidates) {
1258
+ const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
1259
+ const cssDir = path.dirname(id).replace(/\\/g, "/");
1260
+ let relPath = path.posix.relative(cssDir, safelistPath);
1261
+ if (!relPath.startsWith(".")) {
1262
+ relPath = `./${relPath}`;
1263
+ }
1264
+ const sourceDirective = `@source "${relPath}";
833
1265
  `;
834
- const transformed2 = code.replace(
835
- /(@import\s+["']tailwindcss[^"']*["'];)/,
836
- `$1
1266
+ const transformed2 = code.replace(
1267
+ /(@import\s+["']tailwindcss[^"']*["'];)/,
1268
+ `$1
837
1269
  ${sourceDirective}`
838
- );
839
- if (transformed2 !== code) {
840
- return { code: transformed2, map: null };
1270
+ );
1271
+ if (transformed2 !== code) {
1272
+ return { code: transformed2, map: null };
1273
+ }
841
1274
  }
842
1275
  }
1276
+ return null;
843
1277
  }
844
- return null;
845
- }
846
- let transformedCode = code;
847
- let usesRuntime = false;
848
- let usesMerge = false;
849
- let usesColorVar = false;
850
- let transformed = false;
851
- let szClasses;
852
- const hasSzProp = code.includes("sz=") || /\bsz\s*:\s*["'{]/.test(code) || code.includes('sz: "');
853
- if (hasSzProp) {
854
- if (id.endsWith(".vue")) {
855
- const result = vuePreprocess(code, options);
856
- if (result.transformed) {
857
- transformedCode = result.code;
858
- transformed = true;
859
- }
860
- } else if (id.endsWith(".svelte")) {
861
- const result = sveltePreprocess(code, options);
862
- if (result) {
1278
+ let transformedCode = code;
1279
+ let usesRuntime = false;
1280
+ let usesMerge = false;
1281
+ let usesColorVar = false;
1282
+ let transformed = false;
1283
+ let szClasses;
1284
+ const hasSzProp = code.includes("sz=") || /\bsz\s*:\s*["'{]/.test(code) || code.includes('sz: "');
1285
+ if (hasSzProp) {
1286
+ if (id.endsWith(".vue")) {
1287
+ const result = preprocess(code, options);
1288
+ if (result.transformed) {
1289
+ transformedCode = result.code;
1290
+ transformed = true;
1291
+ }
1292
+ } else if (id.endsWith(".svelte")) {
1293
+ const result = preprocess$1(code, options);
1294
+ if (result) {
1295
+ transformedCode = result.code;
1296
+ transformed = true;
1297
+ }
1298
+ } else {
1299
+ const result = transformConfiguredSource(code, id);
863
1300
  transformedCode = result.code;
864
- transformed = true;
865
- }
866
- } else {
867
- const result = transformSourceCode(code, id, { astBudget: astBudgetOverride });
868
- transformedCode = result.code;
869
- usesRuntime = result.usesRuntime;
870
- usesMerge = result.usesMerge;
871
- usesColorVar = result.usesColorVar;
872
- transformed = result.transformed;
873
- szClasses = result.classes;
874
- if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
875
- for (const msg of result.diagnostics) {
876
- this.warn(`[csszyx] ${id}
1301
+ usesRuntime = result.usesRuntime;
1302
+ usesMerge = result.usesMerge;
1303
+ usesColorVar = result.usesColorVar;
1304
+ transformed = result.transformed;
1305
+ szClasses = result.classes;
1306
+ if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
1307
+ for (const msg of result.diagnostics) {
1308
+ this.warn(`[csszyx] ${id}
877
1309
  ${msg}`);
1310
+ }
1311
+ }
1312
+ for (const [token, data] of result.recoveryTokens) {
1313
+ state.recoveryTokens.set(token, data);
878
1314
  }
879
- }
880
- for (const [token, data] of result.recoveryTokens) {
881
- state.recoveryTokens.set(token, data);
882
1315
  }
883
1316
  }
884
- }
885
- if (transformedCode.includes("<html") && /layout|Root|Document|app\\.tsx?$/i.test(id)) {
886
- const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
887
- transformedCode = transformedCode.replace(/<html([^>]*)>/i, `<html$1 ${attrName}="${CHECKSUM_PLACEHOLDER}">`);
888
- const debugScript = `<script dangerouslySetInnerHTML={{__html: \`(function(){var m=${MANGLE_MAP_PLACEHOLDER};var r={};for(var k in m)r[m[k]]=k;window.__csszyx={mangleMap:m,checksum:"${CHECKSUM_PLACEHOLDER}",decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()\`}} />`;
889
- if (transformedCode.includes("<body")) {
1317
+ if (transformedCode.includes("<html") && /layout|Root|Document|app\\.tsx?$/i.test(id)) {
1318
+ const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
890
1319
  transformedCode = transformedCode.replace(
891
- /(<body[^>]*>)/i,
892
- `$1${debugScript}`
1320
+ /<html([^>]*)>/i,
1321
+ `<html$1 ${attrName}="${CHECKSUM_PLACEHOLDER}">`
893
1322
  );
894
- }
895
- transformed = true;
896
- }
897
- {
898
- const imports = [];
899
- if (usesRuntime) {
900
- imports.push("_sz");
901
- }
902
- if (usesMerge) {
903
- imports.push("_szMerge");
904
- }
905
- if (usesColorVar) {
906
- imports.push("__szColorVar");
907
- }
908
- const needed = imports.filter(
909
- (name) => !new RegExp(`\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]@csszyx/runtime['"]`).test(transformedCode)
910
- );
911
- if (needed.length > 0) {
912
- const existingImport = transformedCode.match(/^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m);
913
- if (existingImport) {
1323
+ const debugScript = `<script dangerouslySetInnerHTML={{__html: \`(function(){var m=${MANGLE_MAP_PLACEHOLDER};var r={};for(var k in m)r[m[k]]=k;window.__csszyx={mangleMap:m,checksum:"${CHECKSUM_PLACEHOLDER}",decode:function(c){return r[c]},encode:function(c){return m[c]},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()\`}} />`;
1324
+ if (transformedCode.includes("<body")) {
914
1325
  transformedCode = transformedCode.replace(
915
- existingImport[0],
916
- `${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
1326
+ /(<body[^>]*>)/i,
1327
+ `$1${debugScript}`
917
1328
  );
918
- } else {
919
- const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
920
- `;
921
- const directiveMatch = transformedCode.match(/^['"]use (client|server)['"];?\s*/);
922
- if (directiveMatch) {
923
- const directive = directiveMatch[0];
924
- transformedCode = transformedCode.replace(directive, `${directive}${importStmt}`);
925
- } else {
926
- transformedCode = `${importStmt}${transformedCode}`;
927
- }
928
1329
  }
929
1330
  transformed = true;
930
1331
  }
931
- }
932
- if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
933
- if (szClasses !== void 0) {
934
- for (const cls of szClasses) {
935
- state.classes.add(cls);
1332
+ {
1333
+ const imports = [];
1334
+ if (usesRuntime) {
1335
+ imports.push("_sz");
1336
+ }
1337
+ if (usesMerge) {
1338
+ imports.push("_szMerge");
1339
+ }
1340
+ if (usesColorVar) {
1341
+ imports.push("__szColorVar");
1342
+ }
1343
+ const needed = imports.filter(
1344
+ (name) => !new RegExp(
1345
+ `\\{[^}]*\\b${name}\\b[^}]*\\}\\s*from\\s*['"]@csszyx/runtime['"]`
1346
+ ).test(transformedCode)
1347
+ );
1348
+ if (needed.length > 0) {
1349
+ const existingImport = transformedCode.match(
1350
+ /^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m
1351
+ );
1352
+ if (existingImport) {
1353
+ transformedCode = transformedCode.replace(
1354
+ existingImport[0],
1355
+ `${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
1356
+ );
1357
+ } else {
1358
+ const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
1359
+ `;
1360
+ const directiveMatch = transformedCode.match(
1361
+ /^['"]use (client|server)['"];?\s*/
1362
+ );
1363
+ if (directiveMatch) {
1364
+ const directive = directiveMatch[0];
1365
+ transformedCode = transformedCode.replace(
1366
+ directive,
1367
+ `${directive}${importStmt}`
1368
+ );
1369
+ } else {
1370
+ transformedCode = `${importStmt}${transformedCode}`;
1371
+ }
1372
+ }
1373
+ transformed = true;
936
1374
  }
937
- } else {
938
- extractClasses(transformedCode);
939
1375
  }
940
- return { code: transformedCode, map: null };
941
- }
942
- return null;
943
- },
944
- /** Finalizes the mangle map after all source modules have been processed. */
945
- buildEnd() {
946
- finalizeMangleMap();
947
- if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
948
- globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
949
- }
950
- },
951
- /**
952
- * Webpack hook: pre-scans source files before compilation for Tailwind class discovery.
953
- * @param compiler - the Webpack compiler instance
954
- */
955
- webpack(compiler) {
956
- compiler.hooks.beforeCompile.tap("csszyx:prescan", () => {
957
- const root = compiler.context || process.cwd();
958
- state.rootDir = root;
959
- if (state.classes.size === 0) {
960
- prescanAndWriteClasses();
1376
+ if (/\.[tj]sx?(\?.*)?$/.test(id)) {
1377
+ assertNoRSCBoundaryViolation(transformedCode, id);
1378
+ const record = createRSCModuleRecord(transformedCode, id);
1379
+ state.rscModules.set(record.id, record);
961
1380
  }
962
- runThemeScan(root, options.build?.scanCss);
963
- });
964
- if (options.build?.scanCss) {
965
- const patterns = Array.isArray(options.build.scanCss) ? options.build.scanCss : [options.build.scanCss];
966
- compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
967
- const root = compiler.context || process.cwd();
968
- for (const pattern of patterns) {
969
- const resolved = path.isAbsolute(pattern) ? pattern : path.join(root, pattern);
970
- if (fs.existsSync(resolved)) {
971
- compilation.fileDependencies.add(resolved);
1381
+ if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
1382
+ if (szClasses !== void 0) {
1383
+ for (const cls of szClasses) {
1384
+ state.classes.add(cls);
972
1385
  }
1386
+ } else {
1387
+ extractClasses(transformedCode);
973
1388
  }
974
- });
975
- }
976
- },
977
- vite: {
978
- /**
979
- * Vite hook: pre-scans source files when config is resolved.
980
- * Also runs theme scan to generate .csszyx/theme.d.ts if scanCss is configured.
981
- * @param config - the resolved Vite configuration object
982
- */
983
- configResolved(config) {
984
- const root = config.root || process.cwd();
985
- state.rootDir = root;
986
- prescanAndWriteClasses();
987
- runThemeScan(root, options.build?.scanCss);
1389
+ return { code: transformedCode, map: null };
1390
+ }
1391
+ return null;
1392
+ },
1393
+ /** Finalizes the mangle map after all source modules have been processed. */
1394
+ buildEnd() {
1395
+ finalizeMangleMap();
1396
+ assertNoRSCGraphViolation(state.rscModules);
1397
+ if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
1398
+ globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
1399
+ }
988
1400
  },
989
1401
  /**
990
- * Vite HMR hook: re-runs theme scan when a watched CSS file changes,
991
- * and incrementally updates csszyx-classes.html when a source file gains new sz classes.
992
- * @param ctx - HMR context containing the changed file
1402
+ * Webpack hook: pre-scans source files before compilation for Tailwind class discovery.
1403
+ * @param compiler - the Webpack compiler instance
993
1404
  */
994
- handleHotUpdate(ctx) {
995
- const scanCss = options.build?.scanCss;
996
- if (scanCss) {
997
- const patterns = Array.isArray(scanCss) ? scanCss : [scanCss];
998
- const root = ctx.server.config.root || process.cwd();
999
- const isWatched = patterns.some((p) => {
1000
- const resolved = path.isAbsolute(p) ? p : path.join(root, p);
1001
- return ctx.file === resolved;
1002
- });
1003
- if (isWatched) {
1004
- runThemeScan(root, scanCss);
1405
+ webpack(compiler) {
1406
+ compiler.hooks.beforeCompile.tap("csszyx:prescan", () => {
1407
+ const root = compiler.context || process.cwd();
1408
+ state.rootDir = root;
1409
+ if (state.classes.size === 0) {
1410
+ prescanAndWriteClasses();
1005
1411
  }
1006
- }
1007
- if (!SOURCE_EXTENSIONS.has(path.extname(ctx.file))) {
1008
- return;
1009
- }
1010
- if (ctx.file.includes("node_modules")) {
1011
- return;
1012
- }
1013
- let fileContent, result;
1014
- try {
1015
- fileContent = fs.readFileSync(ctx.file, "utf-8");
1016
- } catch {
1017
- return;
1018
- }
1019
- if (!fileContent.includes("sz=") && !/\bsz\s*:\s*["'{]/.test(fileContent)) {
1020
- return;
1021
- }
1022
- try {
1023
- result = transformSourceCode(fileContent, ctx.file, { astBudget: astBudgetOverride });
1024
- } catch {
1025
- return;
1026
- }
1027
- if (!result.transformed) {
1028
- return;
1029
- }
1030
- const sizeBefore = state.classes.size;
1031
- for (const cls of result.classes) {
1032
- state.classes.add(cls);
1033
- }
1034
- for (const [token, data] of result.recoveryTokens) {
1035
- state.recoveryTokens.set(token, data);
1036
- }
1037
- if (state.classes.size > sizeBefore) {
1038
- writeSafelistFile(state.classes);
1039
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
1040
- ctx.server.watcher.emit("change", safelistPath);
1412
+ runThemeScan(root, options.build?.scanCss);
1413
+ });
1414
+ if (options.build?.scanCss) {
1415
+ compiler.hooks.thisCompilation.tap("csszyx:theme-deps", (compilation) => {
1416
+ const root = compiler.context || process.cwd();
1417
+ for (const file of expandFilePatterns(root, options.build?.scanCss ?? [])) {
1418
+ compilation.fileDependencies.add(file);
1419
+ }
1420
+ });
1041
1421
  }
1042
1422
  },
1043
- transformIndexHtml: {
1044
- order: "pre",
1423
+ vite: {
1045
1424
  /**
1046
- * Injects hydration data (mangle map + checksum) into the HTML document.
1047
- * Also mangles class attributes in SSR-rendered HTML so they match mangled CSS selectors.
1048
- * @param html - the raw HTML string to transform
1049
- * @returns transformed HTML with injected hydration data
1425
+ * Vite hook: pre-scans source files when config is resolved.
1426
+ * Also runs theme scan to generate .csszyx/theme.d.ts if scanCss is configured.
1427
+ * @param config - the resolved Vite configuration object
1050
1428
  */
1051
- handler(html) {
1052
- finalizeMangleMap();
1053
- let result = transformIndexHtml(html, state.mangleMap, state.checksum, {
1054
- mode: options.production?.injectChecksum === false ? "script" : "script",
1055
- minify: process.env.NODE_ENV === "production"
1056
- });
1057
- if (state.recoveryTokens.size > 0) {
1058
- const isProduction = process.env.NODE_ENV === "production";
1059
- const { manifest, strippedDevOnlyPaths } = buildRecoveryManifest(
1060
- state.recoveryTokens,
1061
- { production: isProduction }
1062
- );
1063
- if (strippedDevOnlyPaths.length > 0) {
1064
- console.warn(
1065
- `[csszyx] Stripped ${strippedDevOnlyPaths.length} szRecover="dev-only" token(s) from the production manifest. Recovery for these elements is disabled in production by design. Sites: ${strippedDevOnlyPaths.join(", ")}`
1429
+ configResolved(config) {
1430
+ const root = config.root || process.cwd();
1431
+ state.rootDir = root;
1432
+ prescanAndWriteClasses();
1433
+ runThemeScan(root, options.build?.scanCss);
1434
+ },
1435
+ /**
1436
+ * Vite HMR hook: re-runs theme scan when a watched CSS file changes,
1437
+ * and incrementally updates csszyx-classes.html when a source file gains new sz classes.
1438
+ * @param ctx - HMR context containing the changed file
1439
+ */
1440
+ handleHotUpdate(ctx) {
1441
+ const scanCss = options.build?.scanCss;
1442
+ if (scanCss) {
1443
+ const root = ctx.server.config.root || process.cwd();
1444
+ if (matchesAnyPattern(ctx.file, scanCss, root)) {
1445
+ runThemeScan(root, scanCss);
1446
+ }
1447
+ }
1448
+ if (!shouldProcessSource(ctx.file)) {
1449
+ return;
1450
+ }
1451
+ let fileContent, result;
1452
+ try {
1453
+ fileContent = fs.readFileSync(ctx.file, "utf-8");
1454
+ } catch {
1455
+ return;
1456
+ }
1457
+ if (!fileContent.includes("sz=") && !/\bsz\s*:\s*["'{]/.test(fileContent)) {
1458
+ return;
1459
+ }
1460
+ try {
1461
+ result = transformConfiguredSource(fileContent, ctx.file);
1462
+ } catch {
1463
+ return;
1464
+ }
1465
+ if (!result.transformed) {
1466
+ return;
1467
+ }
1468
+ const sizeBefore = state.classes.size;
1469
+ for (const cls of result.classes) {
1470
+ state.classes.add(cls);
1471
+ }
1472
+ for (const [token, data] of result.recoveryTokens) {
1473
+ state.recoveryTokens.set(token, data);
1474
+ }
1475
+ if (state.classes.size > sizeBefore) {
1476
+ writeSafelistFile(state.classes);
1477
+ const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
1478
+ ctx.server.watcher.emit("change", safelistPath);
1479
+ }
1480
+ },
1481
+ transformIndexHtml: {
1482
+ order: "pre",
1483
+ /**
1484
+ * Injects hydration data (mangle map + checksum) into the HTML document.
1485
+ * Also mangles class attributes in SSR-rendered HTML so they match mangled CSS selectors.
1486
+ * @param html - the raw HTML string to transform
1487
+ * @returns transformed HTML with injected hydration data
1488
+ */
1489
+ handler(html) {
1490
+ finalizeMangleMap();
1491
+ let result = transformIndexHtml(html, state.mangleMap, state.checksum, {
1492
+ mode: options.production?.injectChecksum === false ? "script" : "script",
1493
+ minify: process.env.NODE_ENV === "production"
1494
+ });
1495
+ if (state.recoveryTokens.size > 0) {
1496
+ const isProduction = process.env.NODE_ENV === "production";
1497
+ const { manifest, strippedDevOnlyPaths } = buildRecoveryManifest(
1498
+ state.recoveryTokens,
1499
+ {
1500
+ production: isProduction,
1501
+ mangleChecksum: state.checksum
1502
+ }
1066
1503
  );
1504
+ if (strippedDevOnlyPaths.length > 0) {
1505
+ console.warn(
1506
+ `[csszyx] Stripped ${strippedDevOnlyPaths.length} szRecover="dev-only" token(s) from the production manifest. Recovery for these elements is disabled in production by design. Sites: ${strippedDevOnlyPaths.join(", ")}`
1507
+ );
1508
+ }
1509
+ result = injectRecoveryManifest(result, manifest);
1067
1510
  }
1068
- result = injectRecoveryManifest(result, manifest);
1511
+ return result;
1069
1512
  }
1070
- return result;
1071
1513
  }
1072
1514
  }
1073
- }
1074
- }));
1515
+ })
1516
+ );
1075
1517
  const postPlugin = createUnplugin(() => ({
1076
1518
  name: "csszyx:post",
1077
1519
  enforce: "post",
@@ -1083,8 +1525,7 @@ ${sourceDirective}`
1083
1525
  */
1084
1526
  webpack(compiler) {
1085
1527
  compiler.hooks.compilation.tap("csszyx:post", (compilation) => {
1086
- const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE || // eslint-disable-next-line @typescript-eslint/no-explicit-any
1087
- compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE;
1528
+ const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE || compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE;
1088
1529
  compilation.hooks.processAssets.tap(
1089
1530
  {
1090
1531
  name: "csszyx:post",
@@ -1124,19 +1565,24 @@ ${sourceDirective}`
1124
1565
  continue;
1125
1566
  }
1126
1567
  } catch (e) {
1127
- if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") {
1128
- } else {
1568
+ if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") ; else {
1129
1569
  throw e;
1130
1570
  }
1131
1571
  }
1132
1572
  } else if (file.endsWith(".html")) {
1133
- const mangledHtml = source.replace(/\bclass="([^"]*)"/g, (_m, cls) => {
1134
- const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
1135
- return out !== cls ? `class="${out}"` : _m;
1136
- }).replace(/\bclass='([^']*)'/g, (_m, cls) => {
1137
- const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
1138
- return out !== cls ? `class='${out}'` : _m;
1139
- });
1573
+ const mangledHtml = source.replace(
1574
+ /\bclass="([^"]*)"/g,
1575
+ (_m, cls) => {
1576
+ const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
1577
+ return out !== cls ? `class="${out}"` : _m;
1578
+ }
1579
+ ).replace(
1580
+ /\bclass='([^']*)'/g,
1581
+ (_m, cls) => {
1582
+ const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
1583
+ return out !== cls ? `class='${out}'` : _m;
1584
+ }
1585
+ );
1140
1586
  if (mangledHtml !== source) {
1141
1587
  compilation.updateAsset(
1142
1588
  file,
@@ -1205,8 +1651,7 @@ ${sourceDirective}`
1205
1651
  chunk.source = result.css;
1206
1652
  }
1207
1653
  } catch (e) {
1208
- if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") {
1209
- } else {
1654
+ if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") ; else {
1210
1655
  throw e;
1211
1656
  }
1212
1657
  }
@@ -1232,13 +1677,13 @@ ${sourceDirective}`
1232
1677
  }));
1233
1678
  return { prePlugin, postPlugin };
1234
1679
  }
1235
- var defaultInstance = createCsszyxPlugins();
1236
- var unplugin = defaultInstance.prePlugin;
1237
- var vitePlugin = (options = {}) => {
1680
+ const defaultInstance = createCsszyxPlugins();
1681
+ const unplugin = defaultInstance.prePlugin;
1682
+ const vitePlugin = (options = {}) => {
1238
1683
  const { prePlugin, postPlugin } = createCsszyxPlugins(options);
1239
1684
  return [prePlugin.vite(options), postPlugin.vite(options)];
1240
1685
  };
1241
- var webpackPlugin = (options = {}) => {
1686
+ const webpackPlugin = (options = {}) => {
1242
1687
  const { prePlugin, postPlugin } = createCsszyxPlugins(options);
1243
1688
  return {
1244
1689
  /**
@@ -1251,11 +1696,14 @@ var webpackPlugin = (options = {}) => {
1251
1696
  }
1252
1697
  };
1253
1698
  };
1254
- var rollupPlugin = (options = {}) => {
1699
+ const rollupPlugin = (options = {}) => {
1255
1700
  const { prePlugin, postPlugin } = createCsszyxPlugins(options);
1256
- return [prePlugin.rollup(options), postPlugin.rollup(options)];
1701
+ return [
1702
+ prePlugin.rollup(options),
1703
+ postPlugin.rollup(options)
1704
+ ];
1257
1705
  };
1258
- var esbuildPlugin = (options = {}) => {
1706
+ const esbuildPlugin = (options = {}) => {
1259
1707
  const { prePlugin, postPlugin } = createCsszyxPlugins(options);
1260
1708
  return {
1261
1709
  name: "csszyx",
@@ -1271,14 +1719,4 @@ var esbuildPlugin = (options = {}) => {
1271
1719
  };
1272
1720
  };
1273
1721
 
1274
- export {
1275
- parseThemeBlocks,
1276
- mergeThemes,
1277
- hasTokens,
1278
- mangleCodeClassesSync,
1279
- unplugin,
1280
- vitePlugin,
1281
- webpackPlugin,
1282
- rollupPlugin,
1283
- esbuildPlugin
1284
- };
1722
+ export { assertNoRSCBoundaryViolation as a, assertNoRSCGraphViolation as b, createRSCModuleRecord as c, findRSCGraphViolation as d, esbuildPlugin as e, findRSCBoundaryViolation as f, hasUseClientDirective as g, hasTokens as h, hasUseServerDirective as i, isRSCServerModule as j, mergeThemes as k, mangleCodeClassesSync as m, parseThemeBlocks as p, rollupPlugin as r, unplugin as u, vitePlugin as v, webpackPlugin as w };