@csszyx/unplugin 0.9.9 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ import { isTailwindReservedCustomProperty, CSSZYX_GLOBAL_ALIAS_PREFIX, isCsszyxG
13
13
  import { preprocess } from '@csszyx/vue-adapter';
14
14
  import { createUnplugin } from 'unplugin';
15
15
  import { mangleCSSSync } from '../css-mangler.mjs';
16
+ import { e as escapeHtmlAttribute } from './unplugin.3UumZ5gn.mjs';
16
17
  import { createHash } from 'node:crypto';
17
18
  import { r as resolveTransformCacheDir, c as createTransformCacheKey, a as readTransformCache, w as writeTransformCache, e as evictOldTransformCacheEntries } from './unplugin.BpWUtI9U.mjs';
18
19
  import postcss from 'postcss';
@@ -454,15 +455,7 @@ const CLIENT_RUNTIME_MODULES = /* @__PURE__ */ new Set(["csszyx/browser"]);
454
455
  const CLIENT_RUNTIME_MODULE_ROOTS = ["@csszyx/dynamic", "csszyx/dynamic"];
455
456
  const normalizedModuleIdCache = /* @__PURE__ */ new Map();
456
457
  const resolvedLocalModuleCache = /* @__PURE__ */ new Map();
457
- const FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set([
458
- "_sz",
459
- "_sz2",
460
- "_sz3",
461
- "_szIf",
462
- "_szMerge",
463
- "_szSwitch",
464
- "__csszyx_runtime__"
465
- ]);
458
+ const FORBIDDEN_SYMBOLS = /* @__PURE__ */ new Set(["_sz", "_sz2", "_sz3", "_szMerge", "__csszyx_runtime__"]);
466
459
  function hasUseServerDirective(code) {
467
460
  for (const statement of readDirectivePrologue(code)) {
468
461
  if (SERVER_DIRECTIVE_RE.test(statement)) {
@@ -841,7 +834,10 @@ function resolveLocalModule(importer, source) {
841
834
  const cacheKey = `${importer}\0${source}`;
842
835
  const cached = resolvedLocalModuleCache.get(cacheKey);
843
836
  if (cached) {
844
- return cached;
837
+ if (fs.existsSync(cached)) {
838
+ return cached;
839
+ }
840
+ resolvedLocalModuleCache.delete(cacheKey);
845
841
  }
846
842
  const base = source.startsWith("/") ? source : path.resolve(path.dirname(importer), source);
847
843
  const candidates = [
@@ -1006,7 +1002,14 @@ function stripCommentsForImportScan(code) {
1006
1002
  return out;
1007
1003
  }
1008
1004
 
1009
- const EMPTY_THEME = { colors: [], spacings: [], fonts: [], radii: [], shadows: [] };
1005
+ const EMPTY_THEME = {
1006
+ colors: [],
1007
+ spacings: [],
1008
+ fonts: [],
1009
+ radii: [],
1010
+ shadows: [],
1011
+ breakpoints: []
1012
+ };
1010
1013
  function stripLayerWrappers(css) {
1011
1014
  let result = "";
1012
1015
  let i = 0;
@@ -1074,12 +1077,15 @@ function categorizeProperty(prop) {
1074
1077
  ["spacing-", "spacings"],
1075
1078
  ["font-", "fonts"],
1076
1079
  ["radius-", "radii"],
1077
- ["shadow-", "shadows"]
1080
+ ["shadow-", "shadows"],
1081
+ ["breakpoint-", "breakpoints"]
1078
1082
  ];
1079
1083
  for (const [prefix, category] of categoryMap) {
1080
1084
  if (prop.startsWith(prefix)) {
1081
1085
  let token = prop.slice(prefix.length);
1082
- token = token.replace(/-\d+$/, "");
1086
+ if (category !== "breakpoints") {
1087
+ token = token.replace(/-\d+$/, "");
1088
+ }
1083
1089
  if (token) {
1084
1090
  return { category, token };
1085
1091
  }
@@ -1093,7 +1099,8 @@ function parseThemeBlocks(cssContent) {
1093
1099
  spacings: /* @__PURE__ */ new Set(),
1094
1100
  fonts: /* @__PURE__ */ new Set(),
1095
1101
  radii: /* @__PURE__ */ new Set(),
1096
- shadows: /* @__PURE__ */ new Set()
1102
+ shadows: /* @__PURE__ */ new Set(),
1103
+ breakpoints: /* @__PURE__ */ new Set()
1097
1104
  };
1098
1105
  const stripped = stripLayerWrappers(cssContent);
1099
1106
  const blocks = extractThemeBlocks(stripped);
@@ -1111,7 +1118,8 @@ function parseThemeBlocks(cssContent) {
1111
1118
  spacings: [...result.spacings].sort(),
1112
1119
  fonts: [...result.fonts].sort(),
1113
1120
  radii: [...result.radii].sort(),
1114
- shadows: [...result.shadows].sort()
1121
+ shadows: [...result.shadows].sort(),
1122
+ breakpoints: [...result.breakpoints].sort()
1115
1123
  };
1116
1124
  }
1117
1125
  function mergeThemes(themes) {
@@ -1123,7 +1131,8 @@ function mergeThemes(themes) {
1123
1131
  spacings: /* @__PURE__ */ new Set(),
1124
1132
  fonts: /* @__PURE__ */ new Set(),
1125
1133
  radii: /* @__PURE__ */ new Set(),
1126
- shadows: /* @__PURE__ */ new Set()
1134
+ shadows: /* @__PURE__ */ new Set(),
1135
+ breakpoints: /* @__PURE__ */ new Set()
1127
1136
  };
1128
1137
  for (const theme of themes) {
1129
1138
  for (const cat of Object.keys(merged)) {
@@ -1137,7 +1146,8 @@ function mergeThemes(themes) {
1137
1146
  spacings: [...merged.spacings].sort(),
1138
1147
  fonts: [...merged.fonts].sort(),
1139
1148
  radii: [...merged.radii].sort(),
1140
- shadows: [...merged.shadows].sort()
1149
+ shadows: [...merged.shadows].sort(),
1150
+ breakpoints: [...merged.breakpoints].sort()
1141
1151
  };
1142
1152
  }
1143
1153
  function hasTokens(theme) {
@@ -1425,11 +1435,15 @@ function isSameFileVersion(before, after) {
1425
1435
  return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeNs === after.mtimeNs && before.ctimeNs === after.ctimeNs;
1426
1436
  }
1427
1437
 
1438
+ function escapeTsString(value) {
1439
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\r/g, "\\r").replace(/\n/g, "\\n");
1440
+ }
1428
1441
  function generateThemeDts(opts) {
1429
1442
  const { theme, sourceFiles } = opts;
1430
1443
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1431
- const sources = sourceFiles.join(", ");
1432
- const toUnion = (tokens) => tokens.map((t) => `'${t}'`).join(" | ");
1444
+ const sources = sourceFiles.join(", ").replace(/[\r\n]/g, " ");
1445
+ const toUnion = (tokens) => tokens.map((t) => `'${escapeTsString(t)}'`).join(" | ");
1446
+ const quoteKey = (key) => /^[a-z_$][\w$]*$/i.test(key) ? key : `'${escapeTsString(key)}'`;
1433
1447
  const entries = [];
1434
1448
  if (theme.colors.length > 0) {
1435
1449
  entries.push(` colors: ${toUnion(theme.colors)};`);
@@ -1446,19 +1460,36 @@ function generateThemeDts(opts) {
1446
1460
  if (theme.shadows.length > 0) {
1447
1461
  entries.push(` shadows: ${toUnion(theme.shadows)};`);
1448
1462
  }
1449
- return [
1450
- "// Auto-generated by csszyx theme-scanner \u2014 DO NOT EDIT",
1451
- `// Source: ${sources}`,
1452
- `// Updated: ${timestamp}`,
1453
- "",
1454
- "declare module '@csszyx/compiler' {",
1463
+ const moduleBody = [
1455
1464
  " /**",
1456
1465
  " * Custom design tokens extracted from @theme blocks.",
1457
1466
  " * These tokens are surfaced in sz prop IntelliSense.",
1458
1467
  " */",
1459
1468
  " interface CustomTheme {",
1460
1469
  ...entries,
1461
- " }",
1470
+ " }"
1471
+ ];
1472
+ if (theme.breakpoints.length > 0) {
1473
+ const variantEntries = theme.breakpoints.map(
1474
+ (bp) => ` ${quoteKey(bp)}?: SzPropsBase;`
1475
+ );
1476
+ moduleBody.push(
1477
+ " /**",
1478
+ " * Custom responsive breakpoints from @theme (--breakpoint-*),",
1479
+ " * surfaced as typed sz variant keys.",
1480
+ " */",
1481
+ " interface VariantModifiers {",
1482
+ ...variantEntries,
1483
+ " }"
1484
+ );
1485
+ }
1486
+ return [
1487
+ "// Auto-generated by csszyx theme-scanner \u2014 DO NOT EDIT",
1488
+ `// Source: ${sources}`,
1489
+ `// Updated: ${timestamp}`,
1490
+ "",
1491
+ "declare module '@csszyx/compiler' {",
1492
+ ...moduleBody,
1462
1493
  "}",
1463
1494
  "",
1464
1495
  "export {};",
@@ -1532,6 +1563,7 @@ const UNKNOWN_PACKAGE_VERSION = "0.0.0";
1532
1563
  const TRANSFORM_CACHE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
1533
1564
  const TRANSFORM_CACHE_MAX_ENTRIES = 1e4;
1534
1565
  const TRANSFORM_MEMORY_CACHE_MAX_ENTRIES = 1e3;
1566
+ const MAX_SAFELIST_CLASSES = 1e5;
1535
1567
  const DEFAULT_VAR_MANGLE_MAP_MAX_BYTES = 100 * 1024;
1536
1568
  const GLOBAL_VAR_ALIAS_MAP_OWNER = "\0csszyx:global-var-aliases";
1537
1569
  const DIRECTIVE_PROLOGUE_PREFIX_RE = /^((?:\s|\/\/[^\n]*\n|\/\*(?:[^*]|\*(?!\/))*\*\/)*)(['"]use (?:client|server)['"];?\s*)/;
@@ -1572,6 +1604,133 @@ function resolveNativeCacheIdentity() {
1572
1604
  }
1573
1605
  const BENCH_TRACE_ENABLED = process.env.CSSZYX_BENCH_TRACE === "1";
1574
1606
  const BENCH_TRACE_FILE = process.env.CSSZYX_BENCH_TRACE_FILE;
1607
+ function appendTailwindSourceDirective(code, relPath) {
1608
+ const directive = `@source "${relPath}";`;
1609
+ if (code.includes(directive)) {
1610
+ return null;
1611
+ }
1612
+ const separator = code.length === 0 || code.endsWith("\n") ? "" : "\n";
1613
+ return `${code}${separator}${directive}
1614
+ `;
1615
+ }
1616
+ function stripCssBlockComments(code) {
1617
+ const SLASH = 47;
1618
+ const STAR = 42;
1619
+ let out = "";
1620
+ let last = 0;
1621
+ let i = 0;
1622
+ const n = code.length;
1623
+ while (i < n) {
1624
+ if (code.charCodeAt(i) === SLASH && code.charCodeAt(i + 1) === STAR) {
1625
+ out += code.slice(last, i);
1626
+ i += 2;
1627
+ while (i < n && !(code.charCodeAt(i) === STAR && code.charCodeAt(i + 1) === SLASH)) {
1628
+ i++;
1629
+ }
1630
+ i += 2;
1631
+ last = i;
1632
+ } else {
1633
+ i++;
1634
+ }
1635
+ }
1636
+ return out + code.slice(last);
1637
+ }
1638
+ function cssImportsTailwind(code) {
1639
+ const withoutBlockComments = stripCssBlockComments(code);
1640
+ return /@import\s+["']tailwindcss(?:\/[^"']*)?["']/.test(withoutBlockComments);
1641
+ }
1642
+ function hasInjectableTailwindCandidate(classes) {
1643
+ for (const c of classes) {
1644
+ if (c.length >= 2 && /^[a-z]/.test(c)) {
1645
+ return true;
1646
+ }
1647
+ }
1648
+ return false;
1649
+ }
1650
+ function shouldWarnMissingTailwindEntry(ownedClassCount, sawTailwindEntry) {
1651
+ return ownedClassCount > 0 && !sawTailwindEntry;
1652
+ }
1653
+ function missingTailwindEntryMessage(ownedClassCount) {
1654
+ return `[csszyx] generated ${ownedClassCount} sz class(es) but found no CSS entry importing "tailwindcss" \u2014 those classes will produce no CSS. Import "tailwindcss" in a CSS file (csszyx auto-injects @source for the generated classes) so Tailwind emits their styles.`;
1655
+ }
1656
+ function cssHasContentScope(code) {
1657
+ const s = stripCssBlockComments(code);
1658
+ return /@import\s+["']tailwindcss(?:\/[^"']*)?["']\s+source\(/.test(s) || /@source\s+not\b/.test(s);
1659
+ }
1660
+ function isMonorepoPackage(root) {
1661
+ let dir = path.dirname(path.resolve(root));
1662
+ const { root: fsRoot } = path.parse(dir);
1663
+ while (dir !== fsRoot) {
1664
+ if (fs.existsSync(path.join(dir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(dir, "nx.json")) || fs.existsSync(path.join(dir, "lerna.json"))) {
1665
+ return true;
1666
+ }
1667
+ const pkgPath = path.join(dir, "package.json");
1668
+ if (fs.existsSync(pkgPath)) {
1669
+ try {
1670
+ if ("workspaces" in JSON.parse(fs.readFileSync(pkgPath, "utf8"))) {
1671
+ return true;
1672
+ }
1673
+ } catch {
1674
+ }
1675
+ }
1676
+ dir = path.dirname(dir);
1677
+ }
1678
+ return false;
1679
+ }
1680
+ function shouldWarnUnscopedMonorepo(sawTailwindEntry, tailwindEntryScoped, inMonorepo) {
1681
+ return sawTailwindEntry && !tailwindEntryScoped && inMonorepo;
1682
+ }
1683
+ function unscopedMonorepoMessage() {
1684
+ return '[csszyx] Tailwind content detection is UNSCOPED in a monorepo. Tailwind v4 climbs to the workspace root and scans sibling packages + docs (.md/.mdx/.txt are not ignored), which can generate phantom or broken url() classes and fail the build. Scope it in your Tailwind CSS entry:\n @import "tailwindcss" source(none);\n @source "."; /* this package, relative to the CSS file */\ncsszyx auto-injects @source for its generated classes, so only your own templates need listing. Guide: https://csszyx.com/docs/monorepo-content-scope/\nSilence (if a broad scan is intentional): csszyx({ contentScopeCheck: false }).';
1685
+ }
1686
+ function isCompilePackageOptedIn(id, compilePackages) {
1687
+ const path2 = id.replace(/\\/g, "/");
1688
+ if (path2.includes("node_modules")) {
1689
+ return false;
1690
+ }
1691
+ return compilePackages.some((name) => path2.includes(`/packages/${name}/`));
1692
+ }
1693
+ function isHardIgnoredPath(id, compilePackages = []) {
1694
+ const path2 = id.replace(/\\/g, "/");
1695
+ if (path2.includes("node_modules")) {
1696
+ return true;
1697
+ }
1698
+ if (path2.includes(".next") && !path2.includes("static")) {
1699
+ return true;
1700
+ }
1701
+ if (path2.includes("/packages/")) {
1702
+ return !isCompilePackageOptedIn(path2, compilePackages);
1703
+ }
1704
+ return false;
1705
+ }
1706
+ function isPackagesSkippedSource(id, compilePackages = []) {
1707
+ const path2 = id.replace(/\\/g, "/");
1708
+ if (path2.includes("node_modules")) {
1709
+ return false;
1710
+ }
1711
+ if (path2.includes(".next") && !path2.includes("static")) {
1712
+ return false;
1713
+ }
1714
+ if (!path2.includes("/packages/")) {
1715
+ return false;
1716
+ }
1717
+ return !isCompilePackageOptedIn(path2, compilePackages);
1718
+ }
1719
+ function skippedSzFilesMessage(files) {
1720
+ const list = files.map((file) => ` - ${file}`).join("\n");
1721
+ return `[csszyx] ${files.length} file(s) under packages/ contain \`sz\` but were skipped by ignore rules:
1722
+ ${list}
1723
+ Add the package to \`compilePackages\` (or move the file out of packages/) \u2014 otherwise their \`sz\` produces no CSS.`;
1724
+ }
1725
+ function computeSafelistRelPath(rootDir, safelistFilename, cssId) {
1726
+ const safelistPath = path.join(rootDir, safelistFilename).replace(/\\/g, "/");
1727
+ const cssDir = path.dirname(cssId).replace(/\\/g, "/");
1728
+ let relPath = path.posix.relative(cssDir, safelistPath);
1729
+ if (!relPath.startsWith(".")) {
1730
+ relPath = `./${relPath}`;
1731
+ }
1732
+ return relPath;
1733
+ }
1575
1734
  function cssVariableEntries(result) {
1576
1735
  const entries = [];
1577
1736
  for (const [original, value] of result.cssVariableMap ?? []) {
@@ -2035,6 +2194,45 @@ function mangleCodeClassesSync(code, mangleMap) {
2035
2194
  }
2036
2195
  return changed ? `className:\`${out}\`` : fullMatch;
2037
2196
  });
2197
+ function scanClassExpression(source, from) {
2198
+ let depth = 0;
2199
+ let j = from;
2200
+ while (j < source.length) {
2201
+ const ch = source[j];
2202
+ if (ch === "(" || ch === "[") {
2203
+ depth++;
2204
+ } else if (ch === ")" || ch === "]") {
2205
+ if (depth === 0) {
2206
+ break;
2207
+ }
2208
+ depth--;
2209
+ } else if (depth === 0 && (ch === "," || ch === ";" || ch === "\n" || ch === "}")) {
2210
+ break;
2211
+ }
2212
+ j++;
2213
+ }
2214
+ return j;
2215
+ }
2216
+ function mangleTernaryClassStrings(expr) {
2217
+ const qIdx = expr.indexOf("?");
2218
+ if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
2219
+ return null;
2220
+ }
2221
+ let changed = false;
2222
+ const mangled = expr.replace(/"([^"]*)"/g, (qm, inner) => {
2223
+ const parts = inner.split(/\s+/).filter(Boolean);
2224
+ if (parts.length === 0) {
2225
+ return qm;
2226
+ }
2227
+ const mangledStr = parts.map((p) => mangleMap[p] || p).join(" ");
2228
+ if (mangledStr !== inner) {
2229
+ changed = true;
2230
+ return `"${mangledStr}"`;
2231
+ }
2232
+ return qm;
2233
+ });
2234
+ return changed ? mangled : null;
2235
+ }
2038
2236
  {
2039
2237
  const marker = "className:";
2040
2238
  let searchFrom = 0;
@@ -2056,47 +2254,39 @@ function mangleCodeClassesSync(code, mangleMap) {
2056
2254
  searchFrom = afterColon;
2057
2255
  continue;
2058
2256
  }
2059
- let depth = 0;
2060
- let j = afterColon;
2061
- while (j < result.length) {
2062
- const ch = result[j];
2063
- if (ch === "(" || ch === "[") {
2064
- depth++;
2065
- } else if (ch === ")" || ch === "]") {
2066
- if (depth === 0) {
2067
- break;
2068
- }
2069
- depth--;
2070
- } else if (depth === 0 && (ch === "," || ch === ";" || ch === "\n" || ch === "}")) {
2071
- break;
2072
- }
2073
- j++;
2074
- }
2257
+ const j = scanClassExpression(result, afterColon);
2075
2258
  const expr = result.slice(afterColon, j);
2076
- const qIdx = expr.indexOf("?");
2077
- if (qIdx === -1 || !expr.slice(qIdx).includes(":")) {
2078
- out += expr;
2079
- searchFrom = j;
2080
- continue;
2081
- }
2082
- let changed = false;
2083
- const mangled = expr.replace(/"([^"]*)"/g, (qm, inner) => {
2084
- const parts = inner.split(/\s+/).filter(Boolean);
2085
- if (parts.length === 0) {
2086
- return qm;
2087
- }
2088
- const mangledStr = parts.map((p) => mangleMap[p] || p).join(" ");
2089
- if (mangledStr !== inner) {
2090
- changed = true;
2091
- return `"${mangledStr}"`;
2092
- }
2093
- return qm;
2094
- });
2095
- out += changed ? mangled : expr;
2259
+ out += mangleTernaryClassStrings(expr) ?? expr;
2096
2260
  searchFrom = j;
2097
2261
  }
2098
2262
  result = out;
2099
2263
  }
2264
+ {
2265
+ const markerRe = /"class(?:Name)?"\s*,\s*/g;
2266
+ let out = "";
2267
+ let copiedTo = 0;
2268
+ let m = markerRe.exec(result);
2269
+ while (m !== null) {
2270
+ const exprStart = m.index + m[0].length;
2271
+ const firstChar = result[exprStart];
2272
+ if (firstChar === '"' || firstChar === "'" || firstChar === "`") {
2273
+ m = markerRe.exec(result);
2274
+ continue;
2275
+ }
2276
+ const j = scanClassExpression(result, exprStart);
2277
+ const expr = result.slice(exprStart, j);
2278
+ const mangled = mangleTernaryClassStrings(expr);
2279
+ if (mangled !== null) {
2280
+ out += result.slice(copiedTo, exprStart) + mangled;
2281
+ copiedTo = j;
2282
+ }
2283
+ markerRe.lastIndex = j;
2284
+ m = markerRe.exec(result);
2285
+ }
2286
+ if (copiedTo > 0) {
2287
+ result = out + result.slice(copiedTo);
2288
+ }
2289
+ }
2100
2290
  result = result.replace(/(?<=(?:[,(]|&&)\s*)"([^"]+)"/g, (match, inner) => {
2101
2291
  const tokens = inner.split(/\s+/).filter(Boolean);
2102
2292
  if (tokens.length === 0) {
@@ -2164,6 +2354,7 @@ function createCsszyxPlugins(options = {}) {
2164
2354
  "[csszyx] Transform cache disabled because package versions could not be resolved."
2165
2355
  );
2166
2356
  }
2357
+ const compilePackages = options.compilePackages ?? [];
2167
2358
  const parserOverride = process.env.CSSZYX_PARSER;
2168
2359
  const defaultParser = DEFAULT_BUILD_CONFIG.parser ?? "rust";
2169
2360
  const parserMode = parserOverride === "babel" || parserOverride === "oxc" || parserOverride === "rust" ? parserOverride : options.build?.parser ?? defaultParser;
@@ -2171,6 +2362,15 @@ function createCsszyxPlugins(options = {}) {
2171
2362
  const transformMemoryCache = /* @__PURE__ */ new Map();
2172
2363
  const state = {
2173
2364
  classes: /* @__PURE__ */ new Set(),
2365
+ sawTailwindEntry: false,
2366
+ tailwindWarningEmitted: false,
2367
+ tailwindEntryScoped: false,
2368
+ contentScopeWarningEmitted: false,
2369
+ spreadWarnings: /* @__PURE__ */ new Set(),
2370
+ skippedSzFiles: /* @__PURE__ */ new Set(),
2371
+ skipWarningEmitted: false,
2372
+ classesCapped: false,
2373
+ ownedClasses: /* @__PURE__ */ new Set(),
2174
2374
  mangleMap: {},
2175
2375
  varMangleEntriesByFile: /* @__PURE__ */ new Map(),
2176
2376
  varMangleMap: Object.fromEntries(earlyGlobalVarAliasEntries),
@@ -2231,7 +2431,7 @@ function createCsszyxPlugins(options = {}) {
2231
2431
  return result;
2232
2432
  }
2233
2433
  function isHardIgnored(id) {
2234
- return id.includes("node_modules") || id.includes("/packages/") || id.includes(".next") && !id.includes("static");
2434
+ return isHardIgnoredPath(id, compilePackages);
2235
2435
  }
2236
2436
  function shouldProcessSource(id) {
2237
2437
  return !isHardIgnored(id) && !isUserExcluded(id) && isUserIncluded(id) && (/\.[tj]sx?(\?.*)?$/.test(id) || id.endsWith(".vue") || id.endsWith(".svelte"));
@@ -2437,12 +2637,19 @@ function createCsszyxPlugins(options = {}) {
2437
2637
  maxEntries: TRANSFORM_CACHE_MAX_ENTRIES
2438
2638
  });
2439
2639
  }
2640
+ function addSafelistClass(cls) {
2641
+ if (state.classes.size >= MAX_SAFELIST_CLASSES) {
2642
+ state.classesCapped = true;
2643
+ return;
2644
+ }
2645
+ state.classes.add(cls);
2646
+ }
2440
2647
  function writeSafelistFile(classes) {
2441
2648
  if (classes.size === 0) {
2442
2649
  return;
2443
2650
  }
2444
2651
  const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
2445
- const classList = Array.from(classes).join(" ");
2652
+ const classList = escapeHtmlAttribute(Array.from(classes).join(" "));
2446
2653
  const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
2447
2654
  <!-- Tailwind CSS scans this file for class name detection -->
2448
2655
  <div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
@@ -2462,6 +2669,20 @@ function createCsszyxPlugins(options = {}) {
2462
2669
  } catch {
2463
2670
  }
2464
2671
  }
2672
+ function recordPackagesSkipIfSz(filePath) {
2673
+ if (!isPackagesSkippedSource(filePath, compilePackages)) {
2674
+ return;
2675
+ }
2676
+ let content;
2677
+ try {
2678
+ content = fs.readFileSync(filePath, "utf-8");
2679
+ } catch {
2680
+ return;
2681
+ }
2682
+ if (content.includes("sz=") || content.includes("sz:")) {
2683
+ state.skippedSzFiles.add(filePath);
2684
+ }
2685
+ }
2465
2686
  function prescanAndWriteClasses() {
2466
2687
  const prescanStarted = performance.now();
2467
2688
  const discoveredClasses = /* @__PURE__ */ new Set();
@@ -2482,6 +2703,7 @@ function createCsszyxPlugins(options = {}) {
2482
2703
  } else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
2483
2704
  const filePath = path.join(dir, entry.name);
2484
2705
  if (!shouldProcessSource(filePath)) {
2706
+ recordPackagesSkipIfSz(filePath);
2485
2707
  continue;
2486
2708
  }
2487
2709
  let content;
@@ -2505,7 +2727,8 @@ function createCsszyxPlugins(options = {}) {
2505
2727
  collectPrescanResult(result, filePath, discoveredClasses, rawDiscoveredClasses);
2506
2728
  }
2507
2729
  for (const cls of discoveredClasses) {
2508
- state.classes.add(cls);
2730
+ addSafelistClass(cls);
2731
+ state.ownedClasses.add(cls);
2509
2732
  }
2510
2733
  const safelistClasses = /* @__PURE__ */ new Set([...discoveredClasses, ...rawDiscoveredClasses]);
2511
2734
  writeSafelistFile(safelistClasses);
@@ -2593,7 +2816,7 @@ function createCsszyxPlugins(options = {}) {
2593
2816
  for (const match of code.matchAll(classPattern)) {
2594
2817
  const classes = match[1].split(/\s+/).filter(Boolean);
2595
2818
  for (const cls of classes) {
2596
- state.classes.add(cls);
2819
+ addSafelistClass(cls);
2597
2820
  }
2598
2821
  }
2599
2822
  }
@@ -2615,13 +2838,13 @@ function createCsszyxPlugins(options = {}) {
2615
2838
  const str = strMatch[1] || strMatch[2];
2616
2839
  const classes = str.split(/\s+/).filter(Boolean);
2617
2840
  for (const cls of classes) {
2618
- state.classes.add(cls);
2841
+ addSafelistClass(cls);
2619
2842
  }
2620
2843
  }
2621
2844
  }
2622
2845
  }
2623
2846
  function finalizeMangleMap() {
2624
- const sortedClasses = Array.from(state.classes);
2847
+ const sortedClasses = Array.from(state.ownedClasses);
2625
2848
  const newMap = {};
2626
2849
  for (let i = 0; i < sortedClasses.length; i++) {
2627
2850
  newMap[sortedClasses[i]] = encode(i);
@@ -2668,6 +2891,21 @@ function createCsszyxPlugins(options = {}) {
2668
2891
  }
2669
2892
  return null;
2670
2893
  },
2894
+ /**
2895
+ * Restricts the load hook to csszyx's own virtual modules.
2896
+ *
2897
+ * Without this, unplugin's webpack adapter registers its load
2898
+ * loader with `type: 'javascript/auto'` for every module (its
2899
+ * include defaults to all ids when no loadInclude exists). That
2900
+ * corrupts binary asset modules (images, fonts) in webpack apps —
2901
+ * Next.js builds fail with "not a valid image file" / "Module
2902
+ * parse failed" on assets that build fine without csszyx.
2903
+ * @param id - the module ID webpack is about to load
2904
+ * @returns true only for csszyx virtual modules
2905
+ */
2906
+ loadInclude(id) {
2907
+ return id === RESOLVED_VIRTUAL_MODULE_ID || id === RESOLVED_VIRTUAL_CHECKSUM_ID;
2908
+ },
2671
2909
  /**
2672
2910
  * Loads virtual module content — generates mangle map or checksum module code.
2673
2911
  * @param id - the resolved module ID to load
@@ -2691,7 +2929,7 @@ function createCsszyxPlugins(options = {}) {
2691
2929
  },
2692
2930
  /**
2693
2931
  * Filters files for the pre-transform phase — source files plus CSS files.
2694
- * CSS files need special handling to inject @source inline() for Tailwind class discovery.
2932
+ * CSS files need special handling to append an @source directive for Tailwind class discovery.
2695
2933
  * @param id - the file path to check for inclusion
2696
2934
  * @returns true if the file should be transformed, false otherwise
2697
2935
  */
@@ -2703,7 +2941,7 @@ function createCsszyxPlugins(options = {}) {
2703
2941
  },
2704
2942
  /**
2705
2943
  * Core transform: detects sz prop, compiles to className, injects runtime, collects classes.
2706
- * For CSS files: injects @source inline() so Tailwind generates CSS for sz-derived classes.
2944
+ * For CSS files: appends an @source directive so Tailwind generates CSS for sz-derived classes.
2707
2945
  * @param code - the source code to transform
2708
2946
  * @param id - the file path of the module being transformed
2709
2947
  * @returns transformed code with source map, or null if no changes were made
@@ -2719,24 +2957,19 @@ function createCsszyxPlugins(options = {}) {
2719
2957
  assertNoRSCBoundaryViolation(code, id);
2720
2958
  }
2721
2959
  if (/\.css(\?.*)?$/.test(id)) {
2722
- const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
2723
- if (hasTailwindImport && state.classes.size > 0) {
2724
- const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
2725
- if (candidates) {
2726
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
2727
- const cssDir = path.dirname(id).replace(/\\/g, "/");
2728
- let relPath = path.posix.relative(cssDir, safelistPath);
2729
- if (!relPath.startsWith(".")) {
2730
- relPath = `./${relPath}`;
2731
- }
2732
- const sourceDirective = `@source "${relPath}";
2733
- `;
2734
- const transformed2 = code.replace(
2735
- /(@import\s+["']tailwindcss[^"']*["'];)/,
2736
- `$1
2737
- ${sourceDirective}`
2960
+ if (cssImportsTailwind(code)) {
2961
+ state.sawTailwindEntry = true;
2962
+ if (cssHasContentScope(code)) {
2963
+ state.tailwindEntryScoped = true;
2964
+ }
2965
+ if (hasInjectableTailwindCandidate(state.classes)) {
2966
+ const relPath = computeSafelistRelPath(
2967
+ state.rootDir,
2968
+ SAFELIST_FILENAME,
2969
+ id
2738
2970
  );
2739
- if (transformed2 !== code) {
2971
+ const transformed2 = appendTailwindSourceDirective(code, relPath);
2972
+ if (transformed2 !== null) {
2740
2973
  return { code: transformed2, map: null };
2741
2974
  }
2742
2975
  }
@@ -2779,8 +3012,15 @@ ${sourceDirective}`
2779
3012
  szClasses = result.classes;
2780
3013
  recordFileVarMangleEntries(state, id, cssVariableEntries(result));
2781
3014
  recordFileCSSVariableMetrics(state, id, result.code);
3015
+ for (const msg of result.diagnostics) {
3016
+ if (msg.includes("unresolvable sz spread")) {
3017
+ state.spreadWarnings.add(`${id}
3018
+ ${msg}`);
3019
+ }
3020
+ }
2782
3021
  if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
2783
3022
  for (const msg of result.diagnostics) {
3023
+ if (msg.includes("unresolvable sz spread")) continue;
2784
3024
  this.warn(`[csszyx] ${id}
2785
3025
  ${msg}`);
2786
3026
  }
@@ -2848,7 +3088,8 @@ ${sourceDirective}`
2848
3088
  if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
2849
3089
  if (szClasses !== void 0) {
2850
3090
  for (const cls of szClasses) {
2851
- state.classes.add(cls);
3091
+ addSafelistClass(cls);
3092
+ state.ownedClasses.add(cls);
2852
3093
  }
2853
3094
  } else {
2854
3095
  extractClasses(transformedCode);
@@ -2861,6 +3102,36 @@ ${sourceDirective}`
2861
3102
  buildEnd() {
2862
3103
  finalizeMangleMap();
2863
3104
  assertNoRSCGraphViolation(state.rscModules);
3105
+ if (!state.tailwindWarningEmitted && shouldWarnMissingTailwindEntry(state.ownedClasses.size, state.sawTailwindEntry)) {
3106
+ state.tailwindWarningEmitted = true;
3107
+ console.warn(missingTailwindEntryMessage(state.ownedClasses.size));
3108
+ }
3109
+ if (!state.contentScopeWarningEmitted && options.contentScopeCheck !== false && state.sawTailwindEntry && !state.tailwindEntryScoped) {
3110
+ if (state.inMonorepo === void 0) {
3111
+ state.inMonorepo = isMonorepoPackage(state.rootDir);
3112
+ }
3113
+ if (shouldWarnUnscopedMonorepo(
3114
+ state.sawTailwindEntry,
3115
+ state.tailwindEntryScoped,
3116
+ state.inMonorepo
3117
+ )) {
3118
+ state.contentScopeWarningEmitted = true;
3119
+ console.warn(unscopedMonorepoMessage());
3120
+ }
3121
+ }
3122
+ if (!state.skipWarningEmitted && state.skippedSzFiles.size > 0) {
3123
+ state.skipWarningEmitted = true;
3124
+ console.warn(skippedSzFilesMessage([...state.skippedSzFiles].sort()));
3125
+ }
3126
+ if (state.classesCapped) {
3127
+ console.warn(
3128
+ `[csszyx] safelist exceeded ${MAX_SAFELIST_CLASSES} classes; additional classes were dropped. This usually means an unbounded set of arbitrary values reached an sz prop.`
3129
+ );
3130
+ }
3131
+ for (const warning of state.spreadWarnings) {
3132
+ console.warn(`[csszyx] ${warning}`);
3133
+ }
3134
+ state.spreadWarnings.clear();
2864
3135
  if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
2865
3136
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
2866
3137
  }
@@ -2960,7 +3231,8 @@ ${sourceDirective}`
2960
3231
  const sizeBefore = state.classes.size;
2961
3232
  recordGlobalVarSourceFile(state, ctx.file, fileContent);
2962
3233
  for (const cls of result.classes) {
2963
- state.classes.add(cls);
3234
+ addSafelistClass(cls);
3235
+ state.ownedClasses.add(cls);
2964
3236
  }
2965
3237
  recordFileVarMangleEntries(state, ctx.file, cssVariableEntries(result));
2966
3238
  recordFileCSSVariableMetrics(state, ctx.file, result.code);
@@ -3299,4 +3571,4 @@ const esbuildPlugin = (options = {}) => {
3299
3571
  };
3300
3572
  };
3301
3573
 
3302
- export { rollupPlugin as A, scanGlobalVarCss as B, validateGlobalVarAliasInputs as C, vitePlugin as D, webpackPlugin as E, writeGlobalVarScanCache as F, assertNoRSCBoundaryViolation as a, assertNoRSCGraphViolation as b, createGlobalVarAliasValidationOptions as c, createGlobalVarMapAssetSource as d, createGlobalVarScanCacheKey as e, createRSCModuleRecord as f, deleteRSCModuleRecord as g, esbuildPlugin as h, extractGlobalVarAliasesForManifest as i, findRSCBoundaryViolation as j, findRSCGraphViolation as k, hasTokens as l, hasUseClientDirective as m, hasUseServerDirective as n, isRSCServerModule as o, isTailwindReservedGlobalVar as p, mangleCodeClassesSync as q, mergeThemes as r, normalizeGlobalVarAliasesForCache as s, parseThemeBlocks as t, unplugin as u, planGlobalVarAliases as v, readGlobalVarScanCache as w, resolveGlobalVarScanCacheDir as x, resolveNativeCacheIdentity as y, rewriteGlobalVarCssAliases as z };
3574
+ export { mangleCodeClassesSync as A, mergeThemes as B, missingTailwindEntryMessage as C, normalizeGlobalVarAliasesForCache as D, parseThemeBlocks as E, planGlobalVarAliases as F, readGlobalVarScanCache as G, resolveGlobalVarScanCacheDir as H, resolveNativeCacheIdentity as I, rewriteGlobalVarCssAliases as J, rollupPlugin as K, scanGlobalVarCss as L, shouldWarnMissingTailwindEntry as M, shouldWarnUnscopedMonorepo as N, skippedSzFilesMessage as O, unscopedMonorepoMessage as P, validateGlobalVarAliasInputs as Q, vitePlugin as R, webpackPlugin as S, writeGlobalVarScanCache as T, appendTailwindSourceDirective as a, assertNoRSCBoundaryViolation as b, assertNoRSCGraphViolation as c, computeSafelistRelPath as d, createGlobalVarAliasValidationOptions as e, createGlobalVarMapAssetSource as f, createGlobalVarScanCacheKey as g, createRSCModuleRecord as h, cssHasContentScope as i, cssImportsTailwind as j, deleteRSCModuleRecord as k, esbuildPlugin as l, extractGlobalVarAliasesForManifest as m, findRSCBoundaryViolation as n, findRSCGraphViolation as o, hasInjectableTailwindCandidate as p, hasTokens as q, hasUseClientDirective as r, hasUseServerDirective as s, isCompilePackageOptedIn as t, unplugin as u, isHardIgnoredPath as v, isMonorepoPackage as w, isPackagesSkippedSource as x, isRSCServerModule as y, isTailwindReservedGlobalVar as z };