@csszyx/unplugin 0.15.2 → 0.16.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.
@@ -1732,22 +1732,22 @@ function expandFilePatterns(rootDir, patterns) {
1732
1732
  return safelistSource.sortStrings(files);
1733
1733
  }
1734
1734
 
1735
- const BACKSLASH = String.fromCodePoint(92);
1735
+ const BACKSLASH$1 = String.fromCodePoint(92);
1736
1736
  function unicodeEscape(hexadecimal) {
1737
- return `${BACKSLASH}u${hexadecimal}`;
1737
+ return `${BACKSLASH$1}u${hexadecimal}`;
1738
1738
  }
1739
1739
  function replaceEveryLiteral(value, search, replacement) {
1740
1740
  return value.replaceAll(search, () => replacement);
1741
1741
  }
1742
1742
  function escapeSingleQuotedString(value) {
1743
- let escaped = replaceEveryLiteral(value, BACKSLASH, BACKSLASH.repeat(2));
1744
- escaped = replaceEveryLiteral(escaped, "'", `${BACKSLASH}'`);
1745
- escaped = replaceEveryLiteral(escaped, "\r", `${BACKSLASH}r`);
1746
- return replaceEveryLiteral(escaped, "\n", `${BACKSLASH}n`);
1743
+ let escaped = replaceEveryLiteral(value, BACKSLASH$1, BACKSLASH$1.repeat(2));
1744
+ escaped = replaceEveryLiteral(escaped, "'", `${BACKSLASH$1}'`);
1745
+ escaped = replaceEveryLiteral(escaped, "\r", `${BACKSLASH$1}r`);
1746
+ return replaceEveryLiteral(escaped, "\n", `${BACKSLASH$1}n`);
1747
1747
  }
1748
1748
  function escapeDoubleQuotedString(value) {
1749
- const escaped = replaceEveryLiteral(value, BACKSLASH, BACKSLASH.repeat(2));
1750
- return replaceEveryLiteral(escaped, '"', `${BACKSLASH}"`);
1749
+ const escaped = replaceEveryLiteral(value, BACKSLASH$1, BACKSLASH$1.repeat(2));
1750
+ return replaceEveryLiteral(escaped, '"', `${BACKSLASH$1}"`);
1751
1751
  }
1752
1752
 
1753
1753
  const LINE_SEPARATOR$1 = String.fromCodePoint(8232);
@@ -1936,6 +1936,81 @@ function removedMangleMapDeliveryMessage() {
1936
1936
  return "[csszyx] production.mangleMapDelivery has been removed and is ignored. The runtime mangle map is always registered from inside the JS bundle now, on every lane, so the built HTML never carries an executable inline <script> and a strict script-src 'self' policy needs no exception. Delete the option. (`window.__csszyx` is now opt-in through `production.mangleDebugGlobal`.)";
1937
1937
  }
1938
1938
 
1939
+ function utilityStart(className) {
1940
+ let depth = 0;
1941
+ let start = 0;
1942
+ for (let index = 0; index < className.length; index += 1) {
1943
+ const char = className[index];
1944
+ if (char === "[") depth += 1;
1945
+ else if (char === "]") depth = Math.max(0, depth - 1);
1946
+ else if (char === ":" && depth === 0) start = index + 1;
1947
+ }
1948
+ return start;
1949
+ }
1950
+ function compileManglePreserve(entries) {
1951
+ const list = entries ?? [];
1952
+ list.forEach((entry, index) => {
1953
+ if (typeof entry !== "string" || entry.length === 0) {
1954
+ throw new TypeError(
1955
+ `[csszyx] production.manglePreserve[${index}] must be a non-empty string (an exact class name, or a prefix ending in \`*\`); got ${describe(entry)}.`
1956
+ );
1957
+ }
1958
+ if (entry === "*") {
1959
+ throw new TypeError(
1960
+ "[csszyx] production.manglePreserve must not contain a lone `*`: it would keep every class and silently turn `production.mangle` into a no-op. Name a prefix (`bg-tag-*`) or set `production.mangle: false`."
1961
+ );
1962
+ }
1963
+ });
1964
+ const compiled = list.map((value) => ({
1965
+ value,
1966
+ prefix: value.endsWith("*") ? value.slice(0, -1) : void 0
1967
+ }));
1968
+ const keeps = (entry, className, utilityStartIndex) => entry.prefix === void 0 ? className === entry.value || className.length - utilityStartIndex === entry.value.length && className.startsWith(entry.value, utilityStartIndex) : className.startsWith(entry.prefix) || className.startsWith(entry.prefix, utilityStartIndex);
1969
+ const test = (className) => {
1970
+ if (list.length === 0) return false;
1971
+ const start = utilityStart(className);
1972
+ return compiled.some((entry) => keeps(entry, className, start));
1973
+ };
1974
+ return {
1975
+ entries: list,
1976
+ test,
1977
+ unmatched(census) {
1978
+ if (list.length === 0) return [];
1979
+ const matched = list.map(() => false);
1980
+ let remaining = list.length;
1981
+ for (const className of census) {
1982
+ const start = utilityStart(className);
1983
+ for (let index = 0; index < list.length; index += 1) {
1984
+ if (matched[index] || !keeps(compiled[index], className, start)) continue;
1985
+ matched[index] = true;
1986
+ remaining -= 1;
1987
+ }
1988
+ if (remaining === 0) break;
1989
+ }
1990
+ return list.filter((_, index) => !matched[index]);
1991
+ }
1992
+ };
1993
+ }
1994
+ function describe(value) {
1995
+ if (typeof value === "string") return "an empty string";
1996
+ if (value instanceof RegExp) return `a RegExp (${String(value)})`;
1997
+ return `${typeof value} ${JSON.stringify(value) ?? String(value)}`;
1998
+ }
1999
+ function manglePreserveNoMatchMessage(entries) {
2000
+ const sample = entries.slice(0, 8).join(", ");
2001
+ const noun = entries.length === 1 ? "entry" : "entries";
2002
+ return `[csszyx] production.manglePreserve: ${entries.length} ${noun} matched no csszyx class in this build (${sample}) \u2014 nothing was preserved for them. Check the spelling against the class census; an entry keeps a class only when its name is exact, or when it ends in \`*\` and the class starts with the rest.`;
2003
+ }
2004
+ const TOKEN_CHARACTERS = /^[0-9a-z]+$/i;
2005
+ function mangleExcludeNeverTokenEntries(entries) {
2006
+ return [...entries].filter((entry) => !TOKEN_CHARACTERS.test(entry));
2007
+ }
2008
+ function mangleExcludeNeverTokenMessage(entries) {
2009
+ const sample = entries.slice(0, 8).join(", ");
2010
+ const noun = entries.length === 1 ? "name" : "names";
2011
+ return `[csszyx] production.mangleExclude: ${entries.length} ${noun} can never be a mangle token (${sample}) \u2014 tokens are short base62 strings, so those entries do nothing. To keep a class from being renamed, list it in \`production.manglePreserve\` instead.`;
2012
+ }
2013
+
1939
2014
  const MANGLE_RUNTIME_FILE = "mangle-runtime.mjs";
1940
2015
  function ensureMangleRuntimeFile(outputDir, globalVarAliasPrefix, exposeDebugGlobal) {
1941
2016
  const target = path__default.join(outputDir, MANGLE_RUNTIME_FILE);
@@ -2185,9 +2260,9 @@ let _hasWarnedTsConfig = false;
2185
2260
  let _hasWarnedTransformCacheVersion = false;
2186
2261
  let _hasWarnedNativeFallback = false;
2187
2262
  const _loggedActiveParsers = /* @__PURE__ */ new Set();
2188
- const requireFromHere = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.dz3AdzsO.cjs', document.baseURI).href)));
2263
+ const requireFromHere = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.D3yBoTHP.cjs', document.baseURI).href)));
2189
2264
  const PLUGIN_VERSION = findPackageVersionFromFile(
2190
- node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.dz3AdzsO.cjs', document.baseURI).href))),
2265
+ node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.D3yBoTHP.cjs', document.baseURI).href))),
2191
2266
  UNKNOWN_PACKAGE_VERSION
2192
2267
  );
2193
2268
  const COMPILER_VERSION = findPackageVersionFromModule("@csszyx/compiler", UNKNOWN_PACKAGE_VERSION);
@@ -2229,39 +2304,136 @@ function shouldWarnMissingTailwindEntry(ownedClassCount, sawTailwindEntry, sawAn
2229
2304
  function missingTailwindEntryMessage(ownedClassCount) {
2230
2305
  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.`;
2231
2306
  }
2232
- function collectMangleHybridHazards(mangleMap, mangledSources, externalClasses) {
2307
+ function asciiLower(text) {
2308
+ return text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
2309
+ }
2310
+ function attributeSelectorPredicates(operator, value) {
2311
+ const eq = (v) => (name) => name === v;
2312
+ const startsWith = (v) => (name) => name.startsWith(v);
2313
+ const endsWith = (v) => (name) => name.endsWith(v);
2314
+ const includes = (v) => (name) => name.includes(v);
2315
+ const dashPrefix = (v) => (name) => name === v || name.startsWith(`${v}-`);
2316
+ const segments = value.split(/[\t\n\f\r ]+/);
2317
+ if (segments.length === 1) {
2318
+ switch (operator) {
2319
+ case "=":
2320
+ case "~=":
2321
+ return [eq(value)];
2322
+ case "|=":
2323
+ return [dashPrefix(value)];
2324
+ case "^=":
2325
+ return [startsWith(value)];
2326
+ case "$=":
2327
+ return [endsWith(value)];
2328
+ case "*=":
2329
+ return [includes(value)];
2330
+ default:
2331
+ return [];
2332
+ }
2333
+ }
2334
+ if (operator === "~=") return [];
2335
+ const first = segments[0];
2336
+ const last = segments[segments.length - 1];
2337
+ const predicates = [];
2338
+ if (first.length > 0) {
2339
+ predicates.push(operator === "$=" || operator === "*=" ? endsWith(first) : eq(first));
2340
+ }
2341
+ for (const middle of segments.slice(1, -1)) predicates.push(eq(middle));
2342
+ if (last.length > 0) {
2343
+ if (operator === "^=" || operator === "*=") predicates.push(startsWith(last));
2344
+ else if (operator === "|=") predicates.push(dashPrefix(last));
2345
+ else predicates.push(eq(last));
2346
+ }
2347
+ return predicates;
2348
+ }
2349
+ function renderClassAttributeSelector(selector) {
2350
+ const flag = selector.insensitive ? " i" : "";
2351
+ return `[class${selector.operator}"${selector.value}"${flag}]`;
2352
+ }
2353
+ function selectorHazardFor(mangleMap, selector) {
2354
+ const value = selector.insensitive ? asciiLower(selector.value) : selector.value;
2355
+ const predicates = attributeSelectorPredicates(selector.operator, value);
2356
+ if (predicates.length === 0) return null;
2357
+ const fold = selector.insensitive ? asciiLower : (name) => name;
2358
+ const matches = (name) => {
2359
+ const folded = fold(name);
2360
+ return predicates.some((predicate) => predicate(folded));
2361
+ };
2362
+ const renamed = compiler.sortStrings(Object.keys(mangleMap).filter(matches));
2363
+ const matchedTokens = compiler.sortStrings(Object.values(mangleMap).filter(matches));
2364
+ if (renamed.length === 0 && matchedTokens.length === 0) return null;
2365
+ return {
2366
+ selector: renderClassAttributeSelector(selector),
2367
+ value: selector.value,
2368
+ renamed,
2369
+ matchedTokens
2370
+ };
2371
+ }
2372
+ function collectMangleHybridHazards(mangleMap, mangledSources, externalClasses, attributeSelectors = []) {
2233
2373
  const tokenValues = new Set(Object.values(mangleMap));
2234
2374
  const collisions = compiler.sortStrings([...tokenValues].filter((token) => externalClasses.has(token)));
2235
2375
  const orphans = compiler.sortStrings(
2236
2376
  Object.keys(mangleMap).filter((source) => !mangledSources.has(source))
2237
2377
  );
2238
- return { collisions, orphans };
2378
+ const hazards = /* @__PURE__ */ new Map();
2379
+ for (const selector of attributeSelectors) {
2380
+ const hazard = selectorHazardFor(mangleMap, selector);
2381
+ if (hazard !== null) hazards.set(hazard.selector, hazard);
2382
+ }
2383
+ const selectorMatches = compiler.sortStrings([...hazards.keys()]).map(
2384
+ (key) => hazards.get(key)
2385
+ );
2386
+ return { collisions, orphans, selectorMatches };
2387
+ }
2388
+ const BACKSLASH = String.fromCodePoint(92);
2389
+ const SINGLE_QUOTE = String.fromCodePoint(39);
2390
+ function quoteForConfig(entry) {
2391
+ const escaped = entry.replaceAll(BACKSLASH, BACKSLASH + BACKSLASH).replaceAll(SINGLE_QUOTE, BACKSLASH + SINGLE_QUOTE);
2392
+ return SINGLE_QUOTE + escaped + SINGLE_QUOTE;
2393
+ }
2394
+ function preserveEntriesFor(hazard) {
2395
+ const quote = quoteForConfig;
2396
+ const { value } = hazard;
2397
+ if (hazard.renamed.every((name) => name === value)) return [quote(value)];
2398
+ if (hazard.renamed.every((name) => name.startsWith(value))) return [quote(`${value}*`)];
2399
+ return hazard.renamed.map(quote);
2239
2400
  }
2240
2401
  function mangleHybridHazardMessage(hazards) {
2241
- const { collisions, orphans } = hazards;
2242
- if (collisions.length === 0 && orphans.length === 0) {
2402
+ const { collisions, orphans, selectorMatches = [] } = hazards;
2403
+ if (collisions.length === 0 && orphans.length === 0 && selectorMatches.length === 0) {
2243
2404
  return null;
2244
2405
  }
2406
+ const broken = selectorMatches.filter((hazard) => hazard.renamed.length > 0);
2407
+ const newlyMatched = selectorMatches.filter((hazard) => hazard.matchedTokens.length > 0);
2245
2408
  const parts = ["[csszyx] production mangle found hybrid hazards:"];
2246
2409
  if (collisions.length > 0) {
2247
2410
  const sample = collisions.slice(0, 8).join(", ");
2248
2411
  parts.push(
2249
- ` ${collisions.length} mangled token(s) collide with class names in non-csszyx CSS (e.g. ${sample}) \u2014 those tokens will cross-contaminate external ".${collisions[0]}" elements.`
2412
+ ` ${collisions.length} mangled token(s) collide with class names in non-csszyx CSS (e.g. ${sample}) \u2014 those tokens will cross-contaminate external ".${collisions[0]}" elements.`,
2413
+ " HOTFIX: pass `production: { mangle: false }` to the csszyx plugin to ship now. THEN fix it: if these short names are in your OWN CSS, rename them to something specific (e.g. `.x` \u2192 `.resize-handle-x`) \u2014 short/common names also clash on specificity with other libraries. Only for names in a third-party stylesheet you cannot edit, list them in `production.mangleExclude` instead. Run `npx @csszyx/cli scan-collisions` to find every offending name."
2250
2414
  );
2251
2415
  }
2252
2416
  if (orphans.length > 0) {
2253
2417
  const sample = orphans.slice(0, 8).join(", ");
2254
2418
  parts.push(
2255
- ` ${orphans.length} mangled class(es) have no emitted CSS rule (e.g. ${sample}) \u2014 those elements lose styling.`
2419
+ ` ${orphans.length} mangled class(es) have no emitted CSS rule (e.g. ${sample}) \u2014 those elements lose styling.`,
2420
+ " Those classes are csszyx-owned but no CSS was emitted for them (e.g. a separate Tailwind plugin owns the utility CSS, or the class is not a real utility). Ensure that CSS is generated, or pass `production: { mangle: false }` to the csszyx plugin until the pipelines are reconciled."
2256
2421
  );
2257
2422
  }
2258
- if (collisions.length > 0) {
2423
+ if (broken.length > 0) {
2424
+ const sample = broken.slice(0, 3).map((hazard) => `${hazard.selector} \u2192 ${hazard.renamed.slice(0, 4).join(", ")}`).join("; ");
2425
+ const entries = [...new Set(broken.flatMap(preserveEntriesFor))].join(", ");
2259
2426
  parts.push(
2260
- " HOTFIX: pass `production: { mangle: false }` to the csszyx plugin to ship now. THEN fix it: if these short names are in your OWN CSS, rename them to something specific (e.g. `.x` \u2192 `.resize-handle-x`) \u2014 short/common names also clash on specificity with other libraries. Only for names in a third-party stylesheet you cannot edit, list them in `production.mangleExclude` instead. Run `npx @csszyx/cli scan-collisions` to find every offending name."
2427
+ ` ${broken.length} attribute selector(s) match class names by text (e.g. ${sample}) \u2014 those rules stop matching once the classes are renamed, so the elements lose those styles.`,
2428
+ ` Keep those classes readable with production.manglePreserve: [${entries}] (paste-ready), or key the rule off a data attribute, which mangling never touches. production.mangleExclude cannot help here: it reserves token names and does not keep a class from being renamed.`
2261
2429
  );
2262
- } else {
2430
+ }
2431
+ if (newlyMatched.length > 0) {
2432
+ const sample = newlyMatched.slice(0, 3).map((hazard) => `${hazard.selector} \u2192 ${hazard.matchedTokens.slice(0, 4).join(", ")}`).join("; ");
2433
+ const tokens = [...new Set(newlyMatched.flatMap((hazard) => hazard.matchedTokens))].map(quoteForConfig).join(", ");
2263
2434
  parts.push(
2264
- " Those classes are csszyx-owned but no CSS was emitted for them (e.g. a separate Tailwind plugin owns the utility CSS, or the class is not a real utility). Ensure that CSS is generated, or pass `production: { mangle: false }` to the csszyx plugin until the pipelines are reconciled."
2435
+ ` ${newlyMatched.length} attribute selector(s) would start matching mangled tokens instead (e.g. ${sample}).`,
2436
+ ` Reserve those token names with production.mangleExclude: [${tokens}] (paste-ready) so no class is renamed to one of them, or key the rule off a data attribute, which mangling never touches; a prefix or substring selector goes on matching other tokens, so the data attribute is the durable fix.`
2265
2437
  );
2266
2438
  }
2267
2439
  return parts.join("");
@@ -2333,6 +2505,9 @@ function shouldEmitWarning(quiet, devOnly, isProduction) {
2333
2505
  function shouldEmitMissingCssFallback(quiet, message) {
2334
2506
  return resolveQuietMode(quiet) !== "all" && compiler.szFallbackConsequenceOf(message) === "missing-css";
2335
2507
  }
2508
+ function shouldHoldAdvisories(quiet, serving, nodeEnv) {
2509
+ return quiet !== "off" || !serving && nodeEnv === "production";
2510
+ }
2336
2511
  function emitMissingCssFallback(quiet, message, id, emit) {
2337
2512
  if (shouldEmitMissingCssFallback(quiet, message)) emit(`[csszyx] ${id}
2338
2513
  ${message}`);
@@ -3162,12 +3337,15 @@ ${errors.join("\n")}`
3162
3337
  function createCsszyxPlugins(options = {}) {
3163
3338
  assertGlobalVarMangleConfig(options);
3164
3339
  let manglingEnabled = options.production?.mangle === true;
3340
+ let serving = false;
3165
3341
  const importedStaticSzEnabled = options.build?.importedStaticSz ?? types.DEFAULT_IMPORTED_STATIC_SZ;
3166
3342
  const szvCrossModuleRegistry = /* @__PURE__ */ new Map();
3167
3343
  const szvCrossModuleForwards = /* @__PURE__ */ new Map();
3168
3344
  const szObjectProvidersExamined = /* @__PURE__ */ new Set();
3169
3345
  let specifierAliases = [];
3170
3346
  const mangleReserved = new Set(options.production?.mangleExclude ?? []);
3347
+ const manglePreserve = compileManglePreserve(options.production?.manglePreserve);
3348
+ let manglePreserveNoMatchWarned = false;
3171
3349
  let mangleMapFrozen = false;
3172
3350
  let prescanSawServerModule = false;
3173
3351
  let skipRscRecords = false;
@@ -3178,6 +3356,7 @@ function createCsszyxPlugins(options = {}) {
3178
3356
  let projectCssFiles = [];
3179
3357
  const transformMangledSources = /* @__PURE__ */ new Set();
3180
3358
  const transformExternalClasses = /* @__PURE__ */ new Set();
3359
+ const transformAttributeSelectors = /* @__PURE__ */ new Map();
3181
3360
  if (options.production?.mangleMapDelivery !== void 0) {
3182
3361
  console.warn(removedMangleMapDeliveryMessage());
3183
3362
  }
@@ -3207,6 +3386,10 @@ function createCsszyxPlugins(options = {}) {
3207
3386
  if (unknownConfigKeys.length > 0) {
3208
3387
  emitWarning(unknownConfigKeysMessage(unknownConfigKeys));
3209
3388
  }
3389
+ const excludeNeverTokens = mangleExcludeNeverTokenEntries(mangleReserved);
3390
+ if (excludeNeverTokens.length > 0) {
3391
+ emitWarning(mangleExcludeNeverTokenMessage(excludeNeverTokens));
3392
+ }
3210
3393
  function emitWarning(message, opts = {}) {
3211
3394
  if (shouldEmitWarning(quiet, opts.devOnly ?? false, process.env.NODE_ENV === "production")) {
3212
3395
  console.warn(message);
@@ -4067,10 +4250,15 @@ function createCsszyxPlugins(options = {}) {
4067
4250
  ...state.authoredClasses,
4068
4251
  ...state.ownedClasses
4069
4252
  ]);
4070
- return allocateMangleTokens(
4071
- mangleEligibleClasses(state.ownedClasses, state.authoredClasses),
4072
- forbiddenTokens
4253
+ const eligible = mangleEligibleClasses(state.ownedClasses, state.authoredClasses).filter(
4254
+ (className) => !manglePreserve.test(className)
4073
4255
  );
4256
+ if (!manglePreserveNoMatchWarned && manglingEnabled && state.ownedClasses.size > 0) {
4257
+ manglePreserveNoMatchWarned = true;
4258
+ const unmatched = manglePreserve.unmatched(state.ownedClasses);
4259
+ if (unmatched.length > 0) emitWarning(manglePreserveNoMatchMessage(unmatched));
4260
+ }
4261
+ return allocateMangleTokens(eligible, forbiddenTokens);
4074
4262
  }
4075
4263
  function freezeMangleMap() {
4076
4264
  state.mangleMap = computeMangleMap();
@@ -4223,6 +4411,38 @@ function createCsszyxPlugins(options = {}) {
4223
4411
  }
4224
4412
  return modules;
4225
4413
  }
4414
+ function handleHotFile(pass, ctx) {
4415
+ const { isClientPass } = pass;
4416
+ if (isClientPass) {
4417
+ prescanResultHandoff.clear();
4418
+ }
4419
+ const scanCss = options.build?.scanCss;
4420
+ const reloadThemeGroupsModule = () => {
4421
+ const themeGroupsModule = ctx.server.moduleGraph.getModuleById(
4422
+ themeGroupsFile.RESOLVED_THEME_GROUPS_VIRTUAL_ID
4423
+ );
4424
+ if (themeGroupsModule) {
4425
+ ctx.server.moduleGraph.invalidateModule(themeGroupsModule);
4426
+ }
4427
+ };
4428
+ if (isClientPass && ctx.file.endsWith(".css")) {
4429
+ const root = state.rootDir;
4430
+ const before = themeGroupsFile.createThemeGroupsModule(themeGroupTokens());
4431
+ if (scanCss && matchesAnyPattern(ctx.file, scanCss, root)) {
4432
+ state.scanCssTheme = runThemeScan(root, scanCss) ?? state.scanCssTheme;
4433
+ }
4434
+ runAutoThemeScan(root);
4435
+ reloadThemeGroupsModule();
4436
+ if (themeGroupsFile.createThemeGroupsModule(themeGroupTokens()) !== before) {
4437
+ ctx.server.ws.send({ type: "full-reload" });
4438
+ }
4439
+ }
4440
+ if (safelistSource.normalizePathSeparators(ctx.file) === safelistSource.normalizePathSeparators(path__namespace.join(state.rootDir, SAFELIST_FILENAME))) {
4441
+ return tailwindEntryModules(pass.moduleGraph());
4442
+ }
4443
+ if (isClientPass) discoverHotFileClasses(ctx.file, ctx.server.watcher);
4444
+ return void 0;
4445
+ }
4226
4446
  function transformTailwindCssEntry(code, id) {
4227
4447
  state.sawAnyCss = true;
4228
4448
  const [cssFile] = id.split("?");
@@ -4258,7 +4478,7 @@ function createCsszyxPlugins(options = {}) {
4258
4478
  }
4259
4479
  const advisories = result.diagnostics.filter(isAdvisoryDiagnostic);
4260
4480
  if (advisories.length === 0) return;
4261
- if (quiet !== "off" || process.env.NODE_ENV === "production") {
4481
+ if (shouldHoldAdvisories(quiet, serving, process.env.NODE_ENV)) {
4262
4482
  state.suppressedAdvisories += advisories.length;
4263
4483
  return;
4264
4484
  }
@@ -4677,6 +4897,7 @@ ${transformedCode}`;
4677
4897
  specifierAliases = transformCache.collectSpecifierAliases(root, config.resolve?.alias);
4678
4898
  if (config.command === "serve") {
4679
4899
  manglingEnabled = false;
4900
+ serving = true;
4680
4901
  }
4681
4902
  if (manglingEnabled && config.build?.watch) {
4682
4903
  manglingEnabled = false;
@@ -4724,38 +4945,39 @@ ${transformedCode}`;
4724
4945
  * which leaves Vite's own handling in place.
4725
4946
  */
4726
4947
  hotUpdate(ctx) {
4727
- const isClientPass = (this.environment?.name ?? "client") === "client";
4728
- if (isClientPass) {
4729
- prescanResultHandoff.clear();
4730
- }
4731
- const scanCss = options.build?.scanCss;
4732
- const reloadThemeGroupsModule = () => {
4733
- const themeGroupsModule = ctx.server.moduleGraph.getModuleById(
4734
- themeGroupsFile.RESOLVED_THEME_GROUPS_VIRTUAL_ID
4735
- );
4736
- if (themeGroupsModule) {
4737
- ctx.server.moduleGraph.invalidateModule(themeGroupsModule);
4738
- }
4739
- };
4740
- if (isClientPass && ctx.file.endsWith(".css")) {
4741
- const root = ctx.server.config.root || process.cwd();
4742
- const before = themeGroupsFile.createThemeGroupsModule(themeGroupTokens());
4743
- if (scanCss && matchesAnyPattern(ctx.file, scanCss, root)) {
4744
- state.scanCssTheme = runThemeScan(root, scanCss) ?? state.scanCssTheme;
4745
- }
4746
- runAutoThemeScan(root);
4747
- reloadThemeGroupsModule();
4748
- if (themeGroupsFile.createThemeGroupsModule(themeGroupTokens()) !== before) {
4749
- ctx.server.ws.send({ type: "full-reload" });
4750
- }
4751
- }
4752
- if (safelistSource.normalizePathSeparators(ctx.file) === safelistSource.normalizePathSeparators(path__namespace.join(state.rootDir, SAFELIST_FILENAME))) {
4753
- return tailwindEntryModules(
4754
- this.environment?.moduleGraph ?? ctx.server.environments.client.moduleGraph
4755
- );
4756
- }
4757
- if (isClientPass) discoverHotFileClasses(ctx.file, ctx.server.watcher);
4758
- return void 0;
4948
+ return handleHotFile(
4949
+ {
4950
+ isClientPass: (this.environment?.name ?? "client") === "client",
4951
+ // `server.moduleGraph` holds the backward-compatible
4952
+ // nodes Vite mapped back for the legacy hook only,
4953
+ // so they are the wrong objects to hand this one —
4954
+ // TypeScript says so too.
4955
+ moduleGraph: () => this.environment?.moduleGraph ?? ctx.server.environments.client.moduleGraph
4956
+ },
4957
+ ctx
4958
+ );
4959
+ },
4960
+ /**
4961
+ * The hook Vite 5 calls. Vite 6 and later never run it while
4962
+ * `hotUpdate` is declared, so declaring both costs nothing
4963
+ * there and keeps a Vite 5 dev server compiling `sz` live.
4964
+ *
4965
+ * Vite 5 routes only a file CHANGE here, never a create, so a
4966
+ * project with no `sz` at prescan still sees its first `sz`
4967
+ * edit answered by `@tailwindcss/vite`'s full reload on that
4968
+ * generation — the behaviour it always had there. The hook
4969
+ * runs once, with no environment, so it is the client pass
4970
+ * and answers from the server-wide graph.
4971
+ *
4972
+ * @param ctx - HMR context: the changed file and the dev server.
4973
+ * @returns The modules a safelist write affects; undefined
4974
+ * otherwise.
4975
+ */
4976
+ handleHotUpdate(ctx) {
4977
+ return handleHotFile(
4978
+ { isClientPass: true, moduleGraph: () => ctx.server.moduleGraph },
4979
+ ctx
4980
+ );
4759
4981
  },
4760
4982
  transformIndexHtml: {
4761
4983
  order: "pre",
@@ -4844,7 +5066,7 @@ ${transformedCode}`;
4844
5066
  recordCodePair(sizeAccount, before, after);
4845
5067
  return after;
4846
5068
  }
4847
- function rewriteOutputCss(source, file, shouldMangle, mangledSources, externalClasses) {
5069
+ function rewriteOutputCss(source, file, shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4848
5070
  const css = rewriteCssWithValidatedGlobalVarPlan(
4849
5071
  source,
4850
5072
  file,
@@ -4858,6 +5080,9 @@ ${transformedCode}`;
4858
5080
  });
4859
5081
  for (const className of result.mangledClasses) mangledSources.add(className);
4860
5082
  for (const className of result.unmangledClasses) externalClasses.add(className);
5083
+ for (const selector of result.classAttributeSelectors) {
5084
+ attributeSelectors.set(cssMangler.classAttributeSelectorKey(selector), selector);
5085
+ }
4861
5086
  const mangled = result.transformedCount > 0 ? result.css : css;
4862
5087
  recordCssPair(sizeAccount, css, mangled);
4863
5088
  return mangled;
@@ -4866,10 +5091,12 @@ ${transformedCode}`;
4866
5091
  throw error;
4867
5092
  }
4868
5093
  }
4869
- function reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses) {
5094
+ function reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4870
5095
  if (!shouldMangle) return;
4871
5096
  const message = mangleHybridHazardMessage(
4872
- collectMangleHybridHazards(state.mangleMap, mangledSources, externalClasses)
5097
+ collectMangleHybridHazards(state.mangleMap, mangledSources, externalClasses, [
5098
+ ...attributeSelectors.values()
5099
+ ])
4873
5100
  );
4874
5101
  if (message) console.warn(message);
4875
5102
  }
@@ -4930,6 +5157,7 @@ ${transformedCode}`;
4930
5157
  }
4931
5158
  const mangledSources = /* @__PURE__ */ new Set();
4932
5159
  const externalClasses = /* @__PURE__ */ new Set();
5160
+ const attributeSelectors = /* @__PURE__ */ new Map();
4933
5161
  for (const file in assets) {
4934
5162
  const source = assets[file].source().toString();
4935
5163
  if (file.endsWith(".css")) {
@@ -4938,7 +5166,8 @@ ${transformedCode}`;
4938
5166
  file,
4939
5167
  shouldMangle,
4940
5168
  mangledSources,
4941
- externalClasses
5169
+ externalClasses,
5170
+ attributeSelectors
4942
5171
  );
4943
5172
  if (css !== source) {
4944
5173
  compilation.updateAsset(file, new compiler.webpack.sources.RawSource(css));
@@ -4947,10 +5176,15 @@ ${transformedCode}`;
4947
5176
  rewriteWebpackCodeAsset(file, source, shouldMangle, compilation, compiler);
4948
5177
  }
4949
5178
  }
4950
- reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
5179
+ reportOutputMangleHazards(
5180
+ shouldMangle,
5181
+ mangledSources,
5182
+ externalClasses,
5183
+ attributeSelectors
5184
+ );
4951
5185
  reportBuildSummary();
4952
5186
  }
4953
- function rewriteViteBundleEntry(chunk, file, shouldMangle, mangledSources, externalClasses) {
5187
+ function rewriteViteBundleEntry(chunk, file, shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4954
5188
  if (chunk.type !== "asset" || !chunk.fileName.endsWith(".css") || chunk.source === void 0) {
4955
5189
  return;
4956
5190
  }
@@ -4960,7 +5194,8 @@ ${transformedCode}`;
4960
5194
  file,
4961
5195
  shouldMangle,
4962
5196
  mangledSources,
4963
- externalClasses
5197
+ externalClasses,
5198
+ attributeSelectors
4964
5199
  );
4965
5200
  if (css !== originalCss) chunk.source = css;
4966
5201
  }
@@ -4971,7 +5206,8 @@ ${transformedCode}`;
4971
5206
  id,
4972
5207
  shouldMangle,
4973
5208
  transformMangledSources,
4974
- transformExternalClasses
5209
+ transformExternalClasses,
5210
+ transformAttributeSelectors
4975
5211
  );
4976
5212
  return css === code ? null : { code: css, map: null };
4977
5213
  }
@@ -4996,6 +5232,7 @@ ${transformedCode}`;
4996
5232
  }
4997
5233
  const mangledSources = new Set(transformMangledSources);
4998
5234
  const externalClasses = new Set(transformExternalClasses);
5235
+ const attributeSelectors = new Map(transformAttributeSelectors);
4999
5236
  if (!cssRewrittenInTransform) {
5000
5237
  for (const file in bundle) {
5001
5238
  rewriteViteBundleEntry(
@@ -5003,11 +5240,17 @@ ${transformedCode}`;
5003
5240
  file,
5004
5241
  shouldMangle,
5005
5242
  mangledSources,
5006
- externalClasses
5243
+ externalClasses,
5244
+ attributeSelectors
5007
5245
  );
5008
5246
  }
5009
5247
  }
5010
- reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
5248
+ reportOutputMangleHazards(
5249
+ shouldMangle,
5250
+ mangledSources,
5251
+ externalClasses,
5252
+ attributeSelectors
5253
+ );
5011
5254
  }
5012
5255
  const postPlugin = unplugin$1.createUnplugin(() => ({
5013
5256
  name: "csszyx:post",
@@ -5204,6 +5447,7 @@ exports.rollupPlugin = rollupPlugin;
5204
5447
  exports.scanGlobalVarCss = scanGlobalVarCss;
5205
5448
  exports.shouldEmitMissingCssFallback = shouldEmitMissingCssFallback;
5206
5449
  exports.shouldEmitWarning = shouldEmitWarning;
5450
+ exports.shouldHoldAdvisories = shouldHoldAdvisories;
5207
5451
  exports.shouldTrackGlobalVarSources = shouldTrackGlobalVarSources;
5208
5452
  exports.shouldWarnMissingTailwindEntry = shouldWarnMissingTailwindEntry;
5209
5453
  exports.shouldWarnUnscopedMonorepo = shouldWarnUnscopedMonorepo;