@csszyx/unplugin 0.10.12 → 0.11.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,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import * as fs from 'node:fs';
2
3
  import { mkdirSync, writeFileSync } from 'node:fs';
3
4
  import { createRequire } from 'node:module';
@@ -5,7 +6,7 @@ import * as path from 'node:path';
5
6
  import { dirname } from 'node:path';
6
7
  import { performance } from 'node:perf_hooks';
7
8
  import { fileURLToPath } from 'node:url';
8
- import { scanGlobalVarUsages, sortStrings as sortStrings$1, ensureRustTransformAvailable, transformSourceCode, transformRust, transformOxc, transformRustBatch, transform, isRustTransformAvailable } from '@csszyx/compiler';
9
+ import { scanGlobalVarUsages, sortStrings as sortStrings$1, ensureRustTransformAvailable, transformSourceCode, transformRust, transformOxc, transformRustBatch, ASTBudgetExceededError, transform, isRustTransformAvailable } from '@csszyx/compiler';
9
10
  import { encode, compute_mangle_checksum } from '@csszyx/core';
10
11
  import { getNativePackageName } from '@csszyx/core/native';
11
12
  import { preprocess as preprocess$1 } from '@csszyx/svelte-adapter';
@@ -14,8 +15,8 @@ import { preprocess } from '@csszyx/vue-adapter';
14
15
  import { createUnplugin } from 'unplugin';
15
16
  import { mangleCSSSync } from '../css-mangler.mjs';
16
17
  import { s as sortStrings, e as escapeHtmlAttribute } from './unplugin.B1mblcm-.mjs';
17
- import { createHash } from 'node:crypto';
18
- import { r as resolveTransformCacheDir, c as createTransformCacheKey, a as readTransformCache, w as writeTransformCache, e as evictOldTransformCacheEntries, b as evictMemoryCacheToBudget } from './unplugin.CGqFVGlB.mjs';
18
+ import { i as importsRuntimeHelper, f as findRuntimeImportClause } from './unplugin.B3RHYokB.mjs';
19
+ import { r as resolveTransformCacheDir, c as createTransformCacheKey, a as readTransformCache, w as writeTransformCache, e as evictOldTransformCacheEntries, b as evictMemoryCacheToBudget } from './unplugin.ByzV6iZE.mjs';
19
20
  import postcss from 'postcss';
20
21
  import valueParser from 'postcss-value-parser';
21
22
 
@@ -802,19 +803,118 @@ function readRuntimeImportSymbols(source, clause) {
802
803
  }
803
804
  function findLocalImportSources(code) {
804
805
  const out = [];
805
- const staticImportRe = /import\s+(?!type\b)(?:\S(?:.*\S)?\s+from\s+)?['"]([^'"]+)['"]/g;
806
- const exportFromRe = /export\s+(?!type\b)\S(?:.*\S)?\s+from\s+['"]([^'"]+)['"]/g;
807
- const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
808
- for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
809
- for (const match of code.matchAll(re)) {
810
- const source = match[1];
811
- if (source.startsWith(".") || source.startsWith("/")) {
812
- out.push(source);
813
- }
806
+ for (const line of code.split("\n")) {
807
+ for (const spec of staticImportSpecifiers(line)) {
808
+ pushIfLocal(out, spec);
814
809
  }
815
810
  }
811
+ for (const line of code.split("\n")) {
812
+ for (const spec of exportFromSpecifiers(line)) {
813
+ pushIfLocal(out, spec);
814
+ }
815
+ }
816
+ for (const match of code.matchAll(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
817
+ pushIfLocal(out, match[1]);
818
+ }
816
819
  return out;
817
820
  }
821
+ function pushIfLocal(out, spec) {
822
+ if (spec !== null && (spec.startsWith(".") || spec.startsWith("/"))) {
823
+ out.push(spec);
824
+ }
825
+ }
826
+ function staticImportSpecifiers(line) {
827
+ const specs = [];
828
+ for (const kw of allOccurrences(line, "import")) {
829
+ const afterKw = skipSpaces(line, kw + "import".length);
830
+ if (afterKw === kw + "import".length || startsWithWord(line, afterKw, "type")) {
831
+ continue;
832
+ }
833
+ if (line[afterKw] === '"' || line[afterKw] === "'") {
834
+ const spec2 = readQuoted(line, afterKw);
835
+ if (spec2 !== null) {
836
+ specs.push(spec2);
837
+ }
838
+ continue;
839
+ }
840
+ const spec = specifierAfterFrom(line, afterKw);
841
+ if (spec !== null) {
842
+ specs.push(spec);
843
+ }
844
+ }
845
+ return specs;
846
+ }
847
+ function exportFromSpecifiers(line) {
848
+ const specs = [];
849
+ for (const kw of allOccurrences(line, "export")) {
850
+ const afterKw = skipSpaces(line, kw + "export".length);
851
+ if (afterKw === kw + "export".length || startsWithWord(line, afterKw, "type")) {
852
+ continue;
853
+ }
854
+ const spec = specifierAfterFrom(line, afterKw);
855
+ if (spec !== null) {
856
+ specs.push(spec);
857
+ }
858
+ }
859
+ return specs;
860
+ }
861
+ function specifierAfterFrom(line, clauseStart) {
862
+ const fromAt = findKeywordFrom(line, clauseStart);
863
+ if (fromAt === -1) {
864
+ return null;
865
+ }
866
+ if (skipSpaces(line, clauseStart) >= fromAt) {
867
+ return null;
868
+ }
869
+ const afterFrom = skipSpaces(line, fromAt + "from".length);
870
+ return readQuoted(line, afterFrom);
871
+ }
872
+ function findKeywordFrom(line, at) {
873
+ let i = line.indexOf("from", at);
874
+ while (i !== -1) {
875
+ const before = line[i - 1];
876
+ const after = line[i + "from".length];
877
+ if (before !== void 0 && /\s/.test(before) && after !== void 0 && /\s/.test(after)) {
878
+ return i;
879
+ }
880
+ i = line.indexOf("from", i + 1);
881
+ }
882
+ return -1;
883
+ }
884
+ function readQuoted(line, at) {
885
+ const quote = line[at];
886
+ if (quote !== '"' && quote !== "'") {
887
+ return null;
888
+ }
889
+ const close = line.indexOf(quote, at + 1);
890
+ if (close <= at + 1) {
891
+ return null;
892
+ }
893
+ return line.slice(at + 1, close);
894
+ }
895
+ function allOccurrences(line, word) {
896
+ const positions = [];
897
+ let i = line.indexOf(word);
898
+ while (i !== -1) {
899
+ positions.push(i);
900
+ i = line.indexOf(word, i + 1);
901
+ }
902
+ return positions;
903
+ }
904
+ function startsWithWord(line, at, word) {
905
+ if (!line.startsWith(word, at)) {
906
+ return false;
907
+ }
908
+ const after = line[at + word.length];
909
+ return after === void 0 || !/\w/.test(after);
910
+ }
911
+ function skipSpaces(line, at) {
912
+ let i = at;
913
+ while (i < line.length && /\s/.test(line[i])) {
914
+ i++;
915
+ }
916
+ return i;
917
+ }
818
918
  function normalizeModuleId(id) {
819
919
  const clean = id.split("?")[0] ?? id;
820
920
  const cached = normalizedModuleIdCache.get(clean);
@@ -1113,10 +1213,9 @@ function parseThemeBlocks(cssContent) {
1113
1213
  };
1114
1214
  const stripped = stripLayerWrappers(cssContent);
1115
1215
  const blocks = extractThemeBlocks(stripped);
1116
- const propPattern = /--([a-z][a-z0-9-]*)(?:\s*:[^;]+)?;/g;
1117
1216
  for (const block of blocks) {
1118
- for (const match of block.matchAll(propPattern)) {
1119
- const categorized = categorizeProperty(match[1]);
1217
+ for (const name of scanCustomPropertyNames(block)) {
1218
+ const categorized = categorizeProperty(name);
1120
1219
  if (categorized) {
1121
1220
  result[categorized.category].add(categorized.token);
1122
1221
  }
@@ -1168,6 +1267,48 @@ function mergeThemes(themes) {
1168
1267
  function hasTokens(theme) {
1169
1268
  return Object.values(theme).some((arr) => arr.length > 0);
1170
1269
  }
1270
+ function scanCustomPropertyNames(block) {
1271
+ const names = [];
1272
+ let i = 0;
1273
+ while (i < block.length) {
1274
+ const dashes = block.indexOf("--", i);
1275
+ if (dashes === -1) {
1276
+ break;
1277
+ }
1278
+ let end = dashes + 2;
1279
+ if (end >= block.length || !/[a-z]/.test(block[end])) {
1280
+ i = dashes + 1;
1281
+ continue;
1282
+ }
1283
+ end++;
1284
+ while (end < block.length && /[a-z0-9-]/.test(block[end])) {
1285
+ end++;
1286
+ }
1287
+ const name = block.slice(dashes + 2, end);
1288
+ let matchEnd = -1;
1289
+ let cursor = end;
1290
+ while (cursor < block.length && /\s/.test(block[cursor])) {
1291
+ cursor++;
1292
+ }
1293
+ if (block[cursor] === ":") {
1294
+ const valueStart = cursor + 1;
1295
+ const semi = block.indexOf(";", valueStart);
1296
+ if (semi > valueStart) {
1297
+ matchEnd = semi + 1;
1298
+ }
1299
+ }
1300
+ if (matchEnd === -1 && block[end] === ";") {
1301
+ matchEnd = end + 1;
1302
+ }
1303
+ if (matchEnd === -1) {
1304
+ i = dashes + 1;
1305
+ continue;
1306
+ }
1307
+ names.push(name);
1308
+ i = matchEnd;
1309
+ }
1310
+ return names;
1311
+ }
1171
1312
 
1172
1313
  const GLOB_MAGIC_RE = /[*?[\]{}]/;
1173
1314
  const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", ".turbo", "dist", "build"]);
@@ -1289,16 +1430,29 @@ function safeJsonForScriptTag(value, prettyPrint = false) {
1289
1430
  const json = prettyPrint ? JSON.stringify(value, null, 2) : JSON.stringify(value);
1290
1431
  return json.replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1291
1432
  }
1433
+ function injectHtmlOpeningAttr(html, attr) {
1434
+ const lower = html.toLowerCase();
1435
+ let from = 0;
1436
+ for (; ; ) {
1437
+ const start = lower.indexOf("<html", from);
1438
+ if (start === -1) {
1439
+ return html;
1440
+ }
1441
+ const after = html[start + 5];
1442
+ if (after === void 0 || after === ">" || after === "/" || /\s/.test(after)) {
1443
+ const close = html.indexOf(">", start + 5);
1444
+ if (close === -1) {
1445
+ return html;
1446
+ }
1447
+ const nameEnd = start + 5;
1448
+ return `${html.slice(0, nameEnd)}${attr}${html.slice(nameEnd)}`;
1449
+ }
1450
+ from = start + 5;
1451
+ }
1452
+ }
1292
1453
  function injectChecksum(html, checksum, minify = false) {
1293
1454
  const attrName = minify ? "data-sz-cs" : "data-sz-checksum";
1294
- const htmlTagPattern = /<html([^>]*)>/i;
1295
- const match = html.match(htmlTagPattern);
1296
- if (!match) {
1297
- return html;
1298
- }
1299
- const existingAttrs = match[1];
1300
- const checksumAttr = ` ${attrName}="${checksum}"`;
1301
- return html.replace(htmlTagPattern, `<html${checksumAttr}${existingAttrs}>`);
1455
+ return injectHtmlOpeningAttr(html, ` ${attrName}="${checksum}"`);
1302
1456
  }
1303
1457
  function injectMangleMapScript(html, mangleMap, options = {}) {
1304
1458
  const {
@@ -1327,14 +1481,7 @@ ${debugScript}`;
1327
1481
  function injectMangleMapAttribute(html, mangleMap, minify = false, varMangleMap = {}) {
1328
1482
  const attrName = minify ? "data-sz-m" : "data-sz-map";
1329
1483
  const jsonContent = JSON.stringify(createHydrationMangleMap(mangleMap, varMangleMap));
1330
- const htmlTagPattern = /<html([^>]*)>/i;
1331
- const match = html.match(htmlTagPattern);
1332
- if (!match) {
1333
- return html;
1334
- }
1335
- const existingAttrs = match[1];
1336
- const mapAttr = ` ${attrName}='${jsonContent}'`;
1337
- return html.replace(htmlTagPattern, `<html${mapAttr}${existingAttrs}>`);
1484
+ return injectHtmlOpeningAttr(html, ` ${attrName}='${jsonContent}'`);
1338
1485
  }
1339
1486
  function injectHydrationData(html, mangleMap, checksum, options = {}) {
1340
1487
  const { mode = "script", minify = false } = options;
@@ -1622,11 +1769,26 @@ const MAX_SAFELIST_CLASSES = 1e5;
1622
1769
  const DEFAULT_VAR_MANGLE_MAP_MAX_BYTES = 100 * 1024;
1623
1770
  const GLOBAL_VAR_ALIAS_MAP_OWNER = "\0csszyx:global-var-aliases";
1624
1771
  const DIRECTIVE_PROLOGUE_PREFIX_RE = /^((?:\s|\/\/[^\n]*\n|\/\*(?:[^*]|\*(?!\/))*\*\/)*)(['"]use (?:client|server)['"];?\s*)/;
1625
- const RUNTIME_HELPER_IMPORT_RE = {
1626
- _sz: /(?:import|export)\s+\{[^{}]*\b_sz\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1627
- _szMerge: /(?:import|export)\s+\{[^{}]*\b_szMerge\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1628
- __szColorVar: /(?:import|export)\s+\{[^{}]*\b__szColorVar\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/
1629
- };
1772
+ function findOpeningTag(source, tag) {
1773
+ const lower = source.toLowerCase();
1774
+ const marker = `<${tag}`;
1775
+ let from = 0;
1776
+ for (; ; ) {
1777
+ const start = lower.indexOf(marker, from);
1778
+ if (start === -1) {
1779
+ return null;
1780
+ }
1781
+ const after = source[start + marker.length];
1782
+ if (after === void 0 || after === ">" || after === "/" || /\s/.test(after)) {
1783
+ const close = source.indexOf(">", start + marker.length);
1784
+ if (close !== -1) {
1785
+ return { start, close };
1786
+ }
1787
+ return null;
1788
+ }
1789
+ from = start + marker.length;
1790
+ }
1791
+ }
1630
1792
  let _hasWarnedTsConfig = false;
1631
1793
  let _hasWarnedTransformCacheVersion = false;
1632
1794
  let _hasWarnedNativeFallback = false;
@@ -2458,18 +2620,49 @@ function mangleCodeClassesSync(code, mangleMap) {
2458
2620
  }
2459
2621
  return `${sep}${ws}"${mangled.join(" ")}"`;
2460
2622
  });
2461
- result = result.replace(/\bszs:\s*\{([^{}]*)\}/g, (whole, body) => {
2462
- const mangledBody = body.replace(
2463
- /"((?:[^"\\]|\\.)*)"/g,
2464
- (_m, inner) => `"${mangleClassString(inner)}"`
2465
- ).replace(
2466
- /'((?:[^'\\]|\\.)*)'/g,
2467
- (_m, inner) => `'${mangleClassString(inner)}'`
2468
- );
2623
+ result = result.replace(/\bszsc:\s*\{([^{}]*)\}/g, (whole, body) => {
2624
+ const mangledBody = mangleQuotedStringLiterals(body, mangleClassString);
2469
2625
  return whole.replace(body, mangledBody);
2470
2626
  });
2471
2627
  return result;
2472
2628
  }
2629
+ function mangleQuotedStringLiterals(body, mangle) {
2630
+ let out = "";
2631
+ let i = 0;
2632
+ while (i < body.length) {
2633
+ const quote = body[i];
2634
+ if (quote !== '"' && quote !== "'") {
2635
+ out += quote;
2636
+ i++;
2637
+ continue;
2638
+ }
2639
+ let inner = "";
2640
+ let j = i + 1;
2641
+ let closed = false;
2642
+ while (j < body.length) {
2643
+ const ch = body[j];
2644
+ if (ch === "\\") {
2645
+ inner += ch + (body[j + 1] ?? "");
2646
+ j += 2;
2647
+ continue;
2648
+ }
2649
+ if (ch === quote) {
2650
+ closed = true;
2651
+ break;
2652
+ }
2653
+ inner += ch;
2654
+ j++;
2655
+ }
2656
+ if (!closed) {
2657
+ out += quote;
2658
+ i++;
2659
+ continue;
2660
+ }
2661
+ out += `${quote}${mangle(inner)}${quote}`;
2662
+ i = j + 1;
2663
+ }
2664
+ return out;
2665
+ }
2473
2666
  function assertGlobalVarMangleConfig(options) {
2474
2667
  const config = options.production?.mangleGlobalVars;
2475
2668
  const errors = validateGlobalVarMangleConfig(config);
@@ -2497,6 +2690,7 @@ function createCsszyxPlugins(options = {}) {
2497
2690
  let manglingEnabled = options.production?.mangle !== false;
2498
2691
  const mangleReserved = new Set(options.production?.mangleExclude ?? []);
2499
2692
  const astBudgetOverride = options.build?.astBudgetLimit;
2693
+ const prescanAstBudget = astBudgetOverride ?? 5e5;
2500
2694
  const cacheRequested = (options.build?.cache ?? DEFAULT_BUILD_CONFIG.cache) !== false;
2501
2695
  const cacheVersionsKnown = PLUGIN_VERSION !== UNKNOWN_PACKAGE_VERSION && COMPILER_VERSION !== UNKNOWN_PACKAGE_VERSION;
2502
2696
  const cacheEnabled = cacheRequested && cacheVersionsKnown;
@@ -2544,6 +2738,7 @@ function createCsszyxPlugins(options = {}) {
2544
2738
  let evictedCacheRoot = null;
2545
2739
  const transformMemoryCache = /* @__PURE__ */ new Map();
2546
2740
  let transformMemoryCacheCodeChars = 0;
2741
+ const prescanResultHandoff = /* @__PURE__ */ new Map();
2547
2742
  const state = {
2548
2743
  classes: /* @__PURE__ */ new Set(),
2549
2744
  parsedTheme: null,
@@ -2654,10 +2849,10 @@ function createCsszyxPlugins(options = {}) {
2654
2849
  return !isHardIgnored(id) && !isUserExcluded(id) && isUserIncluded(id) && (matchesScriptExtension(id, SOURCE_MODULE_EXTENSIONS) || id.endsWith(".vue") || id.endsWith(".svelte"));
2655
2850
  }
2656
2851
  function shouldProcessCss(id) {
2657
- return !isHardIgnored(id) && !isUserExcluded(id) && /\.css(\?.*)?$/.test(id);
2852
+ return !isHardIgnored(id) && !isUserExcluded(id) && matchesScriptExtension(id, [".css"]);
2658
2853
  }
2659
- function transformConfiguredSource(source, filename) {
2660
- const compilerOptions = createCompilerOptions();
2854
+ function transformConfiguredSource(source, filename, astBudget) {
2855
+ const compilerOptions = createCompilerOptions(astBudget);
2661
2856
  const effectiveFilename = normalizeSourceFilename(filename);
2662
2857
  const cacheRoot = resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
2663
2858
  if (cacheEnabled) {
@@ -2684,6 +2879,15 @@ function createCsszyxPlugins(options = {}) {
2684
2879
  rememberTransformCacheEntry(cacheKey.key, cached);
2685
2880
  return cached;
2686
2881
  }
2882
+ if (astBudget === void 0) {
2883
+ const handoff = prescanResultHandoff.get(effectiveFilename);
2884
+ if (handoff) {
2885
+ prescanResultHandoff.delete(effectiveFilename);
2886
+ if (handoff.inputSha256 === cacheKey.inputSha256) {
2887
+ return handoff.result;
2888
+ }
2889
+ }
2890
+ }
2687
2891
  }
2688
2892
  let result;
2689
2893
  if (parserMode === "babel") {
@@ -2714,9 +2918,9 @@ function createCsszyxPlugins(options = {}) {
2714
2918
  }
2715
2919
  return result;
2716
2920
  }
2717
- function createCompilerOptions() {
2921
+ function createCompilerOptions(astBudget = astBudgetOverride) {
2718
2922
  return {
2719
- astBudget: astBudgetOverride,
2923
+ astBudget,
2720
2924
  mangleVars: options.production?.mangleVars === true,
2721
2925
  mangleVarHoistMaxDepth: options.production?.mangleVarHoistMaxDepth,
2722
2926
  globalVarAliases: earlyGlobalVarAliasEntries.length > 0 ? earlyGlobalVarAliasEntries : void 0,
@@ -2731,7 +2935,10 @@ function createCsszyxPlugins(options = {}) {
2731
2935
  nativeIdentity: parserMode === "rust" ? resolveNativeCacheIdentity() : void 0,
2732
2936
  parserMode,
2733
2937
  producer: parserMode,
2734
- astBudget: astBudgetOverride,
2938
+ // The EFFECTIVE budget, not the raw override: prescan-lane results
2939
+ // (larger budget) must not be served to the transform hook, whose
2940
+ // smaller budget could not have produced them (and vice versa).
2941
+ astBudget: compilerOptions.astBudget,
2735
2942
  mangleVars: compilerOptions.mangleVars,
2736
2943
  mangleVarHoistMaxDepth: compilerOptions.mangleVarHoistMaxDepth,
2737
2944
  globalVarAliases: normalizeGlobalVarAliasesForCache(compilerOptions.globalVarAliases),
@@ -2743,7 +2950,7 @@ function createCsszyxPlugins(options = {}) {
2743
2950
  if (parserMode !== "rust" || files.length <= 1) {
2744
2951
  return transformPrescanSourcesIndividually(files);
2745
2952
  }
2746
- const compilerOptions = createCompilerOptions();
2953
+ const compilerOptions = createCompilerOptions(prescanAstBudget);
2747
2954
  const cacheRoot = resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
2748
2955
  const results = /* @__PURE__ */ new Map();
2749
2956
  const misses = [];
@@ -2813,7 +3020,11 @@ function createCsszyxPlugins(options = {}) {
2813
3020
  try {
2814
3021
  results.set(
2815
3022
  miss.filePath,
2816
- transformConfiguredSource(miss.content, miss.effectiveFilename)
3023
+ transformConfiguredSource(
3024
+ miss.content,
3025
+ miss.effectiveFilename,
3026
+ prescanAstBudget
3027
+ )
2817
3028
  );
2818
3029
  } catch {
2819
3030
  }
@@ -2824,15 +3035,28 @@ function createCsszyxPlugins(options = {}) {
2824
3035
  return result ? { filePath: file.filePath, result } : null;
2825
3036
  }).filter((entry) => entry !== null);
2826
3037
  }
3038
+ function warnPrescanBudgetSkip(filePath) {
3039
+ console.warn(
3040
+ `[csszyx] prescan skipped ${filePath}: the file exceeds the AST node budget, so NONE of its classes reached the safelist and their CSS will not be generated. Raise \`build.astBudgetLimit\` in the csszyx plugin options, or split the file.`
3041
+ );
3042
+ }
2827
3043
  function transformPrescanSourcesIndividually(files) {
2828
3044
  const results = [];
2829
3045
  for (const file of files) {
2830
3046
  try {
2831
3047
  results.push({
2832
3048
  filePath: file.filePath,
2833
- result: transformConfiguredSource(file.content, file.filePath)
3049
+ result: transformConfiguredSource(
3050
+ file.content,
3051
+ file.filePath,
3052
+ prescanAstBudget
3053
+ )
2834
3054
  });
2835
3055
  } catch (err) {
3056
+ if (err instanceof ASTBudgetExceededError) {
3057
+ warnPrescanBudgetSkip(file.filePath);
3058
+ continue;
3059
+ }
2836
3060
  console.warn(
2837
3061
  `[csszyx] prescan skipped ${file.filePath}: transform failed, so none of its classes reached the safelist. ${err instanceof Error ? err.message : String(err)}`
2838
3062
  );
@@ -2960,7 +3184,23 @@ function createCsszyxPlugins(options = {}) {
2960
3184
  }
2961
3185
  scanDir(sourceDir);
2962
3186
  }
3187
+ const prescanContentByPath = new Map(
3188
+ prescanSources.map((file) => [file.filePath, file.content])
3189
+ );
2963
3190
  for (const { filePath, result } of transformPrescanSources(prescanSources)) {
3191
+ if (cacheEnabled && !result.diagnostics.some((d) => d.includes("AST budget exceeded"))) {
3192
+ const content = prescanContentByPath.get(filePath);
3193
+ if (content !== void 0) {
3194
+ prescanResultHandoff.set(normalizeSourceFilename(filePath), {
3195
+ inputSha256: createHash("sha256").update(content).digest("hex"),
3196
+ result
3197
+ });
3198
+ }
3199
+ }
3200
+ if (result.diagnostics.some((d) => d.includes("AST budget exceeded"))) {
3201
+ warnPrescanBudgetSkip(filePath);
3202
+ continue;
3203
+ }
2964
3204
  if (result.classes.size === 0 && result.rawClassNames.size === 0 && result.diagnostics.some((d) => d.includes("[csszyx] parse error in "))) {
2965
3205
  console.warn(
2966
3206
  `[csszyx] prescan skipped ${filePath}: the file failed to parse, so none of its classes reached the safelist. Fix the syntax error (or check the file extension matches its contents).`
@@ -3219,7 +3459,7 @@ function createCsszyxPlugins(options = {}) {
3219
3459
  if (matchesScriptExtension(id, SCRIPT_ID_EXTENSIONS)) {
3220
3460
  assertNoRSCBoundaryViolation(code, id);
3221
3461
  }
3222
- if (/\.css(\?.*)?$/.test(id)) {
3462
+ if (matchesScriptExtension(id, [".css"])) {
3223
3463
  state.sawAnyCss = true;
3224
3464
  if (cssImportsTailwind(code)) {
3225
3465
  state.sawTailwindEntry = true;
@@ -3243,6 +3483,8 @@ function createCsszyxPlugins(options = {}) {
3243
3483
  let transformedCode = code;
3244
3484
  let usesRuntime = false;
3245
3485
  let usesMerge = false;
3486
+ let usesSzcn = false;
3487
+ let usesSzPart = false;
3246
3488
  let usesColorVar = false;
3247
3489
  let transformed = false;
3248
3490
  let szClasses;
@@ -3271,6 +3513,8 @@ function createCsszyxPlugins(options = {}) {
3271
3513
  transformedCode = result.code;
3272
3514
  usesRuntime = result.usesRuntime;
3273
3515
  usesMerge = result.usesMerge;
3516
+ usesSzcn = result.usesSzcn;
3517
+ usesSzPart = result.usesSzPart;
3274
3518
  usesColorVar = result.usesColorVar;
3275
3519
  transformed = result.transformed;
3276
3520
  szClasses = result.classes;
@@ -3279,12 +3523,17 @@ function createCsszyxPlugins(options = {}) {
3279
3523
  for (const msg of result.diagnostics) {
3280
3524
  if (msg.includes("unresolvable sz spread")) {
3281
3525
  state.spreadWarnings.add(`${id}
3526
+ ${msg}`);
3527
+ } else if (msg.includes("AST budget exceeded")) {
3528
+ console.warn(`[csszyx] ${id}
3282
3529
  ${msg}`);
3283
3530
  }
3284
3531
  }
3285
3532
  if (!quiet && result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
3286
3533
  for (const msg of result.diagnostics) {
3287
- if (msg.includes("unresolvable sz spread")) continue;
3534
+ if (msg.includes("unresolvable sz spread") || msg.includes("AST budget exceeded")) {
3535
+ continue;
3536
+ }
3288
3537
  this.warn(`[csszyx] ${id}
3289
3538
  ${msg}`);
3290
3539
  }
@@ -3299,16 +3548,14 @@ function createCsszyxPlugins(options = {}) {
3299
3548
  }
3300
3549
  if (transformedCode.includes("<html") && /(?:layout|Root|Document|app)\.tsx?$/i.test(id)) {
3301
3550
  const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
3302
- transformedCode = transformedCode.replace(
3303
- /<html([^>]*)>/i,
3304
- `<html$1 ${attrName}="${CHECKSUM_PLACEHOLDER}">`
3305
- );
3551
+ const htmlTag = findOpeningTag(transformedCode, "html");
3552
+ if (htmlTag) {
3553
+ transformedCode = `${transformedCode.slice(0, htmlTag.close)} ${attrName}="${CHECKSUM_PLACEHOLDER}"${transformedCode.slice(htmlTag.close)}`;
3554
+ }
3306
3555
  const debugScript = `<script dangerouslySetInnerHTML={{__html: \`(function(){var m=${MANGLE_MAP_PLACEHOLDER};var vm=${VAR_MANGLE_MAP_PLACEHOLDER};var gp=decodeURIComponent(${escapeJsonForInlineScript(JSON.stringify(encodedGlobalVarAliasPrefix))});var r={};var vr={};for(var k in m)r[m[k]]=k;for(var vk in vm){var vv=vm[vk];var vs=Array.isArray(vv)?vv:[vv];for(var vi=0;vi<vs.length;vi++)(vr[vs[vi]]||(vr[vs[vi]]=[])).push(vk)}window.__csszyx={mangleMap:m,varMangleMap:vm,checksum:"${CHECKSUM_PLACEHOLDER}",decode:function(c){return r[c]},encode:function(c){return m[c]},decodeVar:function(v){return vr[v]||[]},encodeVar:function(v){return vm[v]},decodeGlobalVar:function(v){var a=vr[v]||[];return v.indexOf(gp)===0?a[0]:void 0},decodeAll:function(el){return(el.className||"").split(" ").map(function(c){return r[c]||c})}}})()\`}} />`;
3307
- if (transformedCode.includes("<body")) {
3308
- transformedCode = transformedCode.replace(
3309
- /(<body[^>]*>)/i,
3310
- `$1${debugScript}`
3311
- );
3556
+ const bodyTag = findOpeningTag(transformedCode, "body");
3557
+ if (bodyTag) {
3558
+ transformedCode = `${transformedCode.slice(0, bodyTag.close + 1)}${debugScript}${transformedCode.slice(bodyTag.close + 1)}`;
3312
3559
  }
3313
3560
  transformed = true;
3314
3561
  }
@@ -3320,21 +3567,23 @@ function createCsszyxPlugins(options = {}) {
3320
3567
  if (usesMerge) {
3321
3568
  imports.push("_szMerge");
3322
3569
  }
3570
+ if (usesSzcn) {
3571
+ imports.push("_szcn");
3572
+ }
3573
+ if (usesSzPart) {
3574
+ imports.push("_szPart");
3575
+ }
3323
3576
  if (usesColorVar) {
3324
3577
  imports.push("__szColorVar");
3325
3578
  }
3326
3579
  const hasRuntimeImport = imports.length > 0 && transformedCode.includes("@csszyx/runtime");
3327
- const needed = hasRuntimeImport ? imports.filter(
3328
- (name) => !RUNTIME_HELPER_IMPORT_RE[name]?.test(transformedCode)
3329
- ) : imports;
3580
+ const needed = hasRuntimeImport ? imports.filter((name) => !importsRuntimeHelper(transformedCode, name)) : imports;
3330
3581
  if (needed.length > 0) {
3331
- const existingImport = transformedCode.match(
3332
- /^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m
3333
- );
3582
+ const existingImport = findRuntimeImportClause(transformedCode);
3334
3583
  if (existingImport) {
3335
3584
  transformedCode = transformedCode.replace(
3336
- existingImport[0],
3337
- `${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
3585
+ existingImport.statement,
3586
+ `${existingImport.prefixWithBody}, ${needed.join(", ")} } from '@csszyx/runtime'`
3338
3587
  );
3339
3588
  } else {
3340
3589
  const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
@@ -3344,7 +3593,7 @@ function createCsszyxPlugins(options = {}) {
3344
3593
  transformed = true;
3345
3594
  }
3346
3595
  }
3347
- if (/\bszcn\s*\(/.test(code) && !transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) && shouldProcessSource(id)) {
3596
+ if ((usesSzcn || /\bszcn\s*\(/.test(code)) && !transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) && shouldProcessSource(id)) {
3348
3597
  transformedCode = `import '${THEME_GROUPS_VIRTUAL_ID}';
3349
3598
  ${transformedCode}`;
3350
3599
  transformed = true;
@@ -3407,6 +3656,7 @@ ${transformedCode}`;
3407
3656
  emitWarning(`[csszyx] ${warning}`);
3408
3657
  }
3409
3658
  state.spreadWarnings.clear();
3659
+ prescanResultHandoff.clear();
3410
3660
  if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
3411
3661
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
3412
3662
  }
@@ -3466,6 +3716,7 @@ ${transformedCode}`;
3466
3716
  * @param ctx - HMR context containing the changed file
3467
3717
  */
3468
3718
  handleHotUpdate(ctx) {
3719
+ prescanResultHandoff.clear();
3469
3720
  const scanCss = options.build?.scanCss;
3470
3721
  if (scanCss) {
3471
3722
  const root = ctx.server.config.root || process.cwd();
@@ -3900,4 +4151,4 @@ const esbuildPlugin = (options = {}) => {
3900
4151
  };
3901
4152
  };
3902
4153
 
3903
- export { isRSCServerModule as A, isTailwindReservedGlobalVar as B, mangleCodeClassesSync as C, mangleHybridHazardMessage as D, mergeThemes as E, missingTailwindEntryMessage as F, normalizeGlobalVarAliasesForCache as G, parseThemeBlocks as H, planGlobalVarAliases as I, readGlobalVarScanCache as J, recordGlobalVarSourceFile as K, resolveCompileSourceDirs as L, resolveGlobalVarScanCacheDir as M, resolveNativeCacheIdentity as N, rewriteGlobalVarCssAliases as O, rollupPlugin as P, scanGlobalVarCss as Q, shouldEmitWarning as R, shouldTrackGlobalVarSources as S, shouldWarnMissingTailwindEntry as T, shouldWarnUnscopedMonorepo as U, skippedSzFilesMessage as V, unscopedMonorepoMessage as W, validateGlobalVarAliasInputs as X, vitePlugin as Y, webpackPlugin as Z, writeGlobalVarScanCache as _, appendTailwindSourceDirective as a, assertNoRSCBoundaryViolation as b, assertNoRSCGraphViolation as c, collectMangleHybridHazards as d, computeSafelistRelPath as e, createGlobalVarAliasValidationOptions as f, createGlobalVarMapAssetSource as g, createGlobalVarScanCacheKey as h, createRSCModuleRecord as i, cssHasContentScope as j, cssImportsTailwind as k, deleteRSCModuleRecord as l, esbuildPlugin as m, extractGlobalVarAliasesForManifest as n, fileMayContainSafelistableSz as o, findRSCBoundaryViolation as p, findRSCGraphViolation as q, hasInjectableTailwindCandidate as r, hasTokens as s, hasUseClientDirective as t, unplugin as u, hasUseServerDirective as v, isCompileSourceOptedIn as w, isHardIgnoredPath as x, isMonorepoPackage as y, isPackagesSkippedSource as z };
4154
+ export { webpackPlugin as $, isPackagesSkippedSource as A, isRSCServerModule as B, isTailwindReservedGlobalVar as C, mangleCodeClassesSync as D, mangleHybridHazardMessage as E, mergeThemes as F, missingTailwindEntryMessage as G, normalizeGlobalVarAliasesForCache as H, parseThemeBlocks as I, planGlobalVarAliases as J, readGlobalVarScanCache as K, recordGlobalVarSourceFile as L, resolveCompileSourceDirs as M, resolveGlobalVarScanCacheDir as N, resolveNativeCacheIdentity as O, rewriteGlobalVarCssAliases as P, rollupPlugin as Q, scanCustomPropertyNames as R, scanGlobalVarCss as S, shouldEmitWarning as T, shouldTrackGlobalVarSources as U, shouldWarnMissingTailwindEntry as V, shouldWarnUnscopedMonorepo as W, skippedSzFilesMessage as X, unscopedMonorepoMessage as Y, validateGlobalVarAliasInputs as Z, vitePlugin as _, appendTailwindSourceDirective as a, writeGlobalVarScanCache as a0, assertNoRSCBoundaryViolation as b, assertNoRSCGraphViolation as c, collectMangleHybridHazards as d, computeSafelistRelPath as e, createGlobalVarAliasValidationOptions as f, createGlobalVarMapAssetSource as g, createGlobalVarScanCacheKey as h, createRSCModuleRecord as i, cssHasContentScope as j, cssImportsTailwind as k, deleteRSCModuleRecord as l, esbuildPlugin as m, extractGlobalVarAliasesForManifest as n, fileMayContainSafelistableSz as o, findLocalImportSources as p, findRSCBoundaryViolation as q, findRSCGraphViolation as r, hasInjectableTailwindCandidate as s, hasTokens as t, unplugin as u, hasUseClientDirective as v, hasUseServerDirective as w, isCompileSourceOptedIn as x, isHardIgnoredPath as y, isMonorepoPackage as z };
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
 
5
- const CACHE_SCHEMA_VERSION = 7;
5
+ const CACHE_SCHEMA_VERSION = 9;
6
6
  function resolveTransformCacheDir(rootDir, cacheDir) {
7
7
  return path.resolve(rootDir, cacheDir ?? ".csszyx/cache", "transform");
8
8
  }
@@ -117,6 +117,8 @@ function serializeResult(result) {
117
117
  transformed: result.transformed,
118
118
  usesRuntime: result.usesRuntime,
119
119
  usesMerge: result.usesMerge,
120
+ usesSzcn: result.usesSzcn,
121
+ usesSzPart: result.usesSzPart,
120
122
  usesColorVar: result.usesColorVar,
121
123
  classes: [...result.classes],
122
124
  rawClassNames: [...result.rawClassNames],
@@ -131,6 +133,8 @@ function deserializeResult(result) {
131
133
  transformed: result.transformed,
132
134
  usesRuntime: result.usesRuntime,
133
135
  usesMerge: result.usesMerge,
136
+ usesSzcn: result.usesSzcn,
137
+ usesSzPart: result.usesSzPart,
134
138
  usesColorVar: result.usesColorVar,
135
139
  classes: new Set(result.classes),
136
140
  rawClassNames: new Set(result.rawClassNames),
@@ -2,7 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const compiler = require('@csszyx/compiler');
5
- const transformCache = require('./unplugin.D32Rb29j.cjs');
5
+ const transformCache = require('./unplugin.D0N9bprz.cjs');
6
6
  const node_crypto = require('node:crypto');
7
7
  const htmlEscape = require('./unplugin.BCwRIUs_.cjs');
8
8