@csszyx/unplugin 0.11.6 → 0.11.8

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.
Files changed (41) hide show
  1. package/README.md +6 -1
  2. package/dist/css-mangler.cjs +44 -41
  3. package/dist/css-mangler.mjs +44 -41
  4. package/dist/index.cjs +5 -4
  5. package/dist/index.d.cts +2 -2
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +4 -4
  8. package/dist/next-config.cjs +1 -1
  9. package/dist/next-config.mjs +1 -1
  10. package/dist/next-prebuild.cjs +5 -5
  11. package/dist/next-prebuild.mjs +5 -5
  12. package/dist/next-turbo-loader.cjs +13 -12
  13. package/dist/next-turbo-loader.mjs +13 -12
  14. package/dist/next-watcher.cjs +2 -2
  15. package/dist/next-watcher.mjs +2 -2
  16. package/dist/shared/{unplugin.BFN_Ueyx.cjs → unplugin.C9dMG7ye.cjs} +7 -5
  17. package/dist/shared/{unplugin.PhOFTQpB.d.cts → unplugin.CNwV3RrQ.d.cts} +18 -5
  18. package/dist/shared/{unplugin.PhOFTQpB.d.mts → unplugin.CNwV3RrQ.d.mts} +18 -5
  19. package/dist/shared/unplugin.CcA7jP1r.cjs +48 -0
  20. package/dist/shared/{unplugin.ByzV6iZE.mjs → unplugin.D-K57LNR.mjs} +13 -7
  21. package/dist/shared/{unplugin.D0N9bprz.cjs → unplugin.DVCQC5wq.cjs} +12 -5
  22. package/dist/shared/{unplugin.CBKZoxQ1.cjs → unplugin.DdtOZANG.cjs} +51 -50
  23. package/dist/shared/{unplugin.ChwZMl_w.mjs → unplugin.Dg1tGPSd.mjs} +8 -6
  24. package/dist/shared/{unplugin.Rov-j_Wm.cjs → unplugin.DmuOKVS4.cjs} +21 -0
  25. package/dist/shared/{unplugin.CP2cz9bA.cjs → unplugin.DtPQz8Kg.cjs} +1291 -938
  26. package/dist/shared/{unplugin.BS60onPk.mjs → unplugin.DtYxS8M8.mjs} +1290 -938
  27. package/dist/shared/{unplugin.B3cvNxnk.mjs → unplugin.Rl8sIkHi.mjs} +51 -50
  28. package/dist/shared/unplugin.TtmP77BS.mjs +40 -0
  29. package/dist/shared/unplugin.nnsa7OVW.mjs +49 -0
  30. package/dist/vite.cjs +4 -4
  31. package/dist/vite.d.cts +2 -25
  32. package/dist/vite.d.mts +1 -25
  33. package/dist/vite.mjs +4 -8
  34. package/dist/webpack.cjs +4 -4
  35. package/dist/webpack.d.cts +2 -5
  36. package/dist/webpack.d.mts +1 -5
  37. package/dist/webpack.mjs +4 -8
  38. package/package.json +7 -7
  39. package/dist/shared/unplugin.B1mblcm-.mjs +0 -9
  40. package/dist/shared/unplugin.B3RHYokB.mjs +0 -29
  41. package/dist/shared/unplugin.BCwRIUs_.cjs +0 -12
@@ -6,7 +6,7 @@ import * as path from 'node:path';
6
6
  import { dirname } from 'node:path';
7
7
  import { performance } from 'node:perf_hooks';
8
8
  import { fileURLToPath } from 'node:url';
9
- import { scanGlobalVarUsages, sortStrings as sortStrings$1, ensureRustTransformAvailable, transformSourceCode, transformRust, transformOxc, transformRustBatch, ASTBudgetExceededError, transform, isRustTransformAvailable } from '@csszyx/compiler';
9
+ import { scanGlobalVarUsages, sortStrings as sortStrings$1, ensureRustTransformAvailable, transformSourceCode, transformRust, transformOxc, ASTBudgetExceededError, transformRustBatch, transform, isRustTransformAvailable } from '@csszyx/compiler';
10
10
  import { encode, compute_mangle_checksum } from '@csszyx/core';
11
11
  import { getNativePackageName } from '@csszyx/core/native';
12
12
  import { preprocess as preprocess$1 } from '@csszyx/svelte-adapter';
@@ -14,9 +14,9 @@ import { isTailwindReservedCustomProperty, CSSZYX_GLOBAL_ALIAS_PREFIX, isCsszyxG
14
14
  import { preprocess } from '@csszyx/vue-adapter';
15
15
  import { createUnplugin } from 'unplugin';
16
16
  import { mangleCSSSync } from '../css-mangler.mjs';
17
- import { s as sortStrings, e as escapeHtmlAttribute } from './unplugin.B1mblcm-.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';
17
+ import { i as importsRuntimeHelper, f as findRuntimeImportClause, a as insertAfterUseDirective } from './unplugin.nnsa7OVW.mjs';
18
+ import { n as normalizePathSeparators, r as resolveTransformCacheDir, c as createTransformCacheKey, w as writeTransformCache, e as evictOldTransformCacheEntries, a as readTransformCache, b as evictMemoryCacheToBudget } from './unplugin.D-K57LNR.mjs';
19
+ import { s as sortStrings, a as replaceEveryLiteral, u as unicodeEscape, b as escapeDoubleQuotedString, c as escapeSingleQuotedString, e as escapeHtmlAttribute, r as renderTailwindScannerCandidates } from './unplugin.TtmP77BS.mjs';
20
20
  import postcss from 'postcss';
21
21
  import valueParser from 'postcss-value-parser';
22
22
 
@@ -96,7 +96,8 @@ function buildScopeId(node) {
96
96
  }
97
97
  current = current.parent;
98
98
  }
99
- return parts.reverse().join(" > ") || DEFAULT_SCOPE_ID;
99
+ parts.reverse();
100
+ return parts.join(" > ") || DEFAULT_SCOPE_ID;
100
101
  }
101
102
 
102
103
  const VAR_REFERENCE_RE = /var\(\s*(--[\w-]+)/g;
@@ -369,7 +370,7 @@ function rewriteGlobalVarValue(value, aliases) {
369
370
  return;
370
371
  }
371
372
  const firstArgument = node.nodes.find((child) => child.type !== "space");
372
- if (!firstArgument || firstArgument.type !== "word") {
373
+ if (firstArgument?.type !== "word") {
373
374
  return;
374
375
  }
375
376
  const alias = aliases.get(firstArgument.value);
@@ -519,8 +520,8 @@ function createRSCModuleRecord(code, id) {
519
520
  }
520
521
  function deleteRSCModuleRecord(records, id) {
521
522
  const normalized = normalizeModuleId(id);
522
- const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
523
- const resolved = path.resolve(clean).replace(/\\/g, "/");
523
+ const clean = normalizePathSeparators(id.split("?")[0] ?? id);
524
+ const resolved = normalizePathSeparators(path.resolve(clean));
524
525
  pruneRSCModulePathCaches(/* @__PURE__ */ new Set([normalized, resolved, clean]));
525
526
  let deleted = records.delete(normalized);
526
527
  if (resolved !== normalized) {
@@ -566,7 +567,7 @@ const APP_ROUTER_ENTRIES = /* @__PURE__ */ new Set([
566
567
  "route"
567
568
  ]);
568
569
  function isNextAppRouterEntry(id) {
569
- const clean = id.split("?")[0]?.replace(/\\/g, "/") ?? id;
570
+ const clean = normalizePathSeparators(id.split("?")[0] ?? id);
570
571
  if (!clean.includes("/app/") && !clean.startsWith("app/")) return false;
571
572
  const basename = clean.split("/").pop() ?? "";
572
573
  const dotIdx = basename.indexOf(".");
@@ -615,38 +616,41 @@ function walkRSCGraph(current, records, chain, seen) {
615
616
  }
616
617
  return null;
617
618
  }
619
+ function findDirectiveQuoteEnd(code, start, quote) {
620
+ let escaped = false;
621
+ for (let index = start + 1; index < code.length; index++) {
622
+ const char = code[index];
623
+ if (escaped) {
624
+ escaped = false;
625
+ } else if (char === "\\") {
626
+ escaped = true;
627
+ } else if (char === quote) {
628
+ return index;
629
+ }
630
+ }
631
+ return -1;
632
+ }
633
+ function findDirectiveStatementEnd(code, start) {
634
+ let end = start;
635
+ while (end < code.length && /[ \t\r\n]/.test(code[end])) {
636
+ end++;
637
+ }
638
+ return code[end] === ";" ? end + 1 : end;
639
+ }
618
640
  function readDirectivePrologue(code) {
619
641
  const out = [];
620
- let i = code.charCodeAt(0) === 65279 ? 1 : 0;
642
+ let i = readCodePoint(code, 0) === 65279 ? 1 : 0;
621
643
  while (i < code.length) {
622
644
  i = skipWhitespaceAndComments(code, i);
623
645
  const quote = code[i];
624
646
  if (quote !== '"' && quote !== "'") {
625
647
  break;
626
648
  }
627
- let j = i + 1;
628
- let escaped = false;
629
- while (j < code.length) {
630
- const ch = code[j];
631
- if (escaped) {
632
- escaped = false;
633
- } else if (ch === "\\") {
634
- escaped = true;
635
- } else if (ch === quote) {
636
- break;
637
- }
638
- j++;
639
- }
640
- if (j >= code.length) {
649
+ const quoteEnd = findDirectiveQuoteEnd(code, i, quote);
650
+ if (quoteEnd === -1) {
641
651
  break;
642
652
  }
643
- let end = j + 1;
644
- while (end < code.length && /[ \t\r\n]/.test(code[end])) {
645
- end++;
646
- }
647
- if (code[end] === ";") {
648
- end++;
649
- }
653
+ const end = findDirectiveStatementEnd(code, quoteEnd + 1);
650
654
  out.push(code.slice(i, end).trim());
651
655
  i = end;
652
656
  }
@@ -702,55 +706,50 @@ function findStaticImports(code) {
702
706
  let cursor = 0;
703
707
  while (cursor < code.length) {
704
708
  const importStart = code.indexOf("import", cursor);
705
- if (importStart === -1) {
706
- break;
707
- }
708
- cursor = importStart + 6;
709
- if (importStart > 0 && isIdentifierPart(code.charCodeAt(importStart - 1)) || isIdentifierPart(code.charCodeAt(cursor))) {
710
- continue;
711
- }
712
- let position = skipAsciiWhitespace(code, cursor);
713
- const opener = code.charAt(position);
714
- if (opener === "(") {
715
- continue;
716
- }
717
- if (opener === '"' || opener === "'") {
718
- const literal2 = readQuotedString(code, position);
719
- if (literal2) {
720
- imports.push({ clause: null, source: literal2.value });
721
- cursor = literal2.end;
722
- }
723
- continue;
724
- }
725
- const clauseStart = position;
726
- let fromStart = -1;
727
- while (position < code.length) {
728
- if (code.startsWith("from", position) && (position === clauseStart || !isIdentifierPart(code.charCodeAt(position - 1))) && !isIdentifierPart(code.charCodeAt(position + 4))) {
729
- fromStart = position;
730
- break;
731
- }
732
- if (code.charAt(position) === ";") {
733
- break;
734
- }
735
- position += 1;
736
- }
737
- if (fromStart === -1) {
738
- continue;
739
- }
740
- const clause = code.slice(clauseStart, fromStart).trim();
741
- if (splitAsciiWhitespace(clause)[0] === "type") {
742
- cursor = fromStart + 4;
743
- continue;
744
- }
745
- position = skipAsciiWhitespace(code, fromStart + 4);
746
- const literal = readQuotedString(code, position);
747
- if (literal) {
748
- imports.push({ clause, source: literal.value });
749
- cursor = literal.end;
750
- }
709
+ if (importStart === -1) break;
710
+ const result = scanStaticImport(code, importStart);
711
+ cursor = result.nextCursor;
712
+ if (result.value) imports.push(result.value);
751
713
  }
752
714
  return imports;
753
715
  }
716
+ function scanStaticImport(code, importStart) {
717
+ const afterKeyword = importStart + 6;
718
+ if (importStart > 0 && isIdentifierPart(readCodePoint(code, importStart - 1)) || isIdentifierPart(readCodePoint(code, afterKeyword))) {
719
+ return { nextCursor: afterKeyword };
720
+ }
721
+ const clauseStart = skipAsciiWhitespace(code, afterKeyword);
722
+ const opener = code.charAt(clauseStart);
723
+ if (opener === "(") return { nextCursor: afterKeyword };
724
+ if (opener === '"' || opener === "'") {
725
+ return scanSideEffectImport(code, clauseStart, afterKeyword);
726
+ }
727
+ return scanImportClause(code, clauseStart, afterKeyword);
728
+ }
729
+ function scanSideEffectImport(code, literalStart, fallbackCursor) {
730
+ const literal = readQuotedString(code, literalStart);
731
+ return literal ? { nextCursor: literal.end, value: { clause: null, source: literal.value } } : { nextCursor: fallbackCursor };
732
+ }
733
+ function scanImportClause(code, clauseStart, fallbackCursor) {
734
+ const fromStart = findImportFromKeyword(code, clauseStart);
735
+ if (fromStart === -1) return { nextCursor: fallbackCursor };
736
+ const clause = code.slice(clauseStart, fromStart).trim();
737
+ if (splitAsciiWhitespace(clause)[0] === "type") {
738
+ return { nextCursor: fromStart + 4 };
739
+ }
740
+ const literalStart = skipAsciiWhitespace(code, fromStart + 4);
741
+ const literal = readQuotedString(code, literalStart);
742
+ return literal ? { nextCursor: literal.end, value: { clause, source: literal.value } } : { nextCursor: fallbackCursor };
743
+ }
744
+ function findImportFromKeyword(code, clauseStart) {
745
+ for (let position = clauseStart; position < code.length; position += 1) {
746
+ if (code.charAt(position) === ";") return -1;
747
+ if (code.startsWith("from", position) && (position === clauseStart || !isIdentifierPart(readCodePoint(code, position - 1))) && !isIdentifierPart(readCodePoint(code, position + 4))) {
748
+ return position;
749
+ }
750
+ }
751
+ return -1;
752
+ }
754
753
  function readQuotedString(code, start) {
755
754
  const quote = code.charAt(start);
756
755
  if (quote !== '"' && quote !== "'") {
@@ -778,7 +777,7 @@ function readQuotedString(code, start) {
778
777
  function skipAsciiWhitespace(code, start) {
779
778
  let index = start;
780
779
  while (index < code.length) {
781
- const charCode = code.charCodeAt(index);
780
+ const charCode = readCodePoint(code, index);
782
781
  if (charCode !== 9 && charCode !== 10 && charCode !== 13 && charCode !== 32) {
783
782
  break;
784
783
  }
@@ -923,9 +922,9 @@ function normalizeModuleId(id) {
923
922
  }
924
923
  let normalized;
925
924
  try {
926
- normalized = fs.realpathSync.native(clean).replace(/\\/g, "/");
925
+ normalized = normalizePathSeparators(fs.realpathSync.native(clean));
927
926
  } catch {
928
- normalized = path.resolve(clean).replace(/\\/g, "/");
927
+ normalized = normalizePathSeparators(path.resolve(clean));
929
928
  }
930
929
  normalizedModuleIdCache.set(clean, normalized);
931
930
  return normalized;
@@ -971,7 +970,7 @@ function resolveLocalModule(importer, source) {
971
970
  }
972
971
  function pruneRSCModulePathCaches(moduleIds) {
973
972
  for (const [key, value] of normalizedModuleIdCache) {
974
- const normalizedKey = key.replace(/\\/g, "/");
973
+ const normalizedKey = normalizePathSeparators(key);
975
974
  if (moduleIds.has(value) || moduleIds.has(normalizedKey)) {
976
975
  normalizedModuleIdCache.delete(key);
977
976
  }
@@ -982,43 +981,44 @@ function pruneRSCModulePathCaches(moduleIds) {
982
981
  }
983
982
  }
984
983
  }
985
- function readImportedSymbols(clause) {
986
- const symbols = [];
984
+ function readNamedImportedSymbols(clause) {
987
985
  const openBrace = clause.indexOf("{");
988
986
  const closeBrace = openBrace === -1 ? -1 : clause.indexOf("}", openBrace);
989
- if (openBrace !== -1 && closeBrace !== -1) {
990
- const namedPart = clause.slice(openBrace + 1, closeBrace);
991
- for (const part of namedPart.split(",")) {
992
- const trimmed = part.trim();
993
- if (!trimmed || trimmed.startsWith("type ")) {
994
- continue;
995
- }
996
- const sourceName = trimmed.replace(/^type[ \t]+/, "").split(/(?<![ \t])[ \t]+as[ \t]+/)[0]?.trim();
997
- if (sourceName) {
998
- symbols.push(sourceName);
999
- }
1000
- }
1001
- }
1002
- const namespaceParts = splitAsciiWhitespace(clause);
1003
- if (namespaceParts.length >= 3 && namespaceParts[0] === "*" && namespaceParts[1] === "as" && isIdentifier(namespaceParts[2] ?? "")) {
1004
- symbols.push(...FORBIDDEN_SYMBOLS);
987
+ if (openBrace === -1 || closeBrace === -1) {
988
+ return [];
1005
989
  }
990
+ return clause.slice(openBrace + 1, closeBrace).split(",").map((part) => part.trim()).filter((part) => part !== "" && !part.startsWith("type ")).map(
991
+ (part) => part.replace(/^type[ \t]+/, "").split(/(?<![ \t])[ \t]+as[ \t]+/)[0]?.trim()
992
+ ).filter((symbol) => Boolean(symbol));
993
+ }
994
+ function readForbiddenDefaultSymbol(clause) {
1006
995
  const braceStart = clause.indexOf("{");
1007
996
  const braceEnd = clause.indexOf("}", braceStart);
1008
997
  const stripped = braceStart !== -1 && braceEnd !== -1 ? clause.slice(0, braceStart) + clause.slice(braceEnd + 1) : clause;
1009
- const defaultCandidate = stripped.trimStart().split(",", 1)[0]?.trim() ?? "";
1010
- const defaultSymbol = isIdentifier(defaultCandidate) ? defaultCandidate : void 0;
1011
- if (defaultSymbol && FORBIDDEN_SYMBOLS.has(defaultSymbol)) {
998
+ const candidate = stripped.trimStart().split(",", 1)[0]?.trim() ?? "";
999
+ return isIdentifier(candidate) && FORBIDDEN_SYMBOLS.has(candidate) ? candidate : null;
1000
+ }
1001
+ function hasNamespaceImport(clause) {
1002
+ const parts = splitAsciiWhitespace(clause);
1003
+ return parts.length >= 3 && parts[0] === "*" && parts[1] === "as" && isIdentifier(parts[2] ?? "");
1004
+ }
1005
+ function readImportedSymbols(clause) {
1006
+ const symbols = readNamedImportedSymbols(clause);
1007
+ if (hasNamespaceImport(clause)) {
1008
+ symbols.push(...FORBIDDEN_SYMBOLS);
1009
+ }
1010
+ const defaultSymbol = readForbiddenDefaultSymbol(clause);
1011
+ if (defaultSymbol) {
1012
1012
  symbols.push(defaultSymbol);
1013
1013
  }
1014
1014
  return symbols;
1015
1015
  }
1016
1016
  function isIdentifier(value) {
1017
- if (value.length === 0 || !isIdentifierStart(value.charCodeAt(0))) {
1017
+ if (value.length === 0 || !isIdentifierStart(readCodePoint(value, 0))) {
1018
1018
  return false;
1019
1019
  }
1020
1020
  for (let index = 1; index < value.length; index += 1) {
1021
- const code = value.charCodeAt(index);
1021
+ const code = readCodePoint(value, index);
1022
1022
  if (!isIdentifierStart(code) && (code < 48 || code > 57)) {
1023
1023
  return false;
1024
1024
  }
@@ -1028,11 +1028,14 @@ function isIdentifier(value) {
1028
1028
  function isIdentifierStart(code) {
1029
1029
  return code === 36 || code === 95 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
1030
1030
  }
1031
+ function readCodePoint(value, index) {
1032
+ return value.codePointAt(index) ?? -1;
1033
+ }
1031
1034
  function splitAsciiWhitespace(value) {
1032
1035
  const parts = [];
1033
1036
  let start = -1;
1034
1037
  for (let index = 0; index <= value.length; index += 1) {
1035
- const code = index < value.length ? value.charCodeAt(index) : 32;
1038
+ const code = index < value.length ? readCodePoint(value, index) : 32;
1036
1039
  const whitespace = code === 9 || code === 10 || code === 13 || code === 32;
1037
1040
  if (whitespace) {
1038
1041
  if (start !== -1) {
@@ -1055,13 +1058,7 @@ function stripCommentsForImportScan(code) {
1055
1058
  const next = code[i + 1];
1056
1059
  if (quote) {
1057
1060
  out += ch;
1058
- if (escaped) {
1059
- escaped = false;
1060
- } else if (ch === "\\") {
1061
- escaped = true;
1062
- } else if (ch === quote) {
1063
- quote = null;
1064
- }
1061
+ ({ quote, escaped } = advanceQuotedImportScan(ch, quote, escaped));
1065
1062
  i++;
1066
1063
  continue;
1067
1064
  }
@@ -1072,28 +1069,15 @@ function stripCommentsForImportScan(code) {
1072
1069
  continue;
1073
1070
  }
1074
1071
  if (ch === "/" && next === "/") {
1075
- out += " ";
1076
- i += 2;
1077
- while (i < code.length && code[i] !== "\n") {
1078
- out += " ";
1079
- i++;
1080
- }
1072
+ const masked = maskLineComment(code, i);
1073
+ out += masked.value;
1074
+ i = masked.end;
1081
1075
  continue;
1082
1076
  }
1083
1077
  if (ch === "/" && next === "*") {
1084
- out += " ";
1085
- i += 2;
1086
- while (i < code.length) {
1087
- const blockCh = code[i];
1088
- const blockNext = code[i + 1];
1089
- if (blockCh === "*" && blockNext === "/") {
1090
- out += " ";
1091
- i += 2;
1092
- break;
1093
- }
1094
- out += blockCh === "\n" ? "\n" : " ";
1095
- i++;
1096
- }
1078
+ const masked = maskBlockComment(code, i);
1079
+ out += masked.value;
1080
+ i = masked.end;
1097
1081
  continue;
1098
1082
  }
1099
1083
  out += ch;
@@ -1101,6 +1085,31 @@ function stripCommentsForImportScan(code) {
1101
1085
  }
1102
1086
  return out;
1103
1087
  }
1088
+ function advanceQuotedImportScan(character, quote, escaped) {
1089
+ if (escaped) return { quote, escaped: false };
1090
+ if (character === "\\") return { quote, escaped: true };
1091
+ return { quote: character === quote ? null : quote, escaped: false };
1092
+ }
1093
+ function maskLineComment(code, start) {
1094
+ const newline = code.indexOf("\n", start + 2);
1095
+ const end = newline === -1 ? code.length : newline;
1096
+ return { end, value: " ".repeat(end - start) };
1097
+ }
1098
+ function maskBlockComment(code, start) {
1099
+ let end = start + 2;
1100
+ while (end < code.length) {
1101
+ if (code.charAt(end) === "*" && code.charAt(end + 1) === "/") {
1102
+ end += 2;
1103
+ break;
1104
+ }
1105
+ end += 1;
1106
+ }
1107
+ let value = "";
1108
+ for (let index = start; index < end; index += 1) {
1109
+ value += code.charAt(index) === "\n" ? "\n" : " ";
1110
+ }
1111
+ return { end, value };
1112
+ }
1104
1113
 
1105
1114
  const EMPTY_THEME = {
1106
1115
  colors: [],
@@ -1112,6 +1121,20 @@ const EMPTY_THEME = {
1112
1121
  shadows: [],
1113
1122
  breakpoints: []
1114
1123
  };
1124
+ function findMatchingBrace(source, openBrace) {
1125
+ let depth = 0;
1126
+ for (let index = openBrace; index < source.length; index++) {
1127
+ if (source[index] === "{") {
1128
+ depth++;
1129
+ } else if (source[index] === "}") {
1130
+ depth--;
1131
+ if (depth === 0) {
1132
+ return index;
1133
+ }
1134
+ }
1135
+ }
1136
+ return -1;
1137
+ }
1115
1138
  function stripLayerWrappers(css) {
1116
1139
  let result = "";
1117
1140
  let i = 0;
@@ -1127,26 +1150,13 @@ function stripLayerWrappers(css) {
1127
1150
  result += css.slice(layerIdx);
1128
1151
  break;
1129
1152
  }
1130
- let depth = 0;
1131
- let j = openBrace;
1132
- while (j < css.length) {
1133
- if (css[j] === "{") {
1134
- depth++;
1135
- }
1136
- if (css[j] === "}") {
1137
- depth--;
1138
- if (depth === 0) {
1139
- result += css.slice(openBrace + 1, j);
1140
- i = j + 1;
1141
- break;
1142
- }
1143
- }
1144
- j++;
1145
- }
1146
- if (depth !== 0) {
1153
+ const closeBrace = findMatchingBrace(css, openBrace);
1154
+ if (closeBrace === -1) {
1147
1155
  result += css.slice(openBrace);
1148
1156
  break;
1149
1157
  }
1158
+ result += css.slice(openBrace + 1, closeBrace);
1159
+ i = closeBrace + 1;
1150
1160
  }
1151
1161
  return result;
1152
1162
  }
@@ -1155,20 +1165,9 @@ function extractThemeBlocks(css) {
1155
1165
  const themeStart = /@theme\s+(?:inline\s+)?\{|@theme\{/g;
1156
1166
  for (const match of css.matchAll(themeStart)) {
1157
1167
  const openPos = css.indexOf("{", match.index);
1158
- let depth = 0;
1159
- let j = openPos;
1160
- while (j < css.length) {
1161
- if (css[j] === "{") {
1162
- depth++;
1163
- }
1164
- if (css[j] === "}") {
1165
- depth--;
1166
- if (depth === 0) {
1167
- blocks.push(css.slice(openPos + 1, j));
1168
- break;
1169
- }
1170
- }
1171
- j++;
1168
+ const closePos = findMatchingBrace(css, openPos);
1169
+ if (closePos !== -1) {
1170
+ blocks.push(css.slice(openPos + 1, closePos));
1172
1171
  }
1173
1172
  }
1174
1173
  return blocks;
@@ -1267,6 +1266,29 @@ function mergeThemes(themes) {
1267
1266
  function hasTokens(theme) {
1268
1267
  return Object.values(theme).some((arr) => arr.length > 0);
1269
1268
  }
1269
+ function readCustomPropertyName(block, dashes) {
1270
+ let end = dashes + 2;
1271
+ if (end >= block.length || !/[a-z]/.test(block[end])) {
1272
+ return null;
1273
+ }
1274
+ end++;
1275
+ while (end < block.length && /[a-z0-9-]/.test(block[end])) {
1276
+ end++;
1277
+ }
1278
+ return { name: block.slice(dashes + 2, end), end };
1279
+ }
1280
+ function findCustomPropertyDeclarationEnd(block, nameEnd) {
1281
+ let cursor = nameEnd;
1282
+ while (cursor < block.length && /\s/.test(block[cursor])) {
1283
+ cursor++;
1284
+ }
1285
+ if (block[cursor] === ":") {
1286
+ const valueStart = cursor + 1;
1287
+ const semicolon = block.indexOf(";", valueStart);
1288
+ return semicolon > valueStart ? semicolon + 1 : -1;
1289
+ }
1290
+ return block[nameEnd] === ";" ? nameEnd + 1 : -1;
1291
+ }
1270
1292
  function scanCustomPropertyNames(block) {
1271
1293
  const names = [];
1272
1294
  let i = 0;
@@ -1275,48 +1297,392 @@ function scanCustomPropertyNames(block) {
1275
1297
  if (dashes === -1) {
1276
1298
  break;
1277
1299
  }
1278
- let end = dashes + 2;
1279
- if (end >= block.length || !/[a-z]/.test(block[end])) {
1300
+ const property = readCustomPropertyName(block, dashes);
1301
+ if (!property) {
1280
1302
  i = dashes + 1;
1281
1303
  continue;
1282
1304
  }
1283
- end++;
1284
- while (end < block.length && /[a-z0-9-]/.test(block[end])) {
1285
- end++;
1305
+ const matchEnd = findCustomPropertyDeclarationEnd(block, property.end);
1306
+ if (matchEnd === -1) {
1307
+ i = dashes + 1;
1308
+ continue;
1309
+ }
1310
+ names.push(property.name);
1311
+ i = matchEnd;
1312
+ }
1313
+ return names;
1314
+ }
1315
+
1316
+ const REGEX_PREFIXES = new Set("([{:,;=!?&|+-*%^~<>/");
1317
+ const REGEX_PREFIX_KEYWORDS = /* @__PURE__ */ new Set([
1318
+ "await",
1319
+ "case",
1320
+ "delete",
1321
+ "do",
1322
+ "else",
1323
+ "in",
1324
+ "instanceof",
1325
+ "of",
1326
+ "return",
1327
+ "throw",
1328
+ "typeof",
1329
+ "void",
1330
+ "yield"
1331
+ ]);
1332
+ const SIMPLE_ESCAPES = {
1333
+ b: "\b",
1334
+ f: "\f",
1335
+ n: "\n",
1336
+ r: "\r",
1337
+ t: " ",
1338
+ v: "\v",
1339
+ "0": "\0"
1340
+ };
1341
+ function isFixedHex(value, width) {
1342
+ if (value.length !== width) return false;
1343
+ for (const character of value) {
1344
+ if (!"0123456789abcdefABCDEF".includes(character)) return false;
1345
+ }
1346
+ return true;
1347
+ }
1348
+ function decodeEscape(value, cursor) {
1349
+ const escaped = value[cursor];
1350
+ if (SIMPLE_ESCAPES[escaped] !== void 0) {
1351
+ return { value: SIMPLE_ESCAPES[escaped], cursor };
1352
+ }
1353
+ if (escaped === "\r" && value[cursor + 1] === "\n") {
1354
+ return { value: "", cursor: cursor + 1 };
1355
+ }
1356
+ if (escaped === "u" && value[cursor + 1] === "{") {
1357
+ const close = value.indexOf("}", cursor + 2);
1358
+ const hex2 = close === -1 ? "" : value.slice(cursor + 2, close);
1359
+ const codePoint = hex2 && /^[\dA-F]+$/i.test(hex2) ? Number.parseInt(hex2, 16) : -1;
1360
+ if (codePoint >= 0 && codePoint <= 1114111) {
1361
+ return { value: String.fromCodePoint(codePoint), cursor: close };
1362
+ }
1363
+ }
1364
+ let width = 0;
1365
+ if (escaped === "x") width = 2;
1366
+ else if (escaped === "u") width = 4;
1367
+ const hex = value.slice(cursor + 1, cursor + width + 1);
1368
+ if (width > 0 && isFixedHex(hex, width)) {
1369
+ return {
1370
+ value: String.fromCodePoint(Number.parseInt(hex, 16)),
1371
+ cursor: cursor + width
1372
+ };
1373
+ }
1374
+ const decoded = escaped === "\n" || escaped === "\r" ? "" : escaped;
1375
+ return { value: decoded, cursor };
1376
+ }
1377
+ function decodeJavaScriptString(value) {
1378
+ let output = "";
1379
+ let cursor = 0;
1380
+ while (cursor < value.length) {
1381
+ if (value[cursor] !== "\\" || cursor + 1 >= value.length) {
1382
+ output += value[cursor++];
1383
+ continue;
1384
+ }
1385
+ const decoded = decodeEscape(value, cursor + 1);
1386
+ output += decoded.value;
1387
+ cursor = decoded.cursor + 1;
1388
+ }
1389
+ return output;
1390
+ }
1391
+ function decodeAttributeWhitespace(value) {
1392
+ return value.replace(
1393
+ /&#(?:(\d+)|x([\da-f]+));|&(?:Tab|NewLine);/gi,
1394
+ (entity, decimal, hexadecimal) => {
1395
+ if (!decimal && !hexadecimal) return " ";
1396
+ const codePoint = Number.parseInt(decimal ?? hexadecimal ?? "", decimal ? 10 : 16);
1397
+ return [9, 10, 12, 13, 32].includes(codePoint) ? " " : entity;
1398
+ }
1399
+ );
1400
+ }
1401
+ function collectClassTokens(target, value) {
1402
+ for (const className of value.split(/\s+/)) {
1403
+ if (className) target.add(className);
1404
+ }
1405
+ }
1406
+ function quotedEnd(source, start, quote) {
1407
+ let cursor = start + 1;
1408
+ while (cursor < source.length) {
1409
+ if (source[cursor] === "\\") cursor += 2;
1410
+ else if (source[cursor++] === quote) break;
1411
+ }
1412
+ return cursor;
1413
+ }
1414
+ function commentEnd(source, start) {
1415
+ if (source[start + 1] === "/") {
1416
+ const end2 = source.indexOf("\n", start + 2);
1417
+ return end2 === -1 ? source.length : end2 + 1;
1418
+ }
1419
+ const end = source.indexOf("*/", start + 2);
1420
+ return end === -1 ? source.length : end + 2;
1421
+ }
1422
+ function isRegexStart(source, start) {
1423
+ let cursor = start - 1;
1424
+ while (cursor >= 0 && /\s/.test(source[cursor])) cursor--;
1425
+ if (source[cursor] === "<" && /[A-Z]/i.test(source[start + 1] ?? "")) return false;
1426
+ if (cursor < 0 || REGEX_PREFIXES.has(source[cursor])) return true;
1427
+ if (!/[\w$]/.test(source[cursor])) return false;
1428
+ const end = cursor + 1;
1429
+ while (cursor >= 0 && /[\w$]/.test(source[cursor])) cursor--;
1430
+ return REGEX_PREFIX_KEYWORDS.has(source.slice(cursor + 1, end));
1431
+ }
1432
+ function regexEnd(source, start) {
1433
+ let cursor = start + 1;
1434
+ let inCharacterClass = false;
1435
+ while (cursor < source.length) {
1436
+ const character = source[cursor];
1437
+ if (character === "\\") cursor += 2;
1438
+ else if (character === "[") {
1439
+ inCharacterClass = true;
1440
+ cursor++;
1441
+ } else if (character === "]") {
1442
+ inCharacterClass = false;
1443
+ cursor++;
1444
+ } else if (character === "/" && !inCharacterClass) {
1445
+ cursor++;
1446
+ while (/[A-Z]/i.test(source[cursor] ?? "")) cursor++;
1447
+ break;
1448
+ } else cursor++;
1449
+ }
1450
+ return cursor;
1451
+ }
1452
+ function templateEnd(source, start) {
1453
+ let cursor = start + 1;
1454
+ while (cursor < source.length) {
1455
+ if (source[cursor] === "\\") cursor += 2;
1456
+ else if (source[cursor] === "`") return cursor + 1;
1457
+ else if (source[cursor] === "$" && source[cursor + 1] === "{") {
1458
+ cursor = findBalancedCodeEnd(source, cursor + 2);
1459
+ } else cursor++;
1460
+ }
1461
+ return source.length;
1462
+ }
1463
+ function lexicalRegionEnd(source, start) {
1464
+ const character = source[start];
1465
+ if (character === '"' || character === "'") return quotedEnd(source, start, character);
1466
+ if (character === "`") return templateEnd(source, start);
1467
+ if (character === "/" && (source[start + 1] === "/" || source[start + 1] === "*")) {
1468
+ return commentEnd(source, start);
1469
+ }
1470
+ if (character === "/" && isRegexStart(source, start)) return regexEnd(source, start);
1471
+ return start;
1472
+ }
1473
+ function findBalancedCodeEnd(source, bodyStart) {
1474
+ let depth = 1;
1475
+ let cursor = bodyStart;
1476
+ while (cursor < source.length) {
1477
+ const regionEnd = lexicalRegionEnd(source, cursor);
1478
+ if (regionEnd !== cursor) {
1479
+ cursor = regionEnd;
1480
+ continue;
1481
+ }
1482
+ if (source[cursor] === "{") depth++;
1483
+ else if (source[cursor] === "}" && --depth === 0) return cursor;
1484
+ cursor++;
1485
+ }
1486
+ return source.length;
1487
+ }
1488
+ function collectTemplateTokens(target, source, start) {
1489
+ let cursor = start + 1;
1490
+ let quasiStart = cursor;
1491
+ while (cursor < source.length) {
1492
+ if (source[cursor] === "\\") {
1493
+ cursor += 2;
1494
+ continue;
1495
+ }
1496
+ if (source[cursor] === "`") {
1497
+ collectClassTokens(target, decodeJavaScriptString(source.slice(quasiStart, cursor)));
1498
+ return cursor + 1;
1499
+ }
1500
+ if (source[cursor] === "$" && source[cursor + 1] === "{") {
1501
+ collectClassTokens(target, decodeJavaScriptString(source.slice(quasiStart, cursor)));
1502
+ const expressionEnd = findBalancedCodeEnd(source, cursor + 2);
1503
+ collectExpressionTokens(target, source.slice(cursor + 2, expressionEnd));
1504
+ cursor = expressionEnd + 1;
1505
+ quasiStart = cursor;
1506
+ continue;
1507
+ }
1508
+ cursor++;
1509
+ }
1510
+ collectClassTokens(target, decodeJavaScriptString(source.slice(quasiStart)));
1511
+ return source.length;
1512
+ }
1513
+ function collectExpressionTokens(target, expression) {
1514
+ let cursor = 0;
1515
+ let pendingLiteral = "";
1516
+ let previousLiteralEnd = -1;
1517
+ const flushPending = () => {
1518
+ if (pendingLiteral) collectClassTokens(target, pendingLiteral);
1519
+ pendingLiteral = "";
1520
+ };
1521
+ while (cursor < expression.length) {
1522
+ const character = expression[cursor];
1523
+ if (character === "/" && (expression[cursor + 1] === "/" || expression[cursor + 1] === "*")) {
1524
+ cursor = commentEnd(expression, cursor);
1525
+ continue;
1526
+ }
1527
+ if (character === "/" && isRegexStart(expression, cursor)) {
1528
+ cursor = regexEnd(expression, cursor);
1529
+ continue;
1530
+ }
1531
+ if (character === "`") {
1532
+ flushPending();
1533
+ cursor = collectTemplateTokens(target, expression, cursor);
1534
+ previousLiteralEnd = -1;
1535
+ continue;
1286
1536
  }
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])) {
1537
+ if (character !== '"' && character !== "'") {
1291
1538
  cursor++;
1539
+ continue;
1292
1540
  }
1293
- if (block[cursor] === ":") {
1294
- const valueStart = cursor + 1;
1295
- const semi = block.indexOf(";", valueStart);
1296
- if (semi > valueStart) {
1297
- matchEnd = semi + 1;
1298
- }
1541
+ const end = quotedEnd(expression, cursor, character);
1542
+ const decoded = decodeJavaScriptString(expression.slice(cursor + 1, end - 1));
1543
+ const joinsPrevious = previousLiteralEnd >= 0 && expression.slice(previousLiteralEnd, cursor).trim() === "+";
1544
+ if (!joinsPrevious) flushPending();
1545
+ pendingLiteral += decoded;
1546
+ previousLiteralEnd = end;
1547
+ cursor = end;
1548
+ }
1549
+ flushPending();
1550
+ }
1551
+ function triviaEnd(source, start) {
1552
+ let cursor = start;
1553
+ while (cursor < source.length) {
1554
+ if (/\s/.test(source[cursor])) cursor++;
1555
+ else if (source[cursor] === "/" && (source[cursor + 1] === "/" || source[cursor + 1] === "*")) {
1556
+ cursor = commentEnd(source, cursor);
1557
+ } else break;
1558
+ }
1559
+ return cursor;
1560
+ }
1561
+ function propertyExpressionEnd(source, start) {
1562
+ let cursor = start;
1563
+ let parenDepth = 0;
1564
+ let bracketDepth = 0;
1565
+ let braceDepth = 0;
1566
+ while (cursor < source.length) {
1567
+ const regionEnd = lexicalRegionEnd(source, cursor);
1568
+ if (regionEnd !== cursor) {
1569
+ cursor = regionEnd;
1570
+ continue;
1299
1571
  }
1300
- if (matchEnd === -1 && block[end] === ";") {
1301
- matchEnd = end + 1;
1572
+ const character = source[cursor];
1573
+ if (character === "(") parenDepth++;
1574
+ else if (character === ")") parenDepth--;
1575
+ else if (character === "[") bracketDepth++;
1576
+ else if (character === "]") bracketDepth--;
1577
+ else if (character === "{") braceDepth++;
1578
+ else if (character === "}" && braceDepth > 0) braceDepth--;
1579
+ else if ((character === "," || character === ";" || character === "}") && parenDepth === 0 && bracketDepth === 0 && braceDepth === 0)
1580
+ return cursor;
1581
+ cursor++;
1582
+ }
1583
+ return source.length;
1584
+ }
1585
+ function isClassSink(source, start, end) {
1586
+ const name = source.slice(start, end);
1587
+ if (name !== "class" && name !== "className") return false;
1588
+ let previous = start - 1;
1589
+ while (previous >= 0 && /\s/.test(source[previous])) previous--;
1590
+ return source[previous] !== "." && source[previous] !== "-";
1591
+ }
1592
+ function collectClassDirective(target, source, sinkStart, valueStart) {
1593
+ if (!/[\w$-]/.test(source[valueStart] ?? "")) return null;
1594
+ let directiveEnd = valueStart;
1595
+ while (/[\w$-]/.test(source[directiveEnd] ?? "")) directiveEnd++;
1596
+ const equals = triviaEnd(source, directiveEnd);
1597
+ if (source[equals] !== "=") {
1598
+ const insideMarkupTag = source.lastIndexOf("<", sinkStart) > source.lastIndexOf(">", sinkStart);
1599
+ const next = source[equals] ?? "";
1600
+ const continuesMarkupTag = next === "/" || next === ">" || next === "{" || /[A-Z_:]/i.test(next);
1601
+ if (!insideMarkupTag || !continuesMarkupTag) return null;
1602
+ collectClassTokens(target, source.slice(valueStart, directiveEnd));
1603
+ return directiveEnd;
1604
+ }
1605
+ const directive = source.slice(valueStart, directiveEnd);
1606
+ const directiveValueStart = triviaEnd(source, equals + 1);
1607
+ const directiveQuote = source[directiveValueStart];
1608
+ if (directive !== "list") collectClassTokens(target, directive);
1609
+ if (directiveQuote === "{") {
1610
+ const end = findBalancedCodeEnd(source, directiveValueStart + 1);
1611
+ if (directive === "list") {
1612
+ collectExpressionTokens(target, source.slice(directiveValueStart + 1, end));
1613
+ }
1614
+ return end + 1;
1615
+ }
1616
+ if (directive === "list" && (directiveQuote === '"' || directiveQuote === "'")) {
1617
+ const end = quotedEnd(source, directiveValueStart, directiveQuote);
1618
+ collectExpressionTokens(target, source.slice(directiveValueStart, end));
1619
+ return end;
1620
+ }
1621
+ return directiveValueStart;
1622
+ }
1623
+ function collectClassSink(target, source, start, nameEnd) {
1624
+ const operatorStart = triviaEnd(source, nameEnd);
1625
+ const operator = source[operatorStart];
1626
+ if (operator !== "=" && operator !== ":") return nameEnd;
1627
+ const valueStart = triviaEnd(source, operatorStart + 1);
1628
+ const quote = source[valueStart];
1629
+ const vueBinding = operator === "=" && source.slice(Math.max(0, start - 1), start) === ":";
1630
+ if (operator === ":" && valueStart === operatorStart + 1) {
1631
+ const directiveEnd = collectClassDirective(target, source, start, valueStart);
1632
+ if (directiveEnd !== null) return directiveEnd;
1633
+ }
1634
+ if (quote === '"' || quote === "'") {
1635
+ const end = quotedEnd(source, valueStart, quote);
1636
+ const value = source.slice(valueStart + 1, end - 1);
1637
+ if (vueBinding) collectExpressionTokens(target, decodeAttributeWhitespace(value));
1638
+ else collectClassTokens(target, decodeAttributeWhitespace(value));
1639
+ return end;
1640
+ }
1641
+ if (operator === "=" && quote === "{") {
1642
+ const end = findBalancedCodeEnd(source, valueStart + 1);
1643
+ collectExpressionTokens(target, source.slice(valueStart + 1, end));
1644
+ return end + 1;
1645
+ }
1646
+ if (operator === ":") {
1647
+ const end = propertyExpressionEnd(source, valueStart);
1648
+ collectExpressionTokens(target, source.slice(valueStart, end));
1649
+ return end;
1650
+ }
1651
+ return valueStart;
1652
+ }
1653
+ function collectAuthoredClassNames(source) {
1654
+ const classes = /* @__PURE__ */ new Set();
1655
+ let cursor = 0;
1656
+ while (cursor < source.length) {
1657
+ if (source[cursor] === '"' || source[cursor] === "'") {
1658
+ const keyEnd = quotedEnd(source, cursor, source[cursor]);
1659
+ const key = decodeJavaScriptString(source.slice(cursor + 1, keyEnd - 1));
1660
+ cursor = key === "class" || key === "className" ? collectClassSink(classes, source, cursor, keyEnd) : keyEnd;
1661
+ continue;
1302
1662
  }
1303
- if (matchEnd === -1) {
1304
- i = dashes + 1;
1663
+ const regionEnd = lexicalRegionEnd(source, cursor);
1664
+ if (regionEnd !== cursor) {
1665
+ cursor = regionEnd;
1305
1666
  continue;
1306
1667
  }
1307
- names.push(name);
1308
- i = matchEnd;
1668
+ if (!/[A-Z_$]/i.test(source[cursor])) {
1669
+ cursor++;
1670
+ continue;
1671
+ }
1672
+ let nameEnd = cursor + 1;
1673
+ while (/[\w$]/.test(source[nameEnd] ?? "")) nameEnd++;
1674
+ cursor = isClassSink(source, cursor, nameEnd) ? collectClassSink(classes, source, cursor, nameEnd) : nameEnd;
1309
1675
  }
1310
- return names;
1676
+ return classes;
1311
1677
  }
1312
1678
 
1313
1679
  const GLOB_MAGIC_RE = /[*?[\]{}]/;
1314
1680
  const DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", ".turbo", "dist", "build"]);
1315
1681
  function normalizeFileId(id) {
1316
- return id.split(/[?#]/, 1)[0].replace(/\\/g, "/");
1682
+ return normalizePathSeparators(id.split(/[?#]/, 1)[0]);
1317
1683
  }
1318
1684
  function normalizeRoot(rootDir) {
1319
- return path.resolve(rootDir).replace(/\\/g, "/");
1685
+ return normalizePathSeparators(path.resolve(rootDir));
1320
1686
  }
1321
1687
  function hasGlobMagic(pattern) {
1322
1688
  return GLOB_MAGIC_RE.test(pattern);
@@ -1325,7 +1691,7 @@ function escapeRegExp(ch) {
1325
1691
  return /[|\\{}()[\]^$+?.]/.test(ch) ? `\\${ch}` : ch;
1326
1692
  }
1327
1693
  function globToRegExp(pattern) {
1328
- const normalized = pattern.replace(/\\/g, "/");
1694
+ const normalized = normalizePathSeparators(pattern);
1329
1695
  let out = "^";
1330
1696
  for (let i = 0; i < normalized.length; i++) {
1331
1697
  const ch = normalized[i];
@@ -1348,7 +1714,7 @@ function globToRegExp(pattern) {
1348
1714
  function relativeToRoot(file, rootDir) {
1349
1715
  const root = normalizeRoot(rootDir);
1350
1716
  const normalizedFile = normalizeFileId(file);
1351
- return path.posix.relative(root, normalizedFile).replace(/\\/g, "/");
1717
+ return normalizePathSeparators(path.posix.relative(root, normalizedFile));
1352
1718
  }
1353
1719
  function matchesPattern(id, pattern, rootDir) {
1354
1720
  const file = normalizeFileId(id);
@@ -1357,7 +1723,7 @@ function matchesPattern(id, pattern, rootDir) {
1357
1723
  pattern.lastIndex = 0;
1358
1724
  return pattern.test(file) || pattern.test(relative);
1359
1725
  }
1360
- const normalizedPattern = pattern.replace(/\\/g, "/");
1726
+ const normalizedPattern = normalizePathSeparators(pattern);
1361
1727
  if (hasGlobMagic(normalizedPattern)) {
1362
1728
  const re = globToRegExp(normalizedPattern);
1363
1729
  return re.test(file) || re.test(relative);
@@ -1372,27 +1738,36 @@ function matchesAnyPattern(id, patterns, rootDir) {
1372
1738
  const list = Array.isArray(patterns) ? patterns : [patterns];
1373
1739
  return list.some((pattern) => matchesPattern(id, pattern, rootDir));
1374
1740
  }
1741
+ function collectDirectoryFiles(dir, files) {
1742
+ let entries;
1743
+ try {
1744
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1745
+ } catch {
1746
+ return;
1747
+ }
1748
+ for (const entry of entries) {
1749
+ const full = path.join(dir, entry.name);
1750
+ if (entry.isDirectory()) {
1751
+ if (!DEFAULT_IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1752
+ collectDirectoryFiles(full, files);
1753
+ }
1754
+ } else {
1755
+ files.add(path.resolve(full));
1756
+ }
1757
+ }
1758
+ }
1759
+ function isExpandedPatternMatch(file, patterns, rootDir) {
1760
+ return patterns.some((pattern) => {
1761
+ if (hasGlobMagic(pattern)) {
1762
+ return matchesPattern(file, pattern, rootDir);
1763
+ }
1764
+ const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
1765
+ return normalizeFileId(path.resolve(resolved)) === normalizeFileId(file);
1766
+ });
1767
+ }
1375
1768
  function expandFilePatterns(rootDir, patterns) {
1376
1769
  const list = Array.isArray(patterns) ? patterns : [patterns];
1377
1770
  const files = /* @__PURE__ */ new Set();
1378
- const walk = (dir) => {
1379
- let entries;
1380
- try {
1381
- entries = fs.readdirSync(dir, { withFileTypes: true });
1382
- } catch {
1383
- return;
1384
- }
1385
- for (const entry of entries) {
1386
- const full = path.join(dir, entry.name);
1387
- if (entry.isDirectory()) {
1388
- if (!DEFAULT_IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1389
- walk(full);
1390
- }
1391
- } else {
1392
- files.add(path.resolve(full));
1393
- }
1394
- }
1395
- };
1396
1771
  let needsWalk = false;
1397
1772
  for (const pattern of list) {
1398
1773
  const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
@@ -1405,30 +1780,25 @@ function expandFilePatterns(rootDir, patterns) {
1405
1780
  }
1406
1781
  }
1407
1782
  if (needsWalk) {
1408
- walk(rootDir);
1783
+ collectDirectoryFiles(rootDir, files);
1409
1784
  for (const file of Array.from(files)) {
1410
- if (!list.some(
1411
- (pattern) => hasGlobMagic(pattern) && matchesPattern(file, pattern, rootDir)
1412
- )) {
1413
- const isLiteralMatch = list.some((pattern) => {
1414
- if (hasGlobMagic(pattern)) {
1415
- return false;
1416
- }
1417
- const resolved = path.isAbsolute(pattern) ? pattern : path.join(rootDir, pattern);
1418
- return normalizeFileId(path.resolve(resolved)) === normalizeFileId(file);
1419
- });
1420
- if (!isLiteralMatch) {
1421
- files.delete(file);
1422
- }
1785
+ if (!isExpandedPatternMatch(file, list, rootDir)) {
1786
+ files.delete(file);
1423
1787
  }
1424
1788
  }
1425
1789
  }
1426
1790
  return sortStrings(files);
1427
1791
  }
1428
1792
 
1793
+ const LINE_SEPARATOR$1 = String.fromCodePoint(8232);
1794
+ const PARAGRAPH_SEPARATOR$1 = String.fromCodePoint(8233);
1429
1795
  function safeJsonForScriptTag(value, prettyPrint = false) {
1430
1796
  const json = prettyPrint ? JSON.stringify(value, null, 2) : JSON.stringify(value);
1431
- return json.replace(/</g, "\\u003C").replace(/>/g, "\\u003E").replace(/&/g, "\\u0026").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1797
+ let escaped = replaceEveryLiteral(json, "<", unicodeEscape("003C"));
1798
+ escaped = replaceEveryLiteral(escaped, ">", unicodeEscape("003E"));
1799
+ escaped = replaceEveryLiteral(escaped, "&", unicodeEscape("0026"));
1800
+ escaped = replaceEveryLiteral(escaped, LINE_SEPARATOR$1, unicodeEscape("2028"));
1801
+ return replaceEveryLiteral(escaped, PARAGRAPH_SEPARATOR$1, unicodeEscape("2029"));
1432
1802
  }
1433
1803
  function injectHtmlOpeningAttr(html, attr) {
1434
1804
  const lower = html.toLowerCase();
@@ -1560,11 +1930,17 @@ function injectRecoveryManifest(html, manifest) {
1560
1930
  return html + scriptTag;
1561
1931
  }
1562
1932
 
1933
+ const LINE_SEPARATOR = String.fromCodePoint(8232);
1934
+ const PARAGRAPH_SEPARATOR = String.fromCodePoint(8233);
1563
1935
  function escapeJsonForInlineScript(json) {
1564
- return json.replace(/`/g, "\\u0060").replace(/\$/g, "\\u0024").replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1936
+ let escaped = replaceEveryLiteral(json, "`", unicodeEscape("0060"));
1937
+ escaped = replaceEveryLiteral(escaped, "$", unicodeEscape("0024"));
1938
+ escaped = replaceEveryLiteral(escaped, "<", unicodeEscape("003c"));
1939
+ escaped = replaceEveryLiteral(escaped, LINE_SEPARATOR, unicodeEscape("2028"));
1940
+ return replaceEveryLiteral(escaped, PARAGRAPH_SEPARATOR, unicodeEscape("2029"));
1565
1941
  }
1566
1942
  function escapeForDoubleQuotedString(value) {
1567
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1943
+ return escapeDoubleQuotedString(value);
1568
1944
  }
1569
1945
 
1570
1946
  function isParserMode(value) {
@@ -1611,15 +1987,13 @@ function isSameFileVersion(before, after) {
1611
1987
  return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeNs === after.mtimeNs && before.ctimeNs === after.ctimeNs;
1612
1988
  }
1613
1989
 
1614
- function escapeTsString(value) {
1615
- return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
1616
- }
1617
1990
  function generateThemeDts(opts) {
1618
1991
  const { theme, sourceFiles } = opts;
1619
1992
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1620
- const sources = sourceFiles.join(", ").replace(/[\r\n]/g, " ");
1621
- const toUnion = (tokens) => tokens.map((t) => `'${escapeTsString(t)}'`).join(" | ");
1622
- const quoteKey = (key) => /^[a-z_$][\w$]*$/i.test(key) ? key : `'${escapeTsString(key)}'`;
1993
+ const withoutCarriageReturns = replaceEveryLiteral(sourceFiles.join(", "), "\r", " ");
1994
+ const sources = replaceEveryLiteral(withoutCarriageReturns, "\n", " ");
1995
+ const toUnion = (tokens) => tokens.map((t) => `'${escapeSingleQuotedString(t)}'`).join(" | ");
1996
+ const quoteKey = (key) => /^[a-z_$][\w$]*$/i.test(key) ? key : `'${escapeSingleQuotedString(key)}'`;
1623
1997
  const entries = [];
1624
1998
  if (theme.colors.length > 0) {
1625
1999
  entries.push(` colors: ${toUnion(theme.colors)};`);
@@ -1757,6 +2131,15 @@ function createThemeGroupsModule(tokens) {
1757
2131
  ].join("\n");
1758
2132
  }
1759
2133
 
2134
+ function registerWebpackAssetProcessor(compiler, processAssets) {
2135
+ compiler.hooks.compilation.tap("csszyx:post", (compilation) => {
2136
+ const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE || compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE;
2137
+ compilation.hooks.processAssets.tap(
2138
+ { name: "csszyx:post", stage: stage || 400 },
2139
+ (assets) => processAssets(assets, compilation, compiler)
2140
+ );
2141
+ });
2142
+ }
1760
2143
  const CHECKSUM_PLACEHOLDER = "___CSSZYX_CHECKSUM___";
1761
2144
  const MANGLE_MAP_PLACEHOLDER = "___CSSZYX_MANGLE_MAP___";
1762
2145
  const VAR_MANGLE_MAP_PLACEHOLDER = "___CSSZYX_VAR_MANGLE_MAP___";
@@ -1768,7 +2151,6 @@ const TRANSFORM_MEMORY_CACHE_MAX_CODE_CHARS = 32e6;
1768
2151
  const MAX_SAFELIST_CLASSES = 1e5;
1769
2152
  const DEFAULT_VAR_MANGLE_MAP_MAX_BYTES = 100 * 1024;
1770
2153
  const GLOBAL_VAR_ALIAS_MAP_OWNER = "\0csszyx:global-var-aliases";
1771
- const DIRECTIVE_PROLOGUE_PREFIX_RE = /^((?:\s|\/\/[^\n]*\n|\/\*(?:[^*]|\*(?!\/))*\*\/)*)(['"]use (?:client|server)['"];?\s*)/;
1772
2154
  function findOpeningTag(source, tag) {
1773
2155
  const lower = source.toLowerCase();
1774
2156
  const marker = `<${tag}`;
@@ -1841,10 +2223,10 @@ function stripCssBlockComments(code) {
1841
2223
  let i = 0;
1842
2224
  const n = code.length;
1843
2225
  while (i < n) {
1844
- if (code.charCodeAt(i) === SLASH && code.charCodeAt(i + 1) === STAR) {
2226
+ if (code.codePointAt(i) === SLASH && code.codePointAt(i + 1) === STAR) {
1845
2227
  out += code.slice(last, i);
1846
2228
  i += 2;
1847
- while (i < n && !(code.charCodeAt(i) === STAR && code.charCodeAt(i + 1) === SLASH)) {
2229
+ while (i < n && !(code.codePointAt(i) === STAR && code.codePointAt(i + 1) === SLASH)) {
1848
2230
  i++;
1849
2231
  }
1850
2232
  i += 2;
@@ -1950,9 +2332,17 @@ function shouldEmitWarning(quiet, devOnly, isProduction) {
1950
2332
  return true;
1951
2333
  }
1952
2334
  function normalizeForMatch(p) {
1953
- const n = p.replace(/\\/g, "/");
2335
+ const n = normalizePathSeparators(p);
1954
2336
  return n.length > 1 && n.endsWith("/") ? n.slice(0, -1) : n;
1955
2337
  }
2338
+ function findJsxExpressionEnd(code, bodyStart) {
2339
+ return findBalancedCodeEnd(code, bodyStart);
2340
+ }
2341
+ function warnPrescanBudgetSkip(filePath) {
2342
+ console.warn(
2343
+ `[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.`
2344
+ );
2345
+ }
1956
2346
  function resolveCompileSourceDirs(root, sources, realpathDir = (p) => {
1957
2347
  try {
1958
2348
  const real = fs.realpathSync(p);
@@ -2005,6 +2395,9 @@ function fileMayContainSafelistableSz(content) {
2005
2395
  // engine-parity harness caught its classes missing on all engines.
2006
2396
  content.includes("dynamic(");
2007
2397
  }
2398
+ function mangleEligibleClasses(ownedClasses, authoredClasses) {
2399
+ return sortStrings$1([...ownedClasses].filter((className) => !authoredClasses.has(className)));
2400
+ }
2008
2401
  function isPackagesSkippedSource(id, sourceDirs = []) {
2009
2402
  const p = normalizeForMatch(id);
2010
2403
  if (p.includes("node_modules")) {
@@ -2025,8 +2418,8 @@ ${list}
2025
2418
  Add the package directory to \`compileSources\` (or move the file out of packages/) \u2014 otherwise their \`sz\` produces no CSS.`;
2026
2419
  }
2027
2420
  function computeSafelistRelPath(rootDir, safelistFilename, cssId) {
2028
- const safelistPath = path.join(rootDir, safelistFilename).replace(/\\/g, "/");
2029
- const cssDir = path.dirname(cssId).replace(/\\/g, "/");
2421
+ const safelistPath = normalizePathSeparators(path.join(rootDir, safelistFilename));
2422
+ const cssDir = normalizePathSeparators(path.dirname(cssId));
2030
2423
  let relPath = path.posix.relative(cssDir, safelistPath);
2031
2424
  if (!relPath.startsWith(".")) {
2032
2425
  relPath = `./${relPath}`;
@@ -2200,7 +2593,10 @@ function normalizeGlobalVarAliasesForCache(aliases) {
2200
2593
  if (!aliases) {
2201
2594
  return [];
2202
2595
  }
2203
- const entries = aliases instanceof Map ? aliases.entries() : Array.isArray(aliases) ? aliases : Object.entries(aliases);
2596
+ let entries;
2597
+ if (aliases instanceof Map) entries = aliases.entries();
2598
+ else if (Array.isArray(aliases)) entries = aliases;
2599
+ else entries = Object.entries(aliases);
2204
2600
  const normalized = /* @__PURE__ */ new Map();
2205
2601
  for (const [original, alias] of entries) {
2206
2602
  if (original.startsWith("--") && alias.startsWith("--")) {
@@ -2405,7 +2801,7 @@ function findPackageVersionFromFile(file, fallback) {
2405
2801
  }
2406
2802
  }
2407
2803
  function normalizeSourceFilename(filename) {
2408
- return filename.replace(/\\/g, "/");
2804
+ return normalizePathSeparators(filename);
2409
2805
  }
2410
2806
  const SCRIPT_ID_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
2411
2807
  const SOURCE_MODULE_EXTENSIONS = [...SCRIPT_ID_EXTENSIONS, ".cts", ".mts", ".cjs", ".mjs"];
@@ -2413,11 +2809,26 @@ function matchesScriptExtension(id, extensions) {
2413
2809
  return extensions.some((ext) => id.endsWith(ext) || id.includes(`${ext}?`));
2414
2810
  }
2415
2811
  function insertRuntimeImport(code, importStmt) {
2416
- const directiveMatch = code.match(DIRECTIVE_PROLOGUE_PREFIX_RE);
2417
- if (!directiveMatch) {
2418
- return `${importStmt}${code}`;
2812
+ return insertAfterUseDirective(code, importStmt);
2813
+ }
2814
+ function scanClassExpression(source, from) {
2815
+ let depth = 0;
2816
+ let index = from;
2817
+ while (index < source.length) {
2818
+ const char = source[index];
2819
+ if (char === "(" || char === "[") {
2820
+ depth++;
2821
+ } else if (char === ")" || char === "]") {
2822
+ if (depth === 0) {
2823
+ break;
2824
+ }
2825
+ depth--;
2826
+ } else if (depth === 0 && (char === "," || char === ";" || char === "\n" || char === "}")) {
2827
+ break;
2828
+ }
2829
+ index++;
2419
2830
  }
2420
- return code.replace(directiveMatch[0], `${directiveMatch[1]}${directiveMatch[2]}${importStmt}`);
2831
+ return index;
2421
2832
  }
2422
2833
  function mangleCodeClassesSync(code, mangleMap) {
2423
2834
  function mangleClassString(classString) {
@@ -2425,6 +2836,58 @@ function mangleCodeClassesSync(code, mangleMap) {
2425
2836
  return mangleMap[cls.replace(/\\(.)/g, "$1")] || cls;
2426
2837
  }).join(" ");
2427
2838
  }
2839
+ function mangleClassTemplate(fullMatch, templateContent) {
2840
+ let changed = false;
2841
+ let output = "";
2842
+ let cursor = 0;
2843
+ while (cursor < templateContent.length) {
2844
+ const interpolationStart = templateContent.indexOf("${", cursor);
2845
+ const quasiEnd = interpolationStart === -1 ? templateContent.length : interpolationStart;
2846
+ const quasi = mangleTemplateQuasi(templateContent.slice(cursor, quasiEnd));
2847
+ output += quasi.value;
2848
+ changed ||= quasi.changed;
2849
+ if (interpolationStart === -1) break;
2850
+ const interpolationEnd = findTemplateInterpolationEnd(
2851
+ templateContent,
2852
+ interpolationStart + 2
2853
+ );
2854
+ const inner = templateContent.slice(interpolationStart + 2, interpolationEnd - 1);
2855
+ const interpolation = mangleTemplateInterpolation(inner);
2856
+ output += `\${${interpolation.value}}`;
2857
+ changed ||= interpolation.changed;
2858
+ cursor = interpolationEnd;
2859
+ }
2860
+ return changed ? `className:\`${output}\`` : fullMatch;
2861
+ }
2862
+ function mangleTemplateQuasi(quasi) {
2863
+ const trimmed = quasi.trim();
2864
+ if (!trimmed) return { value: quasi, changed: false };
2865
+ const mangled = mangleClassString(trimmed);
2866
+ return {
2867
+ value: mangled === trimmed ? quasi : quasi.replace(trimmed, mangled),
2868
+ changed: mangled !== trimmed
2869
+ };
2870
+ }
2871
+ function findTemplateInterpolationEnd(templateContent, bodyStart) {
2872
+ let depth = 0;
2873
+ for (let cursor = bodyStart; cursor < templateContent.length; cursor++) {
2874
+ if (templateContent[cursor] === "{") depth++;
2875
+ else if (templateContent[cursor] === "}" && depth-- === 0) return cursor + 1;
2876
+ }
2877
+ return templateContent.length;
2878
+ }
2879
+ function mangleTemplateInterpolation(inner) {
2880
+ let changed = false;
2881
+ const value = inner.replace(/"([^"]*)"/g, (quoted, classString) => {
2882
+ const parts = classString.split(/\s+/).filter(Boolean);
2883
+ if (parts.length === 0) return quoted;
2884
+ const mangled = parts.map((className) => mangleMap[className] || className).join(" ");
2885
+ if (mangled === classString) return quoted;
2886
+ changed = true;
2887
+ return `"${mangled}"`;
2888
+ });
2889
+ return { value, changed };
2890
+ }
2428
2891
  let result = code.replace(/(?:class(?:Name)?|sz)[:=]\s*"((?:[^"\\]|\\.)*)"/g, (match, classes) => {
2429
2892
  const mangled = mangleClassString(classes);
2430
2893
  if (mangled === classes) {
@@ -2438,92 +2901,7 @@ function mangleCodeClassesSync(code, mangleMap) {
2438
2901
  }
2439
2902
  return match.replace(classes, mangled);
2440
2903
  });
2441
- result = result.replace(/className:\s*`([^`]+)`/g, (fullMatch, tplContent) => {
2442
- let changed = false;
2443
- let out = "";
2444
- let i = 0;
2445
- while (i < tplContent.length) {
2446
- const interStart = tplContent.indexOf("${", i);
2447
- if (interStart === -1) {
2448
- const quasi2 = tplContent.slice(i);
2449
- const trimmed2 = quasi2.trim();
2450
- if (trimmed2) {
2451
- const m = mangleClassString(trimmed2);
2452
- if (m !== trimmed2) {
2453
- changed = true;
2454
- out += quasi2.replace(trimmed2, m);
2455
- } else {
2456
- out += quasi2;
2457
- }
2458
- } else {
2459
- out += quasi2;
2460
- }
2461
- break;
2462
- }
2463
- const quasi = tplContent.slice(i, interStart);
2464
- const trimmed = quasi.trim();
2465
- if (trimmed) {
2466
- const m = mangleClassString(trimmed);
2467
- if (m !== trimmed) {
2468
- changed = true;
2469
- out += quasi.replace(trimmed, m);
2470
- } else {
2471
- out += quasi;
2472
- }
2473
- } else {
2474
- out += quasi;
2475
- }
2476
- let j = interStart + 2;
2477
- let depth = 0;
2478
- while (j < tplContent.length) {
2479
- if (tplContent[j] === "{") {
2480
- depth++;
2481
- } else if (tplContent[j] === "}") {
2482
- if (depth === 0) {
2483
- j++;
2484
- break;
2485
- }
2486
- depth--;
2487
- }
2488
- j++;
2489
- }
2490
- const interInner = tplContent.slice(interStart + 2, j - 1);
2491
- const mangledInner = interInner.replace(/"([^"]*)"/g, (qm, inner) => {
2492
- const parts = inner.split(/\s+/).filter(Boolean);
2493
- if (parts.length === 0) {
2494
- return qm;
2495
- }
2496
- const m = parts.map((p) => mangleMap[p] || p).join(" ");
2497
- if (m === inner) {
2498
- return qm;
2499
- }
2500
- changed = true;
2501
- return `"${m}"`;
2502
- });
2503
- out += `\${${mangledInner}}`;
2504
- i = j;
2505
- }
2506
- return changed ? `className:\`${out}\`` : fullMatch;
2507
- });
2508
- function scanClassExpression(source, from) {
2509
- let depth = 0;
2510
- let j = from;
2511
- while (j < source.length) {
2512
- const ch = source[j];
2513
- if (ch === "(" || ch === "[") {
2514
- depth++;
2515
- } else if (ch === ")" || ch === "]") {
2516
- if (depth === 0) {
2517
- break;
2518
- }
2519
- depth--;
2520
- } else if (depth === 0 && (ch === "," || ch === ";" || ch === "\n" || ch === "}")) {
2521
- break;
2522
- }
2523
- j++;
2524
- }
2525
- return j;
2526
- }
2904
+ result = result.replace(/className:\s*`([^`]+)`/g, mangleClassTemplate);
2527
2905
  function mangleTernaryClassStrings(expr) {
2528
2906
  const qIdx = expr.indexOf("?");
2529
2907
  if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
@@ -2731,7 +3109,12 @@ function createCsszyxPlugins(options = {}) {
2731
3109
  }
2732
3110
  if (!_loggedActiveParsers.has(parserMode)) {
2733
3111
  _loggedActiveParsers.add(parserMode);
2734
- const detail = parserDegraded ? "oxc (degraded from default `rust`: no native binary for this platform)" : parserMode === "rust" ? "rust (native engine)" : parserMode;
3112
+ let detail = parserMode;
3113
+ if (parserDegraded) {
3114
+ detail = "oxc (degraded from default `rust`: no native binary for this platform)";
3115
+ } else if (parserMode === "rust") {
3116
+ detail = "rust (native engine)";
3117
+ }
2735
3118
  console.warn(`[csszyx] active parser: ${detail}`);
2736
3119
  }
2737
3120
  }
@@ -2752,6 +3135,7 @@ function createCsszyxPlugins(options = {}) {
2752
3135
  skipWarningEmitted: false,
2753
3136
  classesCapped: false,
2754
3137
  ownedClasses: /* @__PURE__ */ new Set(),
3138
+ authoredClasses: /* @__PURE__ */ new Set(),
2755
3139
  mangleMap: {},
2756
3140
  varMangleEntriesByFile: /* @__PURE__ */ new Map(),
2757
3141
  varMangleMap: Object.fromEntries(earlyGlobalVarAliasEntries),
@@ -2868,53 +3252,76 @@ function createCsszyxPlugins(options = {}) {
2868
3252
  }
2869
3253
  const cacheKey = cacheEnabled ? createTransformCacheKey(cacheInput) : null;
2870
3254
  if (cacheEnabled && cacheKey) {
2871
- const memoryCached = transformMemoryCache.get(cacheKey.key);
2872
- if (memoryCached) {
2873
- transformMemoryCache.delete(cacheKey.key);
2874
- transformMemoryCache.set(cacheKey.key, memoryCached);
2875
- return memoryCached;
2876
- }
2877
- const cached = readTransformCache(cacheRoot, cacheInput, cacheKey);
2878
- if (cached) {
2879
- rememberTransformCacheEntry(cacheKey.key, cached);
2880
- return cached;
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
- }
2891
- }
2892
- let result;
3255
+ const cached = findConfiguredTransformCacheEntry(
3256
+ cacheRoot,
3257
+ cacheInput,
3258
+ cacheKey,
3259
+ effectiveFilename,
3260
+ astBudget
3261
+ );
3262
+ if (cached) return cached;
3263
+ }
3264
+ const execution = runConfiguredParser(source, effectiveFilename, compilerOptions);
3265
+ if (cacheEnabled && cacheKey && execution.cacheable) {
3266
+ writeTransformCache(cacheRoot, cacheInput, execution.result, cacheKey);
3267
+ rememberTransformCacheEntry(cacheKey.key, execution.result);
3268
+ }
3269
+ return execution.result;
3270
+ }
3271
+ function findConfiguredTransformCacheEntry(cacheRoot, cacheInput, cacheKey, effectiveFilename, astBudget) {
3272
+ const memoryCached = transformMemoryCache.get(cacheKey.key);
3273
+ if (memoryCached) {
3274
+ transformMemoryCache.delete(cacheKey.key);
3275
+ transformMemoryCache.set(cacheKey.key, memoryCached);
3276
+ return memoryCached;
3277
+ }
3278
+ const diskCached = readTransformCache(cacheRoot, cacheInput, cacheKey);
3279
+ if (diskCached) {
3280
+ rememberTransformCacheEntry(cacheKey.key, diskCached);
3281
+ return diskCached;
3282
+ }
3283
+ if (astBudget !== void 0) return null;
3284
+ const handoff = prescanResultHandoff.get(effectiveFilename);
3285
+ if (!handoff) return null;
3286
+ prescanResultHandoff.delete(effectiveFilename);
3287
+ return handoff.inputSha256 === cacheKey.inputSha256 ? handoff.result : null;
3288
+ }
3289
+ function runConfiguredParser(source, effectiveFilename, compilerOptions) {
2893
3290
  if (parserMode === "babel") {
2894
- result = transformSourceCode(source, effectiveFilename, compilerOptions);
2895
- } else if (parserMode === "rust") {
2896
- result = transformRust(source, effectiveFilename, compilerOptions);
2897
- } else {
2898
- try {
2899
- result = transformOxc(source, effectiveFilename, compilerOptions);
2900
- } catch (err) {
2901
- result = transformSourceCode(source, effectiveFilename, compilerOptions);
2902
- const reason = err instanceof Error ? err.message : String(err);
2903
- result.diagnostics.push(
2904
- `[csszyx] oxc parser fell back to Babel for ${effectiveFilename}: ${reason}`
2905
- );
2906
- if (!_babelFallbackFiles.has(effectiveFilename)) {
2907
- _babelFallbackFiles.add(effectiveFilename);
2908
- console.warn(
2909
- `[csszyx] oxc parser fell back to Babel for ${effectiveFilename}: ${reason} (${_babelFallbackFiles.size} file(s) so far). Output is still correct; this usually means the file uses a syntax the oxc lane does not yet handle.`
2910
- );
2911
- }
2912
- return result;
2913
- }
3291
+ return {
3292
+ result: transformSourceCode(source, effectiveFilename, compilerOptions),
3293
+ cacheable: true
3294
+ };
2914
3295
  }
2915
- if (cacheEnabled && cacheKey) {
2916
- writeTransformCache(cacheRoot, cacheInput, result, cacheKey);
2917
- rememberTransformCacheEntry(cacheKey.key, result);
3296
+ if (parserMode === "rust") {
3297
+ return {
3298
+ result: transformRust(source, effectiveFilename, compilerOptions),
3299
+ cacheable: true
3300
+ };
3301
+ }
3302
+ try {
3303
+ return {
3304
+ result: transformOxc(source, effectiveFilename, compilerOptions),
3305
+ cacheable: true
3306
+ };
3307
+ } catch (error) {
3308
+ return {
3309
+ result: runBabelFallback(source, effectiveFilename, compilerOptions, error),
3310
+ cacheable: false
3311
+ };
3312
+ }
3313
+ }
3314
+ function runBabelFallback(source, effectiveFilename, compilerOptions, error) {
3315
+ const result = transformSourceCode(source, effectiveFilename, compilerOptions);
3316
+ const reason = error instanceof Error ? error.message : String(error);
3317
+ result.diagnostics.push(
3318
+ `[csszyx] oxc parser fell back to Babel for ${effectiveFilename}: ${reason}`
3319
+ );
3320
+ if (!_babelFallbackFiles.has(effectiveFilename)) {
3321
+ _babelFallbackFiles.add(effectiveFilename);
3322
+ console.warn(
3323
+ `[csszyx] oxc parser fell back to Babel for ${effectiveFilename}: ${reason} (${_babelFallbackFiles.size} file(s) so far). Output is still correct; this usually means the file uses a syntax the oxc lane does not yet handle.`
3324
+ );
2918
3325
  }
2919
3326
  return result;
2920
3327
  }
@@ -2953,11 +3360,19 @@ function createCsszyxPlugins(options = {}) {
2953
3360
  const compilerOptions = createCompilerOptions(prescanAstBudget);
2954
3361
  const cacheRoot = resolveTransformCacheDir(state.rootDir, options.build?.cacheDir);
2955
3362
  const results = /* @__PURE__ */ new Map();
2956
- const misses = [];
2957
- if (cacheEnabled) {
2958
- evictTransformCacheOnce();
2959
- }
3363
+ if (cacheEnabled) evictTransformCacheOnce();
2960
3364
  ensureRustTransformAvailable();
3365
+ const misses = collectRustPrescanMisses(files, compilerOptions, cacheRoot, results);
3366
+ if (misses.length === 0) return orderPrescanResults(files, results);
3367
+ try {
3368
+ runRustPrescanBatch(misses, compilerOptions, cacheRoot, results);
3369
+ } catch {
3370
+ runRustPrescanFallback(misses, results);
3371
+ }
3372
+ return orderPrescanResults(files, results);
3373
+ }
3374
+ function collectRustPrescanMisses(files, compilerOptions, cacheRoot, results) {
3375
+ const misses = [];
2961
3376
  for (const file of files) {
2962
3377
  const effectiveFilename = normalizeSourceFilename(file.filePath);
2963
3378
  const cacheInput = createConfiguredTransformCacheInput(
@@ -2966,79 +3381,54 @@ function createCsszyxPlugins(options = {}) {
2966
3381
  compilerOptions
2967
3382
  );
2968
3383
  const cacheKey = cacheEnabled ? createTransformCacheKey(cacheInput) : null;
2969
- if (cacheEnabled && cacheKey) {
2970
- const memoryCached = transformMemoryCache.get(cacheKey.key);
2971
- if (memoryCached) {
2972
- transformMemoryCache.delete(cacheKey.key);
2973
- transformMemoryCache.set(cacheKey.key, memoryCached);
2974
- results.set(file.filePath, memoryCached);
2975
- continue;
2976
- }
2977
- const cached = readTransformCache(cacheRoot, cacheInput, cacheKey);
2978
- if (cached) {
2979
- rememberTransformCacheEntry(cacheKey.key, cached);
2980
- results.set(file.filePath, cached);
2981
- continue;
2982
- }
2983
- }
2984
- misses.push({
2985
- filePath: file.filePath,
2986
- effectiveFilename,
2987
- content: file.content,
3384
+ const cached = cacheKey ? findConfiguredTransformCacheEntry(
3385
+ cacheRoot,
2988
3386
  cacheInput,
2989
- cacheKey
2990
- });
3387
+ cacheKey,
3388
+ effectiveFilename,
3389
+ prescanAstBudget
3390
+ ) : null;
3391
+ if (cached) results.set(file.filePath, cached);
3392
+ else misses.push({ ...file, effectiveFilename, cacheInput, cacheKey });
2991
3393
  }
2992
- if (misses.length === 0) {
2993
- return files.map((file) => {
2994
- const result = results.get(file.filePath);
2995
- return result ? { filePath: file.filePath, result } : null;
2996
- }).filter((entry) => entry !== null);
3394
+ return misses;
3395
+ }
3396
+ function runRustPrescanBatch(misses, compilerOptions, cacheRoot, results) {
3397
+ const batchResults = transformRustBatch(
3398
+ misses.map((file) => ({ filename: file.effectiveFilename, source: file.content })),
3399
+ compilerOptions
3400
+ );
3401
+ for (let index = 0; index < misses.length; index++) {
3402
+ const miss = misses[index];
3403
+ const result = batchResults[index];
3404
+ if (!miss || !result) continue;
3405
+ cacheRustPrescanResult(miss, result, cacheRoot);
3406
+ results.set(miss.filePath, result);
2997
3407
  }
2998
- try {
2999
- const batchResults = transformRustBatch(
3000
- misses.map((file) => ({
3001
- filename: file.effectiveFilename,
3002
- source: file.content
3003
- })),
3004
- compilerOptions
3005
- );
3006
- for (let index = 0; index < misses.length; index++) {
3007
- const miss = misses[index];
3008
- const result = batchResults[index];
3009
- if (!miss || !result) {
3010
- continue;
3011
- }
3012
- if (cacheEnabled && miss.cacheKey) {
3013
- writeTransformCache(cacheRoot, miss.cacheInput, result, miss.cacheKey);
3014
- rememberTransformCacheEntry(miss.cacheKey.key, result);
3015
- }
3408
+ }
3409
+ function cacheRustPrescanResult(miss, result, cacheRoot) {
3410
+ if (!cacheEnabled || !miss.cacheKey) return;
3411
+ writeTransformCache(cacheRoot, miss.cacheInput, result, miss.cacheKey);
3412
+ rememberTransformCacheEntry(miss.cacheKey.key, result);
3413
+ }
3414
+ function runRustPrescanFallback(misses, results) {
3415
+ for (const miss of misses) {
3416
+ try {
3417
+ const result = transformConfiguredSource(
3418
+ miss.content,
3419
+ miss.effectiveFilename,
3420
+ prescanAstBudget
3421
+ );
3016
3422
  results.set(miss.filePath, result);
3017
- }
3018
- } catch {
3019
- for (const miss of misses) {
3020
- try {
3021
- results.set(
3022
- miss.filePath,
3023
- transformConfiguredSource(
3024
- miss.content,
3025
- miss.effectiveFilename,
3026
- prescanAstBudget
3027
- )
3028
- );
3029
- } catch {
3030
- }
3423
+ } catch {
3031
3424
  }
3032
3425
  }
3033
- return files.map((file) => {
3034
- const result = results.get(file.filePath);
3035
- return result ? { filePath: file.filePath, result } : null;
3036
- }).filter((entry) => entry !== null);
3037
3426
  }
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
- );
3427
+ function orderPrescanResults(files, results) {
3428
+ return files.flatMap((file) => {
3429
+ const result = results.get(file.filePath);
3430
+ return result ? [{ filePath: file.filePath, result }] : [];
3431
+ });
3042
3432
  }
3043
3433
  function transformPrescanSourcesIndividually(files) {
3044
3434
  const results = [];
@@ -3105,11 +3495,12 @@ function createCsszyxPlugins(options = {}) {
3105
3495
  return;
3106
3496
  }
3107
3497
  const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
3108
- const classList = escapeHtmlAttribute(Array.from(classes).join(" "));
3498
+ const classNames = Array.from(classes);
3499
+ const classList = escapeHtmlAttribute(classNames.join(" "));
3109
3500
  const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
3110
3501
  <!-- Tailwind CSS scans this file for class name detection -->
3111
3502
  <div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
3112
- `;
3503
+ ` + renderTailwindScannerCandidates(classNames);
3113
3504
  try {
3114
3505
  let existing = "";
3115
3506
  try {
@@ -3139,12 +3530,56 @@ function createCsszyxPlugins(options = {}) {
3139
3530
  state.skippedSzFiles.add(filePath);
3140
3531
  }
3141
3532
  }
3533
+ function processPrescanTransform(filePath, content, result, discoveredClasses, rawDiscoveredClasses) {
3534
+ const budgetExceeded = result.diagnostics.some(
3535
+ (diagnostic) => diagnostic.includes("AST budget exceeded")
3536
+ );
3537
+ if (cacheEnabled && !budgetExceeded && content !== void 0) {
3538
+ prescanResultHandoff.set(normalizeSourceFilename(filePath), {
3539
+ inputSha256: createHash("sha256").update(content).digest("hex"),
3540
+ result
3541
+ });
3542
+ }
3543
+ if (budgetExceeded) {
3544
+ warnPrescanBudgetSkip(filePath);
3545
+ return;
3546
+ }
3547
+ const parseFailed = result.diagnostics.some(
3548
+ (diagnostic) => diagnostic.includes("[csszyx] parse error in ")
3549
+ );
3550
+ if (result.classes.size === 0 && result.rawClassNames.size === 0 && parseFailed) {
3551
+ console.warn(
3552
+ `[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).`
3553
+ );
3554
+ return;
3555
+ }
3556
+ if (!result.transformed && result.classes.size === 0) {
3557
+ return;
3558
+ }
3559
+ collectPrescanResult(result, filePath, discoveredClasses, rawDiscoveredClasses);
3560
+ }
3142
3561
  function prescanAndWriteClasses() {
3143
3562
  refreshCompileSourceDirs();
3144
3563
  const prescanStarted = performance.now();
3145
3564
  const discoveredClasses = /* @__PURE__ */ new Set();
3146
3565
  const rawDiscoveredClasses = /* @__PURE__ */ new Set();
3147
3566
  const prescanSources = [];
3567
+ function collectPrescanSource(filePath) {
3568
+ if (!shouldProcessSource(filePath)) {
3569
+ recordPackagesSkipIfSz(filePath);
3570
+ return;
3571
+ }
3572
+ let content;
3573
+ try {
3574
+ content = fs.readFileSync(filePath, "utf-8");
3575
+ } catch {
3576
+ return;
3577
+ }
3578
+ recordAuthoredClasses(content);
3579
+ if (fileMayContainSafelistableSz(content)) {
3580
+ prescanSources.push({ filePath, content });
3581
+ }
3582
+ }
3148
3583
  function scanDir(dir) {
3149
3584
  let entries;
3150
3585
  try {
@@ -3157,22 +3592,10 @@ function createCsszyxPlugins(options = {}) {
3157
3592
  if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
3158
3593
  scanDir(path.join(dir, entry.name));
3159
3594
  }
3160
- } else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
3161
- const filePath = path.join(dir, entry.name);
3162
- if (!shouldProcessSource(filePath)) {
3163
- recordPackagesSkipIfSz(filePath);
3164
- continue;
3165
- }
3166
- let content;
3167
- try {
3168
- content = fs.readFileSync(filePath, "utf-8");
3169
- } catch {
3170
- continue;
3171
- }
3172
- if (!fileMayContainSafelistableSz(content)) {
3173
- continue;
3174
- }
3175
- prescanSources.push({ filePath, content });
3595
+ continue;
3596
+ }
3597
+ if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
3598
+ collectPrescanSource(path.join(dir, entry.name));
3176
3599
  }
3177
3600
  }
3178
3601
  }
@@ -3188,29 +3611,13 @@ function createCsszyxPlugins(options = {}) {
3188
3611
  prescanSources.map((file) => [file.filePath, file.content])
3189
3612
  );
3190
3613
  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
- }
3204
- if (result.classes.size === 0 && result.rawClassNames.size === 0 && result.diagnostics.some((d) => d.includes("[csszyx] parse error in "))) {
3205
- console.warn(
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).`
3207
- );
3208
- continue;
3209
- }
3210
- if (!result.transformed && result.classes.size === 0) {
3211
- continue;
3212
- }
3213
- collectPrescanResult(result, filePath, discoveredClasses, rawDiscoveredClasses);
3614
+ processPrescanTransform(
3615
+ filePath,
3616
+ prescanContentByPath.get(filePath),
3617
+ result,
3618
+ discoveredClasses,
3619
+ rawDiscoveredClasses
3620
+ );
3214
3621
  }
3215
3622
  for (const cls of discoveredClasses) {
3216
3623
  addSafelistClass(cls);
@@ -3271,7 +3678,7 @@ function createCsszyxPlugins(options = {}) {
3271
3678
  for (const kv of objStr.matchAll(numKv)) {
3272
3679
  try {
3273
3680
  collectTransformClasses(
3274
- transform({ [kv[1]]: parseFloat(kv[2]) }),
3681
+ transform({ [kv[1]]: Number.parseFloat(kv[2]) }),
3275
3682
  discoveredClasses
3276
3683
  );
3277
3684
  } catch {
@@ -3295,51 +3702,51 @@ function createCsszyxPlugins(options = {}) {
3295
3702
  discoveredClasses.add(cls);
3296
3703
  }
3297
3704
  }
3298
- function extractClasses(code) {
3299
- const dqPattern = /(?:class(?:Name)?|sz)[:=]\s*"([^"]*)"/g;
3300
- const sqPattern = /(?:class(?:Name)?|sz)[:=]\s*'([^']*)'/g;
3301
- for (const classPattern of [dqPattern, sqPattern]) {
3302
- for (const match of code.matchAll(classPattern)) {
3303
- const classes = match[1].split(/\s+/).filter(Boolean);
3304
- for (const cls of classes) {
3305
- addSafelistClass(cls);
3306
- }
3307
- }
3705
+ function addSafelistClasses(value) {
3706
+ for (const className of value.split(/\s+/).filter(Boolean)) {
3707
+ addSafelistClass(className);
3308
3708
  }
3309
- const exprStart = /className=\{/g;
3310
- for (const match of code.matchAll(exprStart)) {
3311
- let depth = 1;
3312
- let i = (match.index ?? 0) + match[0].length;
3313
- while (i < code.length && depth > 0) {
3314
- if (code[i] === "{") {
3315
- depth++;
3316
- } else if (code[i] === "}") {
3317
- depth--;
3318
- }
3319
- i++;
3709
+ }
3710
+ function collectQuotedAttributeClasses(code) {
3711
+ const patterns = [
3712
+ /(?:class(?:Name)?|sz)[:=]\s*"([^"]*)"/g,
3713
+ /(?:class(?:Name)?|sz)[:=]\s*'([^']*)'/g
3714
+ ];
3715
+ for (const pattern of patterns) {
3716
+ for (const match of code.matchAll(pattern)) {
3717
+ addSafelistClasses(match[1] ?? "");
3320
3718
  }
3321
- const expr = code.slice((match.index ?? 0) + match[0].length, i - 1);
3322
- const strPattern = /"([^"]+)"|'([^']+)'/g;
3323
- for (const strMatch of expr.matchAll(strPattern)) {
3324
- const str = strMatch[1] || strMatch[2];
3325
- const classes = str.split(/\s+/).filter(Boolean);
3326
- for (const cls of classes) {
3327
- addSafelistClass(cls);
3328
- }
3719
+ }
3720
+ }
3721
+ function collectExpressionClasses(code) {
3722
+ for (const match of code.matchAll(/className=\{/g)) {
3723
+ const bodyStart = (match.index ?? 0) + match[0].length;
3724
+ const expression = code.slice(bodyStart, findJsxExpressionEnd(code, bodyStart));
3725
+ for (const stringMatch of expression.matchAll(/"([^"]+)"|'([^']+)'/g)) {
3726
+ addSafelistClasses(stringMatch[1] ?? stringMatch[2] ?? "");
3329
3727
  }
3330
3728
  }
3331
3729
  }
3730
+ function extractClasses(code) {
3731
+ collectQuotedAttributeClasses(code);
3732
+ collectExpressionClasses(code);
3733
+ }
3734
+ function recordAuthoredClasses(code) {
3735
+ for (const className of collectAuthoredClassNames(code)) {
3736
+ addSafelistClass(className);
3737
+ state.authoredClasses.add(className);
3738
+ }
3739
+ }
3332
3740
  function finalizeMangleMap() {
3333
- const sortedClasses = Array.from(state.ownedClasses);
3334
3741
  const newMap = {};
3335
3742
  let tokenIndex = 0;
3336
- for (let i = 0; i < sortedClasses.length; i++) {
3743
+ for (const className of mangleEligibleClasses(state.ownedClasses, state.authoredClasses)) {
3337
3744
  let token = encode(tokenIndex);
3338
- while (mangleReserved.has(token)) {
3745
+ while (mangleReserved.has(token) || state.authoredClasses.has(token)) {
3339
3746
  tokenIndex++;
3340
3747
  token = encode(tokenIndex);
3341
3748
  }
3342
- newMap[sortedClasses[i]] = token;
3749
+ newMap[className] = token;
3343
3750
  tokenIndex++;
3344
3751
  }
3345
3752
  state.mangleMap = newMap;
@@ -3352,6 +3759,24 @@ function createCsszyxPlugins(options = {}) {
3352
3759
  function mangleCodeClasses(code) {
3353
3760
  return mangleCodeClassesSync(code, state.mangleMap);
3354
3761
  }
3762
+ function mangleHtmlClassList(classes) {
3763
+ const output = [];
3764
+ for (const className of classes.split(/\s+/)) {
3765
+ if (className) output.push(state.mangleMap[className] || className);
3766
+ }
3767
+ return output.join(" ");
3768
+ }
3769
+ function replaceDoubleQuotedHtmlClass(match, classes) {
3770
+ const output = mangleHtmlClassList(classes);
3771
+ return output !== classes ? `class="${output}"` : match;
3772
+ }
3773
+ function replaceSingleQuotedHtmlClass(match, classes) {
3774
+ const output = mangleHtmlClassList(classes);
3775
+ return output !== classes ? `class='${output}'` : match;
3776
+ }
3777
+ function mangleHtmlClasses(source) {
3778
+ return source.replace(/\bclass="([^"]*)"/g, replaceDoubleQuotedHtmlClass).replace(/\bclass='([^']*)'/g, replaceSingleQuotedHtmlClass);
3779
+ }
3355
3780
  function replacePlaceholders(code) {
3356
3781
  let result = code;
3357
3782
  if (result.includes(CHECKSUM_PLACEHOLDER)) {
@@ -3369,6 +3794,147 @@ function createCsszyxPlugins(options = {}) {
3369
3794
  }
3370
3795
  return result;
3371
3796
  }
3797
+ function unchangedPreTransform(code) {
3798
+ return {
3799
+ code,
3800
+ transformed: false,
3801
+ usesRuntime: false,
3802
+ usesMerge: false,
3803
+ usesSzcn: false,
3804
+ usesSzPart: false,
3805
+ usesColorVar: false,
3806
+ usesSpacingVar: false,
3807
+ usesUnitVar: false
3808
+ };
3809
+ }
3810
+ function transformTailwindCssEntry(code, id) {
3811
+ state.sawAnyCss = true;
3812
+ if (!cssImportsTailwind(code)) return null;
3813
+ state.sawTailwindEntry = true;
3814
+ if (cssHasContentScope(code)) state.tailwindEntryScoped = true;
3815
+ if (!hasInjectableTailwindCandidate(state.classes)) return null;
3816
+ const relPath = computeSafelistRelPath(state.rootDir, SAFELIST_FILENAME, id);
3817
+ const transformed = appendTailwindSourceDirective(code, relPath);
3818
+ return transformed === null ? null : { code: transformed, map: null };
3819
+ }
3820
+ function reportTransformDiagnostics(result, id, warn) {
3821
+ for (const message of result.diagnostics) {
3822
+ if (message.includes("unresolvable sz spread")) {
3823
+ state.spreadWarnings.add(`${id}
3824
+ ${message}`);
3825
+ } else if (message.includes("AST budget exceeded")) {
3826
+ console.warn(`[csszyx] ${id}
3827
+ ${message}`);
3828
+ }
3829
+ }
3830
+ if (quiet || result.diagnostics.length === 0 || process.env.NODE_ENV === "production") {
3831
+ return;
3832
+ }
3833
+ for (const message of result.diagnostics) {
3834
+ if (message.includes("unresolvable sz spread") || message.includes("AST budget exceeded")) {
3835
+ continue;
3836
+ }
3837
+ warn(`[csszyx] ${id}
3838
+ ${message}`);
3839
+ }
3840
+ }
3841
+ function compilerPreTransformOutput(result) {
3842
+ return {
3843
+ code: result.code,
3844
+ transformed: result.transformed,
3845
+ usesRuntime: result.usesRuntime,
3846
+ usesMerge: result.usesMerge,
3847
+ usesSzcn: result.usesSzcn,
3848
+ usesSzPart: result.usesSzPart,
3849
+ usesColorVar: result.usesColorVar,
3850
+ usesSpacingVar: result.usesSpacingVar,
3851
+ usesUnitVar: result.usesUnitVar,
3852
+ szClasses: result.classes
3853
+ };
3854
+ }
3855
+ function transformSzSource(code, id, warn) {
3856
+ if (id.endsWith(".vue")) {
3857
+ const result2 = preprocess(code, options);
3858
+ return { ...unchangedPreTransform(result2.code), transformed: result2.transformed };
3859
+ }
3860
+ if (id.endsWith(".svelte")) {
3861
+ const result2 = preprocess$1(code, options);
3862
+ return result2 ? { ...unchangedPreTransform(result2.code), transformed: true } : unchangedPreTransform(code);
3863
+ }
3864
+ const transformStarted = performance.now();
3865
+ const result = transformConfiguredSource(code, id);
3866
+ traceBenchTiming("transform-hook", id, performance.now() - transformStarted);
3867
+ recordFileVarMangleEntries(state, id, cssVariableEntries(result));
3868
+ recordFileCSSVariableMetrics(state, id, result.code);
3869
+ reportTransformDiagnostics(result, id, warn);
3870
+ for (const [token, data] of result.recoveryTokens) state.recoveryTokens.set(token, data);
3871
+ return compilerPreTransformOutput(result);
3872
+ }
3873
+ function injectLayoutHydration(code, id) {
3874
+ if (!code.includes("<html") || !/(?:layout|Root|Document|app)\.tsx?$/i.test(id)) {
3875
+ return null;
3876
+ }
3877
+ let transformedCode = code;
3878
+ const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
3879
+ const htmlTag = findOpeningTag(transformedCode, "html");
3880
+ if (htmlTag) {
3881
+ transformedCode = `${transformedCode.slice(0, htmlTag.close)} ${attrName}="${CHECKSUM_PLACEHOLDER}"${transformedCode.slice(htmlTag.close)}`;
3882
+ }
3883
+ 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})}}})()\`}} />`;
3884
+ const bodyTag = findOpeningTag(transformedCode, "body");
3885
+ if (bodyTag) {
3886
+ transformedCode = `${transformedCode.slice(0, bodyTag.close + 1)}${debugScript}${transformedCode.slice(bodyTag.close + 1)}`;
3887
+ }
3888
+ return transformedCode;
3889
+ }
3890
+ function requiredRuntimeHelpers(output) {
3891
+ const helpers = [];
3892
+ if (output.usesRuntime) helpers.push("_sz");
3893
+ if (output.usesMerge) helpers.push("_szMerge");
3894
+ if (output.usesSzcn) helpers.push("_szcn");
3895
+ if (output.usesSzPart) helpers.push("_szPart");
3896
+ if (output.usesColorVar) helpers.push("__szColorVar");
3897
+ if (output.usesSpacingVar) helpers.push("__szSpacingVar");
3898
+ if (output.usesUnitVar) helpers.push("__szUnitVar");
3899
+ return helpers;
3900
+ }
3901
+ function injectRuntimeHelpers(code, output) {
3902
+ const imports = requiredRuntimeHelpers(output);
3903
+ const hasRuntimeImport = imports.length > 0 && code.includes("@csszyx/runtime");
3904
+ const needed = hasRuntimeImport ? imports.filter((name) => !importsRuntimeHelper(code, name)) : imports;
3905
+ if (needed.length === 0) return null;
3906
+ const existingImport = findRuntimeImportClause(code);
3907
+ if (existingImport) {
3908
+ return code.replace(
3909
+ existingImport.statement,
3910
+ `${existingImport.prefixWithBody}, ${needed.join(", ")} } from '@csszyx/runtime'`
3911
+ );
3912
+ }
3913
+ const importStatement = `import { ${needed.join(", ")} } from '@csszyx/runtime';
3914
+ `;
3915
+ return insertRuntimeImport(code, importStatement);
3916
+ }
3917
+ function injectThemeGroups(code, transformedCode, id, usesSzcn) {
3918
+ if (!usesSzcn && !/\bszcn\s*\(/.test(code) || transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) || !shouldProcessSource(id)) {
3919
+ return null;
3920
+ }
3921
+ return `import '${THEME_GROUPS_VIRTUAL_ID}';
3922
+ ${transformedCode}`;
3923
+ }
3924
+ function collectPreTransformClasses(output) {
3925
+ if (!output.transformed && !output.code.includes("class=") && !output.code.includes("className=")) {
3926
+ return null;
3927
+ }
3928
+ if (output.szClasses) {
3929
+ for (const className of output.szClasses) {
3930
+ addSafelistClass(className);
3931
+ state.ownedClasses.add(className);
3932
+ }
3933
+ } else {
3934
+ extractClasses(output.code);
3935
+ }
3936
+ return { code: output.code, map: null };
3937
+ }
3372
3938
  const prePlugin = createUnplugin(
3373
3939
  (_pluginOptions) => ({
3374
3940
  name: "csszyx:pre",
@@ -3455,166 +4021,41 @@ function createCsszyxPlugins(options = {}) {
3455
4021
  }
3456
4022
  if (shouldProcessSource(id)) {
3457
4023
  trackGlobalVarSourceFile(id, code);
4024
+ recordAuthoredClasses(code);
3458
4025
  }
3459
4026
  if (matchesScriptExtension(id, SCRIPT_ID_EXTENSIONS)) {
3460
4027
  assertNoRSCBoundaryViolation(code, id);
3461
4028
  }
3462
4029
  if (matchesScriptExtension(id, [".css"])) {
3463
- state.sawAnyCss = true;
3464
- if (cssImportsTailwind(code)) {
3465
- state.sawTailwindEntry = true;
3466
- if (cssHasContentScope(code)) {
3467
- state.tailwindEntryScoped = true;
3468
- }
3469
- if (hasInjectableTailwindCandidate(state.classes)) {
3470
- const relPath = computeSafelistRelPath(
3471
- state.rootDir,
3472
- SAFELIST_FILENAME,
3473
- id
3474
- );
3475
- const transformed2 = appendTailwindSourceDirective(code, relPath);
3476
- if (transformed2 !== null) {
3477
- return { code: transformed2, map: null };
3478
- }
3479
- }
3480
- }
3481
- return null;
4030
+ return transformTailwindCssEntry(code, id);
3482
4031
  }
3483
- let transformedCode = code;
3484
- let usesRuntime = false;
3485
- let usesMerge = false;
3486
- let usesSzcn = false;
3487
- let usesSzPart = false;
3488
- let usesColorVar = false;
3489
- let transformed = false;
3490
- let szClasses;
3491
4032
  const hasSzProp = code.includes("sz=") || code.includes("szs=") || /\bsz\s*:\s*["'{]/.test(code) || code.includes('sz: "');
3492
- if (hasSzProp) {
3493
- if (id.endsWith(".vue")) {
3494
- const result = preprocess(code, options);
3495
- if (result.transformed) {
3496
- transformedCode = result.code;
3497
- transformed = true;
3498
- }
3499
- } else if (id.endsWith(".svelte")) {
3500
- const result = preprocess$1(code, options);
3501
- if (result) {
3502
- transformedCode = result.code;
3503
- transformed = true;
3504
- }
3505
- } else {
3506
- const transformStarted = performance.now();
3507
- const result = transformConfiguredSource(code, id);
3508
- traceBenchTiming(
3509
- "transform-hook",
3510
- id,
3511
- performance.now() - transformStarted
3512
- );
3513
- transformedCode = result.code;
3514
- usesRuntime = result.usesRuntime;
3515
- usesMerge = result.usesMerge;
3516
- usesSzcn = result.usesSzcn;
3517
- usesSzPart = result.usesSzPart;
3518
- usesColorVar = result.usesColorVar;
3519
- transformed = result.transformed;
3520
- szClasses = result.classes;
3521
- recordFileVarMangleEntries(state, id, cssVariableEntries(result));
3522
- recordFileCSSVariableMetrics(state, id, result.code);
3523
- for (const msg of result.diagnostics) {
3524
- if (msg.includes("unresolvable sz spread")) {
3525
- state.spreadWarnings.add(`${id}
3526
- ${msg}`);
3527
- } else if (msg.includes("AST budget exceeded")) {
3528
- console.warn(`[csszyx] ${id}
3529
- ${msg}`);
3530
- }
3531
- }
3532
- if (!quiet && result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
3533
- for (const msg of result.diagnostics) {
3534
- if (msg.includes("unresolvable sz spread") || msg.includes("AST budget exceeded")) {
3535
- continue;
3536
- }
3537
- this.warn(`[csszyx] ${id}
3538
- ${msg}`);
3539
- }
3540
- }
3541
- for (const [token, data] of result.recoveryTokens) {
3542
- state.recoveryTokens.set(token, data);
3543
- }
3544
- }
3545
- } else if (shouldProcessSource(id)) {
4033
+ const output = hasSzProp ? transformSzSource(code, id, (message) => this.warn(message)) : unchangedPreTransform(code);
4034
+ if (!hasSzProp && shouldProcessSource(id)) {
3546
4035
  recordFileVarMangleEntries(state, id, []);
3547
4036
  recordFileCSSVariableMetrics(state, id, null);
3548
4037
  }
3549
- if (transformedCode.includes("<html") && /(?:layout|Root|Document|app)\.tsx?$/i.test(id)) {
3550
- const attrName = options.production?.minify ? "data-sz-cs" : "data-sz-checksum";
3551
- const htmlTag = findOpeningTag(transformedCode, "html");
3552
- if (htmlTag) {
3553
- transformedCode = `${transformedCode.slice(0, htmlTag.close)} ${attrName}="${CHECKSUM_PLACEHOLDER}"${transformedCode.slice(htmlTag.close)}`;
3554
- }
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})}}})()\`}} />`;
3556
- const bodyTag = findOpeningTag(transformedCode, "body");
3557
- if (bodyTag) {
3558
- transformedCode = `${transformedCode.slice(0, bodyTag.close + 1)}${debugScript}${transformedCode.slice(bodyTag.close + 1)}`;
3559
- }
3560
- transformed = true;
4038
+ const layoutCode = injectLayoutHydration(output.code, id);
4039
+ if (layoutCode !== null) {
4040
+ output.code = layoutCode;
4041
+ output.transformed = true;
3561
4042
  }
3562
- {
3563
- const imports = [];
3564
- if (usesRuntime) {
3565
- imports.push("_sz");
3566
- }
3567
- if (usesMerge) {
3568
- imports.push("_szMerge");
3569
- }
3570
- if (usesSzcn) {
3571
- imports.push("_szcn");
3572
- }
3573
- if (usesSzPart) {
3574
- imports.push("_szPart");
3575
- }
3576
- if (usesColorVar) {
3577
- imports.push("__szColorVar");
3578
- }
3579
- const hasRuntimeImport = imports.length > 0 && transformedCode.includes("@csszyx/runtime");
3580
- const needed = hasRuntimeImport ? imports.filter((name) => !importsRuntimeHelper(transformedCode, name)) : imports;
3581
- if (needed.length > 0) {
3582
- const existingImport = findRuntimeImportClause(transformedCode);
3583
- if (existingImport) {
3584
- transformedCode = transformedCode.replace(
3585
- existingImport.statement,
3586
- `${existingImport.prefixWithBody}, ${needed.join(", ")} } from '@csszyx/runtime'`
3587
- );
3588
- } else {
3589
- const importStmt = `import { ${needed.join(", ")} } from '@csszyx/runtime';
3590
- `;
3591
- transformedCode = insertRuntimeImport(transformedCode, importStmt);
3592
- }
3593
- transformed = true;
3594
- }
4043
+ const runtimeCode = injectRuntimeHelpers(output.code, output);
4044
+ if (runtimeCode !== null) {
4045
+ output.code = runtimeCode;
4046
+ output.transformed = true;
3595
4047
  }
3596
- if ((usesSzcn || /\bszcn\s*\(/.test(code)) && !transformedCode.includes(THEME_GROUPS_VIRTUAL_ID) && shouldProcessSource(id)) {
3597
- transformedCode = `import '${THEME_GROUPS_VIRTUAL_ID}';
3598
- ${transformedCode}`;
3599
- transformed = true;
4048
+ const themedCode = injectThemeGroups(code, output.code, id, output.usesSzcn);
4049
+ if (themedCode !== null) {
4050
+ output.code = themedCode;
4051
+ output.transformed = true;
3600
4052
  }
3601
4053
  if (matchesScriptExtension(id, SCRIPT_ID_EXTENSIONS)) {
3602
- assertNoRSCBoundaryViolation(transformedCode, id);
3603
- const record = createRSCModuleRecord(transformedCode, id);
4054
+ assertNoRSCBoundaryViolation(output.code, id);
4055
+ const record = createRSCModuleRecord(output.code, id);
3604
4056
  state.rscModules.set(record.id, record);
3605
4057
  }
3606
- if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
3607
- if (szClasses !== void 0) {
3608
- for (const cls of szClasses) {
3609
- addSafelistClass(cls);
3610
- state.ownedClasses.add(cls);
3611
- }
3612
- } else {
3613
- extractClasses(transformedCode);
3614
- }
3615
- return { code: transformedCode, map: null };
3616
- }
3617
- return null;
4058
+ return collectPreTransformClasses(output);
3618
4059
  },
3619
4060
  /** Finalizes the mangle map after all source modules have been processed. */
3620
4061
  buildEnd() {
@@ -3823,6 +4264,166 @@ ${transformedCode}`;
3823
4264
  }
3824
4265
  })
3825
4266
  );
4267
+ function createBundleManifest(includeMangleMap) {
4268
+ const manifest = {
4269
+ version: "0.4.0",
4270
+ buildId: state.checksum,
4271
+ classes: Object.keys(state.mangleMap)
4272
+ };
4273
+ if (includeMangleMap && Object.keys(state.mangleMap).length > 0) {
4274
+ manifest.mangleMap = state.mangleMap;
4275
+ }
4276
+ if (Object.keys(state.varMangleMap).length > 0) {
4277
+ manifest.varMangleMap = state.varMangleMap;
4278
+ }
4279
+ const globalVarAliases = extractGlobalVarAliasesForManifest(
4280
+ state.varMangleMap,
4281
+ globalVarAliasPrefix,
4282
+ state.globalVarValidationResult
4283
+ );
4284
+ if (Object.keys(globalVarAliases).length > 0) {
4285
+ manifest.globalVarAliases = globalVarAliases;
4286
+ }
4287
+ if (hasCSSVariableMetrics(state.cssVarMetrics)) {
4288
+ manifest.cssVarMetrics = state.cssVarMetrics;
4289
+ }
4290
+ return manifest;
4291
+ }
4292
+ function isCssSyntaxError(error) {
4293
+ return !!error && typeof error === "object" && "name" in error && error.name === "CssSyntaxError";
4294
+ }
4295
+ function rewriteOutputCss(source, file, shouldMangle, mangledSources, externalClasses) {
4296
+ let css = rewriteCssWithValidatedGlobalVarPlan(
4297
+ source,
4298
+ file,
4299
+ state.globalVarValidationResult
4300
+ );
4301
+ if (!shouldMangle) return css;
4302
+ try {
4303
+ const result = mangleCSSSync(css, state.mangleMap, {
4304
+ debug: options.development?.debug,
4305
+ from: file
4306
+ });
4307
+ for (const className of result.mangledClasses) mangledSources.add(className);
4308
+ for (const className of result.unmangledClasses) externalClasses.add(className);
4309
+ if (result.transformedCount > 0) css = result.css;
4310
+ return css;
4311
+ } catch (error) {
4312
+ if (isCssSyntaxError(error)) return css;
4313
+ throw error;
4314
+ }
4315
+ }
4316
+ function reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses) {
4317
+ if (!shouldMangle) return;
4318
+ const message = mangleHybridHazardMessage(
4319
+ collectMangleHybridHazards(state.mangleMap, mangledSources, externalClasses)
4320
+ );
4321
+ if (message) console.warn(message);
4322
+ }
4323
+ function rewriteWebpackCodeAsset(file, source, shouldMangle, compilation, compiler) {
4324
+ if (shouldMangle && file.endsWith(".html")) {
4325
+ const mangledHtml = mangleHtmlClasses(source);
4326
+ if (mangledHtml !== source) {
4327
+ compilation.updateAsset(file, new compiler.webpack.sources.RawSource(mangledHtml));
4328
+ }
4329
+ return;
4330
+ }
4331
+ if (!file.endsWith(".js")) return;
4332
+ const rewritten = shouldMangle ? replacePlaceholders(mangleCodeClasses(source)) : replacePlaceholders(source);
4333
+ if (rewritten !== source) {
4334
+ compilation.updateAsset(file, new compiler.webpack.sources.RawSource(rewritten));
4335
+ }
4336
+ }
4337
+ function processWebpackAssets(assets, compilation, compiler) {
4338
+ finalizeMangleMap();
4339
+ state.globalVarValidationResult = validateGlobalVarBundleInputs(
4340
+ collectWebpackGlobalVarCssAssets(assets)
4341
+ );
4342
+ const shouldMangle = manglingEnabled && compiler.options.mode !== "development" && Object.keys(state.mangleMap).length > 0;
4343
+ const manifest = createBundleManifest(shouldMangle);
4344
+ compilation.emitAsset(
4345
+ "csszyx-manifest.json",
4346
+ new compiler.webpack.sources.RawSource(JSON.stringify(manifest))
4347
+ );
4348
+ if (shouldEmitGlobalVarMapAsset(globalVarMangleConfig)) {
4349
+ const globalVarMap = createGlobalVarMapAssetSource(
4350
+ state.varMangleMap,
4351
+ globalVarAliasPrefix,
4352
+ state.globalVarValidationResult
4353
+ );
4354
+ if (globalVarMap) {
4355
+ compilation.emitAsset(
4356
+ ".csszyx/global-var-map.json",
4357
+ new compiler.webpack.sources.RawSource(globalVarMap)
4358
+ );
4359
+ }
4360
+ }
4361
+ const mangledSources = /* @__PURE__ */ new Set();
4362
+ const externalClasses = /* @__PURE__ */ new Set();
4363
+ for (const file in assets) {
4364
+ const source = assets[file].source().toString();
4365
+ if (file.endsWith(".css")) {
4366
+ const css = rewriteOutputCss(
4367
+ source,
4368
+ file,
4369
+ shouldMangle,
4370
+ mangledSources,
4371
+ externalClasses
4372
+ );
4373
+ if (css !== source) {
4374
+ compilation.updateAsset(file, new compiler.webpack.sources.RawSource(css));
4375
+ }
4376
+ } else {
4377
+ rewriteWebpackCodeAsset(file, source, shouldMangle, compilation, compiler);
4378
+ }
4379
+ }
4380
+ reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
4381
+ }
4382
+ function rewriteViteBundleEntry(chunk, file, shouldMangle, mangledSources, externalClasses) {
4383
+ if (chunk.type === "asset" && chunk.fileName.endsWith(".css") && chunk.source !== void 0) {
4384
+ const originalCss = chunk.source.toString();
4385
+ const css = rewriteOutputCss(
4386
+ originalCss,
4387
+ file,
4388
+ shouldMangle,
4389
+ mangledSources,
4390
+ externalClasses
4391
+ );
4392
+ if (css !== originalCss) chunk.source = css;
4393
+ return;
4394
+ }
4395
+ if (chunk.type !== "chunk" || chunk.code === void 0) return;
4396
+ const rewritten = shouldMangle ? replacePlaceholders(mangleCodeClasses(chunk.code)) : replacePlaceholders(chunk.code);
4397
+ if (rewritten !== chunk.code) chunk.code = rewritten;
4398
+ }
4399
+ function processRollupBundle(bundle, emitAsset) {
4400
+ finalizeMangleMap();
4401
+ state.globalVarValidationResult = validateGlobalVarBundleInputs(
4402
+ collectRollupGlobalVarCssAssets(bundle)
4403
+ );
4404
+ const shouldMangle = manglingEnabled && Object.keys(state.mangleMap).length > 0;
4405
+ emitAsset("csszyx-manifest.json", JSON.stringify(createBundleManifest(shouldMangle)));
4406
+ if (shouldEmitGlobalVarMapAsset(globalVarMangleConfig)) {
4407
+ const globalVarMap = createGlobalVarMapAssetSource(
4408
+ state.varMangleMap,
4409
+ globalVarAliasPrefix,
4410
+ state.globalVarValidationResult
4411
+ );
4412
+ if (globalVarMap) emitAsset(".csszyx/global-var-map.json", globalVarMap);
4413
+ }
4414
+ const mangledSources = /* @__PURE__ */ new Set();
4415
+ const externalClasses = /* @__PURE__ */ new Set();
4416
+ for (const file in bundle) {
4417
+ rewriteViteBundleEntry(
4418
+ bundle[file],
4419
+ file,
4420
+ shouldMangle,
4421
+ mangledSources,
4422
+ externalClasses
4423
+ );
4424
+ }
4425
+ reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
4426
+ }
3826
4427
  const postPlugin = createUnplugin(() => ({
3827
4428
  name: "csszyx:post",
3828
4429
  enforce: "post",
@@ -3833,278 +4434,20 @@ ${transformedCode}`;
3833
4434
  * @param compiler - the Webpack compiler instance
3834
4435
  */
3835
4436
  webpack(compiler) {
3836
- compiler.hooks.compilation.tap("csszyx:post", (compilation) => {
3837
- const stage = compiler.webpack?.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE || compilation.constructor.PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE;
3838
- compilation.hooks.processAssets.tap(
3839
- {
3840
- name: "csszyx:post",
3841
- stage: stage || 400
3842
- // Fallback integer
3843
- },
3844
- (assets) => {
3845
- finalizeMangleMap();
3846
- state.globalVarValidationResult = validateGlobalVarBundleInputs(
3847
- collectWebpackGlobalVarCssAssets(assets)
3848
- );
3849
- const isWebpackDevMode = compiler.options.mode === "development";
3850
- const manifestData = {
3851
- version: "0.4.0",
3852
- buildId: state.checksum,
3853
- classes: Object.keys(state.mangleMap)
3854
- };
3855
- if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
3856
- manifestData.mangleMap = state.mangleMap;
3857
- }
3858
- if (Object.keys(state.varMangleMap).length > 0) {
3859
- manifestData.varMangleMap = state.varMangleMap;
3860
- }
3861
- const globalVarAliases = extractGlobalVarAliasesForManifest(
3862
- state.varMangleMap,
3863
- globalVarAliasPrefix,
3864
- state.globalVarValidationResult
3865
- );
3866
- if (Object.keys(globalVarAliases).length > 0) {
3867
- manifestData.globalVarAliases = globalVarAliases;
3868
- }
3869
- if (hasCSSVariableMetrics(state.cssVarMetrics)) {
3870
- manifestData.cssVarMetrics = state.cssVarMetrics;
3871
- }
3872
- compilation.emitAsset(
3873
- "csszyx-manifest.json",
3874
- new compiler.webpack.sources.RawSource(JSON.stringify(manifestData))
3875
- );
3876
- if (shouldEmitGlobalVarMapAsset(globalVarMangleConfig)) {
3877
- const globalVarMapAsset = createGlobalVarMapAssetSource(
3878
- state.varMangleMap,
3879
- globalVarAliasPrefix,
3880
- state.globalVarValidationResult
3881
- );
3882
- if (globalVarMapAsset) {
3883
- compilation.emitAsset(
3884
- ".csszyx/global-var-map.json",
3885
- new compiler.webpack.sources.RawSource(globalVarMapAsset)
3886
- );
3887
- }
3888
- }
3889
- const mangledSources = /* @__PURE__ */ new Set();
3890
- const externalClasses = /* @__PURE__ */ new Set();
3891
- for (const file in assets) {
3892
- const asset = assets[file];
3893
- const source = asset.source().toString();
3894
- if (file.endsWith(".css")) {
3895
- let css = rewriteCssWithValidatedGlobalVarPlan(
3896
- source,
3897
- file,
3898
- state.globalVarValidationResult
3899
- );
3900
- if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
3901
- try {
3902
- const result = mangleCSSSync(css, state.mangleMap, {
3903
- debug: options.development?.debug,
3904
- from: file
3905
- });
3906
- for (const c of result.mangledClasses) {
3907
- mangledSources.add(c);
3908
- }
3909
- for (const c of result.unmangledClasses) {
3910
- externalClasses.add(c);
3911
- }
3912
- if (result.transformedCount > 0) {
3913
- css = result.css;
3914
- }
3915
- } catch (e) {
3916
- if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") ; else {
3917
- throw e;
3918
- }
3919
- }
3920
- }
3921
- if (css !== source) {
3922
- compilation.updateAsset(
3923
- file,
3924
- new compiler.webpack.sources.RawSource(css)
3925
- );
3926
- }
3927
- continue;
3928
- }
3929
- if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
3930
- if (file.endsWith(".html")) {
3931
- const mangledHtml = source.replace(
3932
- /\bclass="([^"]*)"/g,
3933
- (_m, cls) => {
3934
- const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
3935
- return out !== cls ? `class="${out}"` : _m;
3936
- }
3937
- ).replace(
3938
- /\bclass='([^']*)'/g,
3939
- (_m, cls) => {
3940
- const out = cls.split(/\s+/).filter(Boolean).map((c) => state.mangleMap[c] || c).join(" ");
3941
- return out !== cls ? `class='${out}'` : _m;
3942
- }
3943
- );
3944
- if (mangledHtml !== source) {
3945
- compilation.updateAsset(
3946
- file,
3947
- new compiler.webpack.sources.RawSource(mangledHtml)
3948
- );
3949
- continue;
3950
- }
3951
- } else if (file.endsWith(".js")) {
3952
- let mangled = mangleCodeClasses(source);
3953
- mangled = replacePlaceholders(mangled);
3954
- if (mangled !== source) {
3955
- compilation.updateAsset(
3956
- file,
3957
- new compiler.webpack.sources.RawSource(mangled)
3958
- );
3959
- continue;
3960
- }
3961
- }
3962
- }
3963
- if (file.endsWith(".js") && (source.includes(CHECKSUM_PLACEHOLDER) || source.includes(MANGLE_MAP_PLACEHOLDER))) {
3964
- const replaced = replacePlaceholders(source);
3965
- if (replaced !== source) {
3966
- compilation.updateAsset(
3967
- file,
3968
- new compiler.webpack.sources.RawSource(replaced)
3969
- );
3970
- }
3971
- }
3972
- }
3973
- if (manglingEnabled && !isWebpackDevMode && Object.keys(state.mangleMap).length > 0) {
3974
- const hazardMessage = mangleHybridHazardMessage(
3975
- collectMangleHybridHazards(
3976
- state.mangleMap,
3977
- mangledSources,
3978
- externalClasses
3979
- )
3980
- );
3981
- if (hazardMessage) {
3982
- console.warn(hazardMessage);
3983
- }
3984
- }
3985
- }
3986
- );
3987
- });
4437
+ registerWebpackAssetProcessor(compiler, processWebpackAssets);
3988
4438
  },
3989
- vite: {
3990
- /**
3991
- * Vite hook: mangles CSS selectors and JS class strings in the final bundle.
3992
- * @param _options - the output options (unused)
3993
- * @param bundle - the output bundle containing chunks and assets to process
3994
- */
3995
- generateBundle(_options, bundle) {
3996
- finalizeMangleMap();
3997
- state.globalVarValidationResult = validateGlobalVarBundleInputs(
3998
- collectRollupGlobalVarCssAssets(bundle)
3999
- );
4000
- const manifestData = {
4001
- version: "0.4.0",
4002
- buildId: state.checksum,
4003
- classes: Object.keys(state.mangleMap)
4004
- };
4005
- if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
4006
- manifestData.mangleMap = state.mangleMap;
4007
- }
4008
- if (Object.keys(state.varMangleMap).length > 0) {
4009
- manifestData.varMangleMap = state.varMangleMap;
4010
- }
4011
- const globalVarAliases = extractGlobalVarAliasesForManifest(
4012
- state.varMangleMap,
4013
- globalVarAliasPrefix,
4014
- state.globalVarValidationResult
4015
- );
4016
- if (Object.keys(globalVarAliases).length > 0) {
4017
- manifestData.globalVarAliases = globalVarAliases;
4018
- }
4019
- if (hasCSSVariableMetrics(state.cssVarMetrics)) {
4020
- manifestData.cssVarMetrics = state.cssVarMetrics;
4021
- }
4022
- this.emitFile({
4023
- type: "asset",
4024
- fileName: "csszyx-manifest.json",
4025
- source: JSON.stringify(manifestData)
4026
- });
4027
- if (shouldEmitGlobalVarMapAsset(globalVarMangleConfig)) {
4028
- const globalVarMapAsset = createGlobalVarMapAssetSource(
4029
- state.varMangleMap,
4030
- globalVarAliasPrefix,
4031
- state.globalVarValidationResult
4032
- );
4033
- if (globalVarMapAsset) {
4034
- this.emitFile({
4035
- type: "asset",
4036
- fileName: ".csszyx/global-var-map.json",
4037
- source: globalVarMapAsset
4038
- });
4039
- }
4040
- }
4041
- const mangledSources = /* @__PURE__ */ new Set();
4042
- const externalClasses = /* @__PURE__ */ new Set();
4043
- for (const file in bundle) {
4044
- const chunk = bundle[file];
4045
- if (chunk.type === "asset" && chunk.fileName.endsWith(".css")) {
4046
- const originalCss = chunk.source.toString();
4047
- let css = rewriteCssWithValidatedGlobalVarPlan(
4048
- originalCss,
4049
- file,
4050
- state.globalVarValidationResult
4051
- );
4052
- if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
4053
- try {
4054
- const result = mangleCSSSync(css, state.mangleMap, {
4055
- debug: options.development?.debug,
4056
- from: file
4057
- });
4058
- for (const c of result.mangledClasses) {
4059
- mangledSources.add(c);
4060
- }
4061
- for (const c of result.unmangledClasses) {
4062
- externalClasses.add(c);
4063
- }
4064
- if (result.transformedCount > 0) {
4065
- css = result.css;
4066
- }
4067
- } catch (e) {
4068
- if (e && typeof e === "object" && "name" in e && e.name === "CssSyntaxError") ; else {
4069
- throw e;
4070
- }
4071
- }
4072
- }
4073
- if (css !== originalCss) {
4074
- chunk.source = css;
4075
- }
4076
- continue;
4077
- }
4078
- if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
4079
- if (chunk.type === "chunk") {
4080
- let mangledCode = mangleCodeClasses(chunk.code);
4081
- mangledCode = replacePlaceholders(mangledCode);
4082
- if (mangledCode !== chunk.code) {
4083
- chunk.code = mangledCode;
4084
- }
4085
- continue;
4086
- }
4087
- }
4088
- if (chunk.type === "chunk" && (chunk.code.includes(CHECKSUM_PLACEHOLDER) || chunk.code.includes(MANGLE_MAP_PLACEHOLDER))) {
4089
- const replaced = replacePlaceholders(chunk.code);
4090
- if (replaced !== chunk.code) {
4091
- chunk.code = replaced;
4092
- }
4093
- }
4094
- }
4095
- if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
4096
- const hazardMessage = mangleHybridHazardMessage(
4097
- collectMangleHybridHazards(
4098
- state.mangleMap,
4099
- mangledSources,
4100
- externalClasses
4101
- )
4102
- );
4103
- if (hazardMessage) {
4104
- console.warn(hazardMessage);
4105
- }
4106
- }
4107
- }
4439
+ /**
4440
+ * Rollup-compatible hook used by both pure Rollup and Vite adapters.
4441
+ *
4442
+ * @param this Rollup-compatible output hook context.
4443
+ * @param this.emitFile Output asset emitter.
4444
+ * @param _options Output options (unused).
4445
+ * @param bundle Complete Rollup output bundle.
4446
+ */
4447
+ generateBundle(_options, bundle) {
4448
+ processRollupBundle(bundle, (fileName, source) => {
4449
+ this.emitFile({ type: "asset", fileName, source });
4450
+ });
4108
4451
  }
4109
4452
  }));
4110
4453
  return { prePlugin, postPlugin };
@@ -4136,7 +4479,16 @@ const rollupPlugin = (options = {}) => {
4136
4479
  ];
4137
4480
  };
4138
4481
  const esbuildPlugin = (options = {}) => {
4139
- const { prePlugin, postPlugin } = createCsszyxPlugins(options);
4482
+ if (options.production?.mangle === true) {
4483
+ console.warn(
4484
+ "[csszyx] production.mangle is not supported by the esbuild adapter; class mangling is disabled so emitted JS and CSS keep matching names."
4485
+ );
4486
+ }
4487
+ const safeOptions = {
4488
+ ...options,
4489
+ production: { ...options.production, mangle: false }
4490
+ };
4491
+ const { prePlugin, postPlugin } = createCsszyxPlugins(safeOptions);
4140
4492
  return {
4141
4493
  name: "csszyx",
4142
4494
  /**
@@ -4145,10 +4497,10 @@ const esbuildPlugin = (options = {}) => {
4145
4497
  */
4146
4498
  setup(build) {
4147
4499
  const b = build;
4148
- prePlugin.esbuild(options).setup(b);
4149
- postPlugin.esbuild(options).setup(b);
4500
+ prePlugin.esbuild(safeOptions).setup(b);
4501
+ postPlugin.esbuild(safeOptions).setup(b);
4150
4502
  }
4151
4503
  };
4152
4504
  };
4153
4505
 
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 };
4506
+ export { vitePlugin as $, isPackagesSkippedSource as A, isRSCServerModule as B, isTailwindReservedGlobalVar as C, mangleCodeClassesSync as D, mangleEligibleClasses as E, mangleHybridHazardMessage as F, mergeThemes as G, missingTailwindEntryMessage as H, normalizeGlobalVarAliasesForCache as I, parseThemeBlocks as J, planGlobalVarAliases as K, readGlobalVarScanCache as L, recordGlobalVarSourceFile as M, resolveCompileSourceDirs as N, resolveGlobalVarScanCacheDir as O, resolveNativeCacheIdentity as P, rewriteGlobalVarCssAliases as Q, rollupPlugin as R, scanCustomPropertyNames as S, scanGlobalVarCss as T, shouldEmitWarning as U, shouldTrackGlobalVarSources as V, shouldWarnMissingTailwindEntry as W, shouldWarnUnscopedMonorepo as X, skippedSzFilesMessage as Y, unscopedMonorepoMessage as Z, validateGlobalVarAliasInputs as _, appendTailwindSourceDirective as a, webpackPlugin as a0, writeGlobalVarScanCache as a1, 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 };