@csszyx/unplugin 0.9.10 → 0.10.1

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 ?? []) {
@@ -2195,6 +2354,7 @@ function createCsszyxPlugins(options = {}) {
2195
2354
  "[csszyx] Transform cache disabled because package versions could not be resolved."
2196
2355
  );
2197
2356
  }
2357
+ const compilePackages = options.compilePackages ?? [];
2198
2358
  const parserOverride = process.env.CSSZYX_PARSER;
2199
2359
  const defaultParser = DEFAULT_BUILD_CONFIG.parser ?? "rust";
2200
2360
  const parserMode = parserOverride === "babel" || parserOverride === "oxc" || parserOverride === "rust" ? parserOverride : options.build?.parser ?? defaultParser;
@@ -2202,6 +2362,15 @@ function createCsszyxPlugins(options = {}) {
2202
2362
  const transformMemoryCache = /* @__PURE__ */ new Map();
2203
2363
  const state = {
2204
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(),
2205
2374
  mangleMap: {},
2206
2375
  varMangleEntriesByFile: /* @__PURE__ */ new Map(),
2207
2376
  varMangleMap: Object.fromEntries(earlyGlobalVarAliasEntries),
@@ -2262,7 +2431,7 @@ function createCsszyxPlugins(options = {}) {
2262
2431
  return result;
2263
2432
  }
2264
2433
  function isHardIgnored(id) {
2265
- return id.includes("node_modules") || id.includes("/packages/") || id.includes(".next") && !id.includes("static");
2434
+ return isHardIgnoredPath(id, compilePackages);
2266
2435
  }
2267
2436
  function shouldProcessSource(id) {
2268
2437
  return !isHardIgnored(id) && !isUserExcluded(id) && isUserIncluded(id) && (/\.[tj]sx?(\?.*)?$/.test(id) || id.endsWith(".vue") || id.endsWith(".svelte"));
@@ -2468,12 +2637,19 @@ function createCsszyxPlugins(options = {}) {
2468
2637
  maxEntries: TRANSFORM_CACHE_MAX_ENTRIES
2469
2638
  });
2470
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
+ }
2471
2647
  function writeSafelistFile(classes) {
2472
2648
  if (classes.size === 0) {
2473
2649
  return;
2474
2650
  }
2475
2651
  const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME);
2476
- const classList = Array.from(classes).join(" ");
2652
+ const classList = escapeHtmlAttribute(Array.from(classes).join(" "));
2477
2653
  const content = `<!-- Auto-generated by csszyx \u2014 DO NOT EDIT -->
2478
2654
  <!-- Tailwind CSS scans this file for class name detection -->
2479
2655
  <div class="${classList}"><div class="${classList}">x</div><div class="${classList}">x</div></div>
@@ -2493,6 +2669,20 @@ function createCsszyxPlugins(options = {}) {
2493
2669
  } catch {
2494
2670
  }
2495
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
+ }
2496
2686
  function prescanAndWriteClasses() {
2497
2687
  const prescanStarted = performance.now();
2498
2688
  const discoveredClasses = /* @__PURE__ */ new Set();
@@ -2513,6 +2703,7 @@ function createCsszyxPlugins(options = {}) {
2513
2703
  } else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
2514
2704
  const filePath = path.join(dir, entry.name);
2515
2705
  if (!shouldProcessSource(filePath)) {
2706
+ recordPackagesSkipIfSz(filePath);
2516
2707
  continue;
2517
2708
  }
2518
2709
  let content;
@@ -2536,7 +2727,8 @@ function createCsszyxPlugins(options = {}) {
2536
2727
  collectPrescanResult(result, filePath, discoveredClasses, rawDiscoveredClasses);
2537
2728
  }
2538
2729
  for (const cls of discoveredClasses) {
2539
- state.classes.add(cls);
2730
+ addSafelistClass(cls);
2731
+ state.ownedClasses.add(cls);
2540
2732
  }
2541
2733
  const safelistClasses = /* @__PURE__ */ new Set([...discoveredClasses, ...rawDiscoveredClasses]);
2542
2734
  writeSafelistFile(safelistClasses);
@@ -2624,7 +2816,7 @@ function createCsszyxPlugins(options = {}) {
2624
2816
  for (const match of code.matchAll(classPattern)) {
2625
2817
  const classes = match[1].split(/\s+/).filter(Boolean);
2626
2818
  for (const cls of classes) {
2627
- state.classes.add(cls);
2819
+ addSafelistClass(cls);
2628
2820
  }
2629
2821
  }
2630
2822
  }
@@ -2646,13 +2838,13 @@ function createCsszyxPlugins(options = {}) {
2646
2838
  const str = strMatch[1] || strMatch[2];
2647
2839
  const classes = str.split(/\s+/).filter(Boolean);
2648
2840
  for (const cls of classes) {
2649
- state.classes.add(cls);
2841
+ addSafelistClass(cls);
2650
2842
  }
2651
2843
  }
2652
2844
  }
2653
2845
  }
2654
2846
  function finalizeMangleMap() {
2655
- const sortedClasses = Array.from(state.classes);
2847
+ const sortedClasses = Array.from(state.ownedClasses);
2656
2848
  const newMap = {};
2657
2849
  for (let i = 0; i < sortedClasses.length; i++) {
2658
2850
  newMap[sortedClasses[i]] = encode(i);
@@ -2737,7 +2929,7 @@ function createCsszyxPlugins(options = {}) {
2737
2929
  },
2738
2930
  /**
2739
2931
  * Filters files for the pre-transform phase — source files plus CSS files.
2740
- * 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.
2741
2933
  * @param id - the file path to check for inclusion
2742
2934
  * @returns true if the file should be transformed, false otherwise
2743
2935
  */
@@ -2749,7 +2941,7 @@ function createCsszyxPlugins(options = {}) {
2749
2941
  },
2750
2942
  /**
2751
2943
  * Core transform: detects sz prop, compiles to className, injects runtime, collects classes.
2752
- * 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.
2753
2945
  * @param code - the source code to transform
2754
2946
  * @param id - the file path of the module being transformed
2755
2947
  * @returns transformed code with source map, or null if no changes were made
@@ -2765,24 +2957,19 @@ function createCsszyxPlugins(options = {}) {
2765
2957
  assertNoRSCBoundaryViolation(code, id);
2766
2958
  }
2767
2959
  if (/\.css(\?.*)?$/.test(id)) {
2768
- const hasTailwindImport = code.includes('@import "tailwindcss') || code.includes("@import 'tailwindcss");
2769
- if (hasTailwindImport && state.classes.size > 0) {
2770
- const candidates = Array.from(state.classes).filter((c) => c.length >= 2 && /^[a-z]/.test(c)).join(" ");
2771
- if (candidates) {
2772
- const safelistPath = path.join(state.rootDir, SAFELIST_FILENAME).replace(/\\/g, "/");
2773
- const cssDir = path.dirname(id).replace(/\\/g, "/");
2774
- let relPath = path.posix.relative(cssDir, safelistPath);
2775
- if (!relPath.startsWith(".")) {
2776
- relPath = `./${relPath}`;
2777
- }
2778
- const sourceDirective = `@source "${relPath}";
2779
- `;
2780
- const transformed2 = code.replace(
2781
- /(@import\s+["']tailwindcss[^"']*["'];)/,
2782
- `$1
2783
- ${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
2784
2970
  );
2785
- if (transformed2 !== code) {
2971
+ const transformed2 = appendTailwindSourceDirective(code, relPath);
2972
+ if (transformed2 !== null) {
2786
2973
  return { code: transformed2, map: null };
2787
2974
  }
2788
2975
  }
@@ -2825,8 +3012,15 @@ ${sourceDirective}`
2825
3012
  szClasses = result.classes;
2826
3013
  recordFileVarMangleEntries(state, id, cssVariableEntries(result));
2827
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
+ }
2828
3021
  if (result.diagnostics.length > 0 && process.env.NODE_ENV !== "production") {
2829
3022
  for (const msg of result.diagnostics) {
3023
+ if (msg.includes("unresolvable sz spread")) continue;
2830
3024
  this.warn(`[csszyx] ${id}
2831
3025
  ${msg}`);
2832
3026
  }
@@ -2894,7 +3088,8 @@ ${sourceDirective}`
2894
3088
  if (transformed || transformedCode.includes("class=") || transformedCode.includes("className=")) {
2895
3089
  if (szClasses !== void 0) {
2896
3090
  for (const cls of szClasses) {
2897
- state.classes.add(cls);
3091
+ addSafelistClass(cls);
3092
+ state.ownedClasses.add(cls);
2898
3093
  }
2899
3094
  } else {
2900
3095
  extractClasses(transformedCode);
@@ -2907,6 +3102,36 @@ ${sourceDirective}`
2907
3102
  buildEnd() {
2908
3103
  finalizeMangleMap();
2909
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();
2910
3135
  if (manglingEnabled && Object.keys(state.mangleMap).length > 0) {
2911
3136
  globalThis.__csszyx_ssr_mangle_map = state.mangleMap;
2912
3137
  }
@@ -3006,7 +3231,8 @@ ${sourceDirective}`
3006
3231
  const sizeBefore = state.classes.size;
3007
3232
  recordGlobalVarSourceFile(state, ctx.file, fileContent);
3008
3233
  for (const cls of result.classes) {
3009
- state.classes.add(cls);
3234
+ addSafelistClass(cls);
3235
+ state.ownedClasses.add(cls);
3010
3236
  }
3011
3237
  recordFileVarMangleEntries(state, ctx.file, cssVariableEntries(result));
3012
3238
  recordFileCSSVariableMetrics(state, ctx.file, result.code);
@@ -3345,4 +3571,4 @@ const esbuildPlugin = (options = {}) => {
3345
3571
  };
3346
3572
  };
3347
3573
 
3348
- 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 };
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { hostname } from 'node:os';
5
5
  import lockfile from 'proper-lockfile';
6
+ import { e as escapeHtmlAttribute } from './unplugin.3UumZ5gn.mjs';
6
7
 
7
8
  const DEFAULT_RENAME_RETRIES = 5;
8
9
  const DEFAULT_RENAME_RETRY_DELAY_MS = 10;
@@ -235,9 +236,6 @@ function renderTailwindSourceHtml(classNames) {
235
236
  return `${classNames.map((className) => `<div class="${escapeHtmlAttribute(className)}"></div>`).join("\n")}
236
237
  `;
237
238
  }
238
- function escapeHtmlAttribute(value) {
239
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
240
- }
241
239
  function createLockMetadata(options) {
242
240
  const now = new Date(options.now ?? Date.now()).toISOString();
243
241
  const pid = options.pid ?? process.pid;
@@ -5,6 +5,7 @@ const fs = require('node:fs');
5
5
  const node_crypto = require('node:crypto');
6
6
  const node_os = require('node:os');
7
7
  const lockfile = require('proper-lockfile');
8
+ const htmlEscape = require('./unplugin.DoBHTRra.cjs');
8
9
 
9
10
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
10
11
 
@@ -252,12 +253,9 @@ function renderTailwindSourceHtml(classNames) {
252
253
  if (classNames.length === 0) {
253
254
  return "<!-- csszyx Next safelist: empty -->\n";
254
255
  }
255
- return `${classNames.map((className) => `<div class="${escapeHtmlAttribute(className)}"></div>`).join("\n")}
256
+ return `${classNames.map((className) => `<div class="${htmlEscape.escapeHtmlAttribute(className)}"></div>`).join("\n")}
256
257
  `;
257
258
  }
258
- function escapeHtmlAttribute(value) {
259
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
260
- }
261
259
  function createLockMetadata(options) {
262
260
  const now = new Date(options.now ?? Date.now()).toISOString();
263
261
  const pid = options.pid ?? process.pid;