@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,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const node_crypto = require('node:crypto');
3
4
  const fs = require('node:fs');
4
5
  const node_module = require('node:module');
5
6
  const path = require('node:path');
@@ -14,8 +15,8 @@ const vueAdapter = require('@csszyx/vue-adapter');
14
15
  const unplugin$1 = require('unplugin');
15
16
  const cssMangler = require('../css-mangler.cjs');
16
17
  const htmlEscape = require('./unplugin.BCwRIUs_.cjs');
17
- const node_crypto = require('node:crypto');
18
- const transformCache = require('./unplugin.D32Rb29j.cjs');
18
+ const runtimeImportScan = require('./unplugin.Rov-j_Wm.cjs');
19
+ const transformCache = require('./unplugin.D0N9bprz.cjs');
19
20
  const postcss = require('postcss');
20
21
  const valueParser = require('postcss-value-parser');
21
22
 
@@ -822,19 +823,118 @@ function readRuntimeImportSymbols(source, clause) {
822
823
  }
823
824
  function findLocalImportSources(code) {
824
825
  const out = [];
825
- const staticImportRe = /import\s+(?!type\b)(?:\S(?:.*\S)?\s+from\s+)?['"]([^'"]+)['"]/g;
826
- const exportFromRe = /export\s+(?!type\b)\S(?:.*\S)?\s+from\s+['"]([^'"]+)['"]/g;
827
- const dynamicImportRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
828
- for (const re of [staticImportRe, exportFromRe, dynamicImportRe]) {
829
- for (const match of code.matchAll(re)) {
830
- const source = match[1];
831
- if (source.startsWith(".") || source.startsWith("/")) {
832
- out.push(source);
833
- }
826
+ for (const line of code.split("\n")) {
827
+ for (const spec of staticImportSpecifiers(line)) {
828
+ pushIfLocal(out, spec);
834
829
  }
835
830
  }
831
+ for (const line of code.split("\n")) {
832
+ for (const spec of exportFromSpecifiers(line)) {
833
+ pushIfLocal(out, spec);
834
+ }
835
+ }
836
+ for (const match of code.matchAll(/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g)) {
837
+ pushIfLocal(out, match[1]);
838
+ }
836
839
  return out;
837
840
  }
841
+ function pushIfLocal(out, spec) {
842
+ if (spec !== null && (spec.startsWith(".") || spec.startsWith("/"))) {
843
+ out.push(spec);
844
+ }
845
+ }
846
+ function staticImportSpecifiers(line) {
847
+ const specs = [];
848
+ for (const kw of allOccurrences(line, "import")) {
849
+ const afterKw = skipSpaces(line, kw + "import".length);
850
+ if (afterKw === kw + "import".length || startsWithWord(line, afterKw, "type")) {
851
+ continue;
852
+ }
853
+ if (line[afterKw] === '"' || line[afterKw] === "'") {
854
+ const spec2 = readQuoted(line, afterKw);
855
+ if (spec2 !== null) {
856
+ specs.push(spec2);
857
+ }
858
+ continue;
859
+ }
860
+ const spec = specifierAfterFrom(line, afterKw);
861
+ if (spec !== null) {
862
+ specs.push(spec);
863
+ }
864
+ }
865
+ return specs;
866
+ }
867
+ function exportFromSpecifiers(line) {
868
+ const specs = [];
869
+ for (const kw of allOccurrences(line, "export")) {
870
+ const afterKw = skipSpaces(line, kw + "export".length);
871
+ if (afterKw === kw + "export".length || startsWithWord(line, afterKw, "type")) {
872
+ continue;
873
+ }
874
+ const spec = specifierAfterFrom(line, afterKw);
875
+ if (spec !== null) {
876
+ specs.push(spec);
877
+ }
878
+ }
879
+ return specs;
880
+ }
881
+ function specifierAfterFrom(line, clauseStart) {
882
+ const fromAt = findKeywordFrom(line, clauseStart);
883
+ if (fromAt === -1) {
884
+ return null;
885
+ }
886
+ if (skipSpaces(line, clauseStart) >= fromAt) {
887
+ return null;
888
+ }
889
+ const afterFrom = skipSpaces(line, fromAt + "from".length);
890
+ return readQuoted(line, afterFrom);
891
+ }
892
+ function findKeywordFrom(line, at) {
893
+ let i = line.indexOf("from", at);
894
+ while (i !== -1) {
895
+ const before = line[i - 1];
896
+ const after = line[i + "from".length];
897
+ if (before !== void 0 && /\s/.test(before) && after !== void 0 && /\s/.test(after)) {
898
+ return i;
899
+ }
900
+ i = line.indexOf("from", i + 1);
901
+ }
902
+ return -1;
903
+ }
904
+ function readQuoted(line, at) {
905
+ const quote = line[at];
906
+ if (quote !== '"' && quote !== "'") {
907
+ return null;
908
+ }
909
+ const close = line.indexOf(quote, at + 1);
910
+ if (close <= at + 1) {
911
+ return null;
912
+ }
913
+ return line.slice(at + 1, close);
914
+ }
915
+ function allOccurrences(line, word) {
916
+ const positions = [];
917
+ let i = line.indexOf(word);
918
+ while (i !== -1) {
919
+ positions.push(i);
920
+ i = line.indexOf(word, i + 1);
921
+ }
922
+ return positions;
923
+ }
924
+ function startsWithWord(line, at, word) {
925
+ if (!line.startsWith(word, at)) {
926
+ return false;
927
+ }
928
+ const after = line[at + word.length];
929
+ return after === void 0 || !/\w/.test(after);
930
+ }
931
+ function skipSpaces(line, at) {
932
+ let i = at;
933
+ while (i < line.length && /\s/.test(line[i])) {
934
+ i++;
935
+ }
936
+ return i;
937
+ }
838
938
  function normalizeModuleId(id) {
839
939
  const clean = id.split("?")[0] ?? id;
840
940
  const cached = normalizedModuleIdCache.get(clean);
@@ -1133,10 +1233,9 @@ function parseThemeBlocks(cssContent) {
1133
1233
  };
1134
1234
  const stripped = stripLayerWrappers(cssContent);
1135
1235
  const blocks = extractThemeBlocks(stripped);
1136
- const propPattern = /--([a-z][a-z0-9-]*)(?:\s*:[^;]+)?;/g;
1137
1236
  for (const block of blocks) {
1138
- for (const match of block.matchAll(propPattern)) {
1139
- const categorized = categorizeProperty(match[1]);
1237
+ for (const name of scanCustomPropertyNames(block)) {
1238
+ const categorized = categorizeProperty(name);
1140
1239
  if (categorized) {
1141
1240
  result[categorized.category].add(categorized.token);
1142
1241
  }
@@ -1188,6 +1287,48 @@ function mergeThemes(themes) {
1188
1287
  function hasTokens(theme) {
1189
1288
  return Object.values(theme).some((arr) => arr.length > 0);
1190
1289
  }
1290
+ function scanCustomPropertyNames(block) {
1291
+ const names = [];
1292
+ let i = 0;
1293
+ while (i < block.length) {
1294
+ const dashes = block.indexOf("--", i);
1295
+ if (dashes === -1) {
1296
+ break;
1297
+ }
1298
+ let end = dashes + 2;
1299
+ if (end >= block.length || !/[a-z]/.test(block[end])) {
1300
+ i = dashes + 1;
1301
+ continue;
1302
+ }
1303
+ end++;
1304
+ while (end < block.length && /[a-z0-9-]/.test(block[end])) {
1305
+ end++;
1306
+ }
1307
+ const name = block.slice(dashes + 2, end);
1308
+ let matchEnd = -1;
1309
+ let cursor = end;
1310
+ while (cursor < block.length && /\s/.test(block[cursor])) {
1311
+ cursor++;
1312
+ }
1313
+ if (block[cursor] === ":") {
1314
+ const valueStart = cursor + 1;
1315
+ const semi = block.indexOf(";", valueStart);
1316
+ if (semi > valueStart) {
1317
+ matchEnd = semi + 1;
1318
+ }
1319
+ }
1320
+ if (matchEnd === -1 && block[end] === ";") {
1321
+ matchEnd = end + 1;
1322
+ }
1323
+ if (matchEnd === -1) {
1324
+ i = dashes + 1;
1325
+ continue;
1326
+ }
1327
+ names.push(name);
1328
+ i = matchEnd;
1329
+ }
1330
+ return names;
1331
+ }
1191
1332
 
1192
1333
  const GLOB_MAGIC_RE = /[*?[\]{}]/;
1193
1334
  const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", ".turbo", "dist", "build"]);
@@ -1309,16 +1450,29 @@ function safeJsonForScriptTag(value, prettyPrint = false) {
1309
1450
  const json = prettyPrint ? JSON.stringify(value, null, 2) : JSON.stringify(value);
1310
1451
  return json.replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1311
1452
  }
1453
+ function injectHtmlOpeningAttr(html, attr) {
1454
+ const lower = html.toLowerCase();
1455
+ let from = 0;
1456
+ for (; ; ) {
1457
+ const start = lower.indexOf("<html", from);
1458
+ if (start === -1) {
1459
+ return html;
1460
+ }
1461
+ const after = html[start + 5];
1462
+ if (after === void 0 || after === ">" || after === "/" || /\s/.test(after)) {
1463
+ const close = html.indexOf(">", start + 5);
1464
+ if (close === -1) {
1465
+ return html;
1466
+ }
1467
+ const nameEnd = start + 5;
1468
+ return `${html.slice(0, nameEnd)}${attr}${html.slice(nameEnd)}`;
1469
+ }
1470
+ from = start + 5;
1471
+ }
1472
+ }
1312
1473
  function injectChecksum(html, checksum, minify = false) {
1313
1474
  const attrName = minify ? "data-sz-cs" : "data-sz-checksum";
1314
- const htmlTagPattern = /<html([^>]*)>/i;
1315
- const match = html.match(htmlTagPattern);
1316
- if (!match) {
1317
- return html;
1318
- }
1319
- const existingAttrs = match[1];
1320
- const checksumAttr = ` ${attrName}="${checksum}"`;
1321
- return html.replace(htmlTagPattern, `<html${checksumAttr}${existingAttrs}>`);
1475
+ return injectHtmlOpeningAttr(html, ` ${attrName}="${checksum}"`);
1322
1476
  }
1323
1477
  function injectMangleMapScript(html, mangleMap, options = {}) {
1324
1478
  const {
@@ -1347,14 +1501,7 @@ ${debugScript}`;
1347
1501
  function injectMangleMapAttribute(html, mangleMap, minify = false, varMangleMap = {}) {
1348
1502
  const attrName = minify ? "data-sz-m" : "data-sz-map";
1349
1503
  const jsonContent = JSON.stringify(createHydrationMangleMap(mangleMap, varMangleMap));
1350
- const htmlTagPattern = /<html([^>]*)>/i;
1351
- const match = html.match(htmlTagPattern);
1352
- if (!match) {
1353
- return html;
1354
- }
1355
- const existingAttrs = match[1];
1356
- const mapAttr = ` ${attrName}='${jsonContent}'`;
1357
- return html.replace(htmlTagPattern, `<html${mapAttr}${existingAttrs}>`);
1504
+ return injectHtmlOpeningAttr(html, ` ${attrName}='${jsonContent}'`);
1358
1505
  }
1359
1506
  function injectHydrationData(html, mangleMap, checksum, options = {}) {
1360
1507
  const { mode = "script", minify = false } = options;
@@ -1642,19 +1789,34 @@ const MAX_SAFELIST_CLASSES = 1e5;
1642
1789
  const DEFAULT_VAR_MANGLE_MAP_MAX_BYTES = 100 * 1024;
1643
1790
  const GLOBAL_VAR_ALIAS_MAP_OWNER = "\0csszyx:global-var-aliases";
1644
1791
  const DIRECTIVE_PROLOGUE_PREFIX_RE = /^((?:\s|\/\/[^\n]*\n|\/\*(?:[^*]|\*(?!\/))*\*\/)*)(['"]use (?:client|server)['"];?\s*)/;
1645
- const RUNTIME_HELPER_IMPORT_RE = {
1646
- _sz: /(?:import|export)\s+\{[^{}]*\b_sz\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1647
- _szMerge: /(?:import|export)\s+\{[^{}]*\b_szMerge\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/,
1648
- __szColorVar: /(?:import|export)\s+\{[^{}]*\b__szColorVar\b[^{}]*\}\s*from\s*['"]@csszyx\/runtime['"]/
1649
- };
1792
+ function findOpeningTag(source, tag) {
1793
+ const lower = source.toLowerCase();
1794
+ const marker = `<${tag}`;
1795
+ let from = 0;
1796
+ for (; ; ) {
1797
+ const start = lower.indexOf(marker, from);
1798
+ if (start === -1) {
1799
+ return null;
1800
+ }
1801
+ const after = source[start + marker.length];
1802
+ if (after === void 0 || after === ">" || after === "/" || /\s/.test(after)) {
1803
+ const close = source.indexOf(">", start + marker.length);
1804
+ if (close !== -1) {
1805
+ return { start, close };
1806
+ }
1807
+ return null;
1808
+ }
1809
+ from = start + marker.length;
1810
+ }
1811
+ }
1650
1812
  let _hasWarnedTsConfig = false;
1651
1813
  let _hasWarnedTransformCacheVersion = false;
1652
1814
  let _hasWarnedNativeFallback = false;
1653
1815
  const _loggedActiveParsers = /* @__PURE__ */ new Set();
1654
1816
  const _babelFallbackFiles = /* @__PURE__ */ new Set();
1655
- const requireFromHere = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CoiN0OSE.cjs', document.baseURI).href)));
1817
+ const requireFromHere = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CP2cz9bA.cjs', document.baseURI).href)));
1656
1818
  const PLUGIN_VERSION = findPackageVersionFromFile(
1657
- node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CoiN0OSE.cjs', document.baseURI).href))),
1819
+ node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.CP2cz9bA.cjs', document.baseURI).href))),
1658
1820
  UNKNOWN_PACKAGE_VERSION
1659
1821
  );
1660
1822
  const COMPILER_VERSION = findPackageVersionFromModule("@csszyx/compiler", UNKNOWN_PACKAGE_VERSION);
@@ -2478,18 +2640,49 @@ function mangleCodeClassesSync(code, mangleMap) {
2478
2640
  }
2479
2641
  return `${sep}${ws}"${mangled.join(" ")}"`;
2480
2642
  });
2481
- result = result.replace(/\bszs:\s*\{([^{}]*)\}/g, (whole, body) => {
2482
- const mangledBody = body.replace(
2483
- /"((?:[^"\\]|\\.)*)"/g,
2484
- (_m, inner) => `"${mangleClassString(inner)}"`
2485
- ).replace(
2486
- /'((?:[^'\\]|\\.)*)'/g,
2487
- (_m, inner) => `'${mangleClassString(inner)}'`
2488
- );
2643
+ result = result.replace(/\bszsc:\s*\{([^{}]*)\}/g, (whole, body) => {
2644
+ const mangledBody = mangleQuotedStringLiterals(body, mangleClassString);
2489
2645
  return whole.replace(body, mangledBody);
2490
2646
  });
2491
2647
  return result;
2492
2648
  }
2649
+ function mangleQuotedStringLiterals(body, mangle) {
2650
+ let out = "";
2651
+ let i = 0;
2652
+ while (i < body.length) {
2653
+ const quote = body[i];
2654
+ if (quote !== '"' && quote !== "'") {
2655
+ out += quote;
2656
+ i++;
2657
+ continue;
2658
+ }
2659
+ let inner = "";
2660
+ let j = i + 1;
2661
+ let closed = false;
2662
+ while (j < body.length) {
2663
+ const ch = body[j];
2664
+ if (ch === "\\") {
2665
+ inner += ch + (body[j + 1] ?? "");
2666
+ j += 2;
2667
+ continue;
2668
+ }
2669
+ if (ch === quote) {
2670
+ closed = true;
2671
+ break;
2672
+ }
2673
+ inner += ch;
2674
+ j++;
2675
+ }
2676
+ if (!closed) {
2677
+ out += quote;
2678
+ i++;
2679
+ continue;
2680
+ }
2681
+ out += `${quote}${mangle(inner)}${quote}`;
2682
+ i = j + 1;
2683
+ }
2684
+ return out;
2685
+ }
2493
2686
  function assertGlobalVarMangleConfig(options) {
2494
2687
  const config = options.production?.mangleGlobalVars;
2495
2688
  const errors = types.validateGlobalVarMangleConfig(config);
@@ -2517,6 +2710,7 @@ function createCsszyxPlugins(options = {}) {
2517
2710
  let manglingEnabled = options.production?.mangle !== false;
2518
2711
  const mangleReserved = new Set(options.production?.mangleExclude ?? []);
2519
2712
  const astBudgetOverride = options.build?.astBudgetLimit;
2713
+ const prescanAstBudget = astBudgetOverride ?? 5e5;
2520
2714
  const cacheRequested = (options.build?.cache ?? types.DEFAULT_BUILD_CONFIG.cache) !== false;
2521
2715
  const cacheVersionsKnown = PLUGIN_VERSION !== UNKNOWN_PACKAGE_VERSION && COMPILER_VERSION !== UNKNOWN_PACKAGE_VERSION;
2522
2716
  const cacheEnabled = cacheRequested && cacheVersionsKnown;
@@ -2564,6 +2758,7 @@ function createCsszyxPlugins(options = {}) {
2564
2758
  let evictedCacheRoot = null;
2565
2759
  const transformMemoryCache = /* @__PURE__ */ new Map();
2566
2760
  let transformMemoryCacheCodeChars = 0;
2761
+ const prescanResultHandoff = /* @__PURE__ */ new Map();
2567
2762
  const state = {
2568
2763
  classes: /* @__PURE__ */ new Set(),
2569
2764
  parsedTheme: null,
@@ -2674,10 +2869,10 @@ function createCsszyxPlugins(options = {}) {
2674
2869
  return !isHardIgnored(id) && !isUserExcluded(id) && isUserIncluded(id) && (matchesScriptExtension(id, SOURCE_MODULE_EXTENSIONS) || id.endsWith(".vue") || id.endsWith(".svelte"));
2675
2870
  }
2676
2871
  function shouldProcessCss(id) {
2677
- return !isHardIgnored(id) && !isUserExcluded(id) && /\.css(\?.*)?$/.test(id);
2872
+ return !isHardIgnored(id) && !isUserExcluded(id) && matchesScriptExtension(id, [".css"]);
2678
2873
  }
2679
- function transformConfiguredSource(source, filename) {
2680
- const compilerOptions = createCompilerOptions();
2874
+ function transformConfiguredSource(source, filename, astBudget) {
2875
+ const compilerOptions = createCompilerOptions(astBudget);
2681
2876
  const effectiveFilename = normalizeSourceFilename(filename);
2682
2877
  const cacheRoot = transformCache.resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
2683
2878
  if (cacheEnabled) {
@@ -2704,6 +2899,15 @@ function createCsszyxPlugins(options = {}) {
2704
2899
  rememberTransformCacheEntry(cacheKey.key, cached);
2705
2900
  return cached;
2706
2901
  }
2902
+ if (astBudget === void 0) {
2903
+ const handoff = prescanResultHandoff.get(effectiveFilename);
2904
+ if (handoff) {
2905
+ prescanResultHandoff.delete(effectiveFilename);
2906
+ if (handoff.inputSha256 === cacheKey.inputSha256) {
2907
+ return handoff.result;
2908
+ }
2909
+ }
2910
+ }
2707
2911
  }
2708
2912
  let result;
2709
2913
  if (parserMode === "babel") {
@@ -2734,9 +2938,9 @@ function createCsszyxPlugins(options = {}) {
2734
2938
  }
2735
2939
  return result;
2736
2940
  }
2737
- function createCompilerOptions() {
2941
+ function createCompilerOptions(astBudget = astBudgetOverride) {
2738
2942
  return {
2739
- astBudget: astBudgetOverride,
2943
+ astBudget,
2740
2944
  mangleVars: options.production?.mangleVars === true,
2741
2945
  mangleVarHoistMaxDepth: options.production?.mangleVarHoistMaxDepth,
2742
2946
  globalVarAliases: earlyGlobalVarAliasEntries.length > 0 ? earlyGlobalVarAliasEntries : void 0,
@@ -2751,7 +2955,10 @@ function createCsszyxPlugins(options = {}) {
2751
2955
  nativeIdentity: parserMode === "rust" ? resolveNativeCacheIdentity() : void 0,
2752
2956
  parserMode,
2753
2957
  producer: parserMode,
2754
- astBudget: astBudgetOverride,
2958
+ // The EFFECTIVE budget, not the raw override: prescan-lane results
2959
+ // (larger budget) must not be served to the transform hook, whose
2960
+ // smaller budget could not have produced them (and vice versa).
2961
+ astBudget: compilerOptions.astBudget,
2755
2962
  mangleVars: compilerOptions.mangleVars,
2756
2963
  mangleVarHoistMaxDepth: compilerOptions.mangleVarHoistMaxDepth,
2757
2964
  globalVarAliases: normalizeGlobalVarAliasesForCache(compilerOptions.globalVarAliases),
@@ -2763,7 +2970,7 @@ function createCsszyxPlugins(options = {}) {
2763
2970
  if (parserMode !== "rust" || files.length <= 1) {
2764
2971
  return transformPrescanSourcesIndividually(files);
2765
2972
  }
2766
- const compilerOptions = createCompilerOptions();
2973
+ const compilerOptions = createCompilerOptions(prescanAstBudget);
2767
2974
  const cacheRoot = transformCache.resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
2768
2975
  const results = /* @__PURE__ */ new Map();
2769
2976
  const misses = [];
@@ -2833,7 +3040,11 @@ function createCsszyxPlugins(options = {}) {
2833
3040
  try {
2834
3041
  results.set(
2835
3042
  miss.filePath,
2836
- transformConfiguredSource(miss.content, miss.effectiveFilename)
3043
+ transformConfiguredSource(
3044
+ miss.content,
3045
+ miss.effectiveFilename,
3046
+ prescanAstBudget
3047
+ )
2837
3048
  );
2838
3049
  } catch {
2839
3050
  }
@@ -2844,15 +3055,28 @@ function createCsszyxPlugins(options = {}) {
2844
3055
  return result ? { filePath: file.filePath, result } : null;
2845
3056
  }).filter((entry) => entry !== null);
2846
3057
  }
3058
+ function warnPrescanBudgetSkip(filePath) {
3059
+ console.warn(
3060
+ `[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.`
3061
+ );
3062
+ }
2847
3063
  function transformPrescanSourcesIndividually(files) {
2848
3064
  const results = [];
2849
3065
  for (const file of files) {
2850
3066
  try {
2851
3067
  results.push({
2852
3068
  filePath: file.filePath,
2853
- result: transformConfiguredSource(file.content, file.filePath)
3069
+ result: transformConfiguredSource(
3070
+ file.content,
3071
+ file.filePath,
3072
+ prescanAstBudget
3073
+ )
2854
3074
  });
2855
3075
  } catch (err) {
3076
+ if (err instanceof compiler.ASTBudgetExceededError) {
3077
+ warnPrescanBudgetSkip(file.filePath);
3078
+ continue;
3079
+ }
2856
3080
  console.warn(
2857
3081
  `[csszyx] prescan skipped ${file.filePath}: transform failed, so none of its classes reached the safelist. ${err instanceof Error ? err.message : String(err)}`
2858
3082
  );
@@ -2980,7 +3204,23 @@ function createCsszyxPlugins(options = {}) {
2980
3204
  }
2981
3205
  scanDir(sourceDir);
2982
3206
  }
3207
+ const prescanContentByPath = new Map(
3208
+ prescanSources.map((file) => [file.filePath, file.content])
3209
+ );
2983
3210
  for (const { filePath, result } of transformPrescanSources(prescanSources)) {
3211
+ if (cacheEnabled && !result.diagnostics.some((d) => d.includes("AST budget exceeded"))) {
3212
+ const content = prescanContentByPath.get(filePath);
3213
+ if (content !== void 0) {
3214
+ prescanResultHandoff.set(normalizeSourceFilename(filePath), {
3215
+ inputSha256: node_crypto.createHash("sha256").update(content).digest("hex"),
3216
+ result
3217
+ });
3218
+ }
3219
+ }
3220
+ if (result.diagnostics.some((d) => d.includes("AST budget exceeded"))) {
3221
+ warnPrescanBudgetSkip(filePath);
3222
+ continue;
3223
+ }
2984
3224
  if (result.classes.size === 0 && result.rawClassNames.size === 0 && result.diagnostics.some((d) => d.includes("[csszyx] parse error in "))) {
2985
3225
  console.warn(
2986
3226
  `[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).`
@@ -3239,7 +3479,7 @@ function createCsszyxPlugins(options = {}) {
3239
3479
  if (matchesScriptExtension(id, SCRIPT_ID_EXTENSIONS)) {
3240
3480
  assertNoRSCBoundaryViolation(code, id);
3241
3481
  }
3242
- if (/\.css(\?.*)?$/.test(id)) {
3482
+ if (matchesScriptExtension(id, [".css"])) {
3243
3483
  state.sawAnyCss = true;
3244
3484
  if (cssImportsTailwind(code)) {
3245
3485
  state.sawTailwindEntry = true;
@@ -3263,6 +3503,8 @@ function createCsszyxPlugins(options = {}) {
3263
3503
  let transformedCode = code;
3264
3504
  let usesRuntime = false;
3265
3505
  let usesMerge = false;
3506
+ let usesSzcn = false;
3507
+ let usesSzPart = false;
3266
3508
  let usesColorVar = false;
3267
3509
  let transformed = false;
3268
3510
  let szClasses;
@@ -3291,6 +3533,8 @@ function createCsszyxPlugins(options = {}) {
3291
3533
  transformedCode = result.code;
3292
3534
  usesRuntime = result.usesRuntime;
3293
3535
  usesMerge = result.usesMerge;
3536
+ usesSzcn = result.usesSzcn;
3537
+ usesSzPart = result.usesSzPart;
3294
3538
  usesColorVar = result.usesColorVar;
3295
3539
  transformed = result.transformed;
3296
3540
  szClasses = result.classes;
@@ -3299,12 +3543,17 @@ function createCsszyxPlugins(options = {}) {
3299
3543
  for (const msg of result.diagnostics) {
3300
3544
  if (msg.includes("unresolvable sz spread")) {
3301
3545
  state.spreadWarnings.add(`${id}
3546
+ ${msg}`);
3547
+ } else if (msg.includes("AST budget exceeded")) {
3548
+ console.warn(`[csszyx] ${id}
3302
3549
  ${msg}`);
3303
3550
  }
3304
3551
  }
3305
3552
  if (!quiet && result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
3306
3553
  for (const msg of result.diagnostics) {
3307
- if (msg.includes("unresolvable sz spread")) continue;
3554
+ if (msg.includes("unresolvable sz spread") || msg.includes("AST budget exceeded")) {
3555
+ continue;
3556
+ }
3308
3557
  this.warn(`[csszyx] ${id}
3309
3558
  ${msg}`);
3310
3559
  }
@@ -3319,16 +3568,14 @@ function createCsszyxPlugins(options = {}) {
3319
3568
  }
3320
3569
  if (transformedCode.includes("<html") && /(?:layout|Root|Document|app)\.tsx?$/i.test(id)) {
3321
3570
  const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
3322
- transformedCode = transformedCode.replace(
3323
- /<html([^>]*)>/i,
3324
- `<html$1 ${attrName}="${CHECKSUM_PLACEHOLDER}">`
3325
- );
3571
+ const htmlTag = findOpeningTag(transformedCode, "html");
3572
+ if (htmlTag) {
3573
+ transformedCode = `${transformedCode.slice(0, htmlTag.close)} ${attrName}="${CHECKSUM_PLACEHOLDER}"${transformedCode.slice(htmlTag.close)}`;
3574
+ }
3326
3575
  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})}}})()\`}} />`;
3327
- if (transformedCode.includes("<body")) {
3328
- transformedCode = transformedCode.replace(
3329
- /(<body[^>]*>)/i,
3330
- `$1${debugScript}`
3331
- );
3576
+ const bodyTag = findOpeningTag(transformedCode, "body");
3577
+ if (bodyTag) {
3578
+ transformedCode = `${transformedCode.slice(0, bodyTag.close + 1)}${debugScript}${transformedCode.slice(bodyTag.close + 1)}`;
3332
3579
  }
3333
3580
  transformed = true;
3334
3581
  }
@@ -3340,21 +3587,23 @@ function createCsszyxPlugins(options = {}) {
3340
3587
  if (usesMerge) {
3341
3588
  imports.push("_szMerge");
3342
3589
  }
3590
+ if (usesSzcn) {
3591
+ imports.push("_szcn");
3592
+ }
3593
+ if (usesSzPart) {
3594
+ imports.push("_szPart");
3595
+ }
3343
3596
  if (usesColorVar) {
3344
3597
  imports.push("__szColorVar");
3345
3598
  }
3346
3599
  const hasRuntimeImport = imports.length > 0 && transformedCode.includes("@csszyx/runtime");
3347
- const needed = hasRuntimeImport ? imports.filter(
3348
- (name) => !RUNTIME_HELPER_IMPORT_RE[name]?.test(transformedCode)
3349
- ) : imports;
3600
+ const needed = hasRuntimeImport ? imports.filter((name) => !runtimeImportScan.importsRuntimeHelper(transformedCode, name)) : imports;
3350
3601
  if (needed.length > 0) {
3351
- const existingImport = transformedCode.match(
3352
- /^(import\s*\{[^}]*)\}\s*from\s*'@csszyx\/runtime'/m
3353
- );
3602
+ const existingImport = runtimeImportScan.findRuntimeImportClause(transformedCode);
3354
3603
  if (existingImport) {
3355
3604
  transformedCode = transformedCode.replace(
3356
- existingImport[0],
3357
- `${existingImport[1]}, ${needed.join(", ")} } from '@csszyx/runtime'`
3605
+ existingImport.statement,
3606
+ `${existingImport.prefixWithBody}, ${needed.join(", ")} } from '@csszyx/runtime'`
3358
3607
  );
3359
3608
  } else {
3360
3609
  const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
@@ -3364,7 +3613,7 @@ function createCsszyxPlugins(options = {}) {
3364
3613
  transformed = true;
3365
3614
  }
3366
3615
  }
3367
- if (/\bszcn\s*\(/.test(code) && !transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) && shouldProcessSource(id)) {
3616
+ if ((usesSzcn || /\bszcn\s*\(/.test(code)) && !transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) && shouldProcessSource(id)) {
3368
3617
  transformedCode = `import '${THEME_GROUPS_VIRTUAL_ID}';
3369
3618
  ${transformedCode}`;
3370
3619
  transformed = true;
@@ -3427,6 +3676,7 @@ ${transformedCode}`;
3427
3676
  emitWarning(`[csszyx] ${warning}`);
3428
3677
  }
3429
3678
  state.spreadWarnings.clear();
3679
+ prescanResultHandoff.clear();
3430
3680
  if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
3431
3681
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
3432
3682
  }
@@ -3486,6 +3736,7 @@ ${transformedCode}`;
3486
3736
  * @param ctx - HMR context containing the changed file
3487
3737
  */
3488
3738
  handleHotUpdate(ctx) {
3739
+ prescanResultHandoff.clear();
3489
3740
  const scanCss = options.build?.scanCss;
3490
3741
  if (scanCss) {
3491
3742
  const root = ctx.server.config.root || process.cwd();
@@ -3935,6 +4186,7 @@ exports.deleteRSCModuleRecord = deleteRSCModuleRecord;
3935
4186
  exports.esbuildPlugin = esbuildPlugin;
3936
4187
  exports.extractGlobalVarAliasesForManifest = extractGlobalVarAliasesForManifest;
3937
4188
  exports.fileMayContainSafelistableSz = fileMayContainSafelistableSz;
4189
+ exports.findLocalImportSources = findLocalImportSources;
3938
4190
  exports.findRSCBoundaryViolation = findRSCBoundaryViolation;
3939
4191
  exports.findRSCGraphViolation = findRSCGraphViolation;
3940
4192
  exports.hasInjectableTailwindCandidate = hasInjectableTailwindCandidate;
@@ -3961,6 +4213,7 @@ exports.resolveGlobalVarScanCacheDir = resolveGlobalVarScanCacheDir;
3961
4213
  exports.resolveNativeCacheIdentity = resolveNativeCacheIdentity;
3962
4214
  exports.rewriteGlobalVarCssAliases = rewriteGlobalVarCssAliases;
3963
4215
  exports.rollupPlugin = rollupPlugin;
4216
+ exports.scanCustomPropertyNames = scanCustomPropertyNames;
3964
4217
  exports.scanGlobalVarCss = scanGlobalVarCss;
3965
4218
  exports.shouldEmitWarning = shouldEmitWarning;
3966
4219
  exports.shouldTrackGlobalVarSources = shouldTrackGlobalVarSources;
@@ -19,7 +19,7 @@ function _interopNamespaceCompat(e) {
19
19
  const fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
20
20
  const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
21
21
 
22
- const CACHE_SCHEMA_VERSION = 7;
22
+ const CACHE_SCHEMA_VERSION = 9;
23
23
  function resolveTransformCacheDir(rootDir, cacheDir) {
24
24
  return path__namespace.resolve(rootDir, cacheDir ?? ".csszyx/cache", "transform");
25
25
  }
@@ -134,6 +134,8 @@ function serializeResult(result) {
134
134
  transformed: result.transformed,
135
135
  usesRuntime: result.usesRuntime,
136
136
  usesMerge: result.usesMerge,
137
+ usesSzcn: result.usesSzcn,
138
+ usesSzPart: result.usesSzPart,
137
139
  usesColorVar: result.usesColorVar,
138
140
  classes: [...result.classes],
139
141
  rawClassNames: [...result.rawClassNames],
@@ -148,6 +150,8 @@ function deserializeResult(result) {
148
150
  transformed: result.transformed,
149
151
  usesRuntime: result.usesRuntime,
150
152
  usesMerge: result.usesMerge,
153
+ usesSzcn: result.usesSzcn,
154
+ usesSzPart: result.usesSzPart,
151
155
  usesColorVar: result.usesColorVar,
152
156
  classes: new Set(result.classes),
153
157
  rawClassNames: new Set(result.rawClassNames),