@csszyx/unplugin 0.15.1 → 0.15.3

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,60 @@ 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 compileManglePreserve(entries) {
1940
+ const exact = /* @__PURE__ */ new Set();
1941
+ const prefixes = [];
1942
+ const list = entries ?? [];
1943
+ list.forEach((entry, index) => {
1944
+ if (typeof entry !== "string" || entry.length === 0) {
1945
+ throw new TypeError(
1946
+ `[csszyx] production.manglePreserve[${index}] must be a non-empty string (an exact class name, or a prefix ending in \`*\`); got ${describe(entry)}.`
1947
+ );
1948
+ }
1949
+ if (entry === "*") {
1950
+ throw new TypeError(
1951
+ "[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`."
1952
+ );
1953
+ }
1954
+ if (entry.endsWith("*")) prefixes.push(entry.slice(0, -1));
1955
+ else exact.add(entry);
1956
+ });
1957
+ const test = (className) => exact.has(className) || prefixes.some((prefix) => className.startsWith(prefix));
1958
+ return {
1959
+ entries: list,
1960
+ test,
1961
+ unmatched(census) {
1962
+ const names = [...census];
1963
+ return list.filter((entry) => {
1964
+ if (entry.endsWith("*")) {
1965
+ const prefix = entry.slice(0, -1);
1966
+ return !names.some((name) => name.startsWith(prefix));
1967
+ }
1968
+ return !names.includes(entry);
1969
+ });
1970
+ }
1971
+ };
1972
+ }
1973
+ function describe(value) {
1974
+ if (typeof value === "string") return "an empty string";
1975
+ if (value instanceof RegExp) return `a RegExp (${String(value)})`;
1976
+ return `${typeof value} ${JSON.stringify(value) ?? String(value)}`;
1977
+ }
1978
+ function manglePreserveNoMatchMessage(entries) {
1979
+ const sample = entries.slice(0, 8).join(", ");
1980
+ const noun = entries.length === 1 ? "entry" : "entries";
1981
+ 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.`;
1982
+ }
1983
+ const TOKEN_CHARACTERS = /^[0-9a-z]+$/i;
1984
+ function mangleExcludeNeverTokenEntries(entries) {
1985
+ return [...entries].filter((entry) => !TOKEN_CHARACTERS.test(entry));
1986
+ }
1987
+ function mangleExcludeNeverTokenMessage(entries) {
1988
+ const sample = entries.slice(0, 8).join(", ");
1989
+ const noun = entries.length === 1 ? "name" : "names";
1990
+ 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.`;
1991
+ }
1992
+
1939
1993
  const MANGLE_RUNTIME_FILE = "mangle-runtime.mjs";
1940
1994
  function ensureMangleRuntimeFile(outputDir, globalVarAliasPrefix, exposeDebugGlobal) {
1941
1995
  const target = path__default.join(outputDir, MANGLE_RUNTIME_FILE);
@@ -2185,9 +2239,9 @@ let _hasWarnedTsConfig = false;
2185
2239
  let _hasWarnedTransformCacheVersion = false;
2186
2240
  let _hasWarnedNativeFallback = false;
2187
2241
  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.8KYddE6x.cjs', document.baseURI).href)));
2242
+ 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.ZqxgHk5F.cjs', document.baseURI).href)));
2189
2243
  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.8KYddE6x.cjs', document.baseURI).href))),
2244
+ node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('shared/unplugin.ZqxgHk5F.cjs', document.baseURI).href))),
2191
2245
  UNKNOWN_PACKAGE_VERSION
2192
2246
  );
2193
2247
  const COMPILER_VERSION = findPackageVersionFromModule("@csszyx/compiler", UNKNOWN_PACKAGE_VERSION);
@@ -2229,17 +2283,103 @@ function shouldWarnMissingTailwindEntry(ownedClassCount, sawTailwindEntry, sawAn
2229
2283
  function missingTailwindEntryMessage(ownedClassCount) {
2230
2284
  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
2285
  }
2232
- function collectMangleHybridHazards(mangleMap, mangledSources, externalClasses) {
2286
+ function asciiLower(text) {
2287
+ return text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
2288
+ }
2289
+ function attributeSelectorPredicates(operator, value) {
2290
+ const eq = (v) => (name) => name === v;
2291
+ const startsWith = (v) => (name) => name.startsWith(v);
2292
+ const endsWith = (v) => (name) => name.endsWith(v);
2293
+ const includes = (v) => (name) => name.includes(v);
2294
+ const dashPrefix = (v) => (name) => name === v || name.startsWith(`${v}-`);
2295
+ const segments = value.split(/[\t\n\f\r ]+/);
2296
+ if (segments.length === 1) {
2297
+ switch (operator) {
2298
+ case "=":
2299
+ case "~=":
2300
+ return [eq(value)];
2301
+ case "|=":
2302
+ return [dashPrefix(value)];
2303
+ case "^=":
2304
+ return [startsWith(value)];
2305
+ case "$=":
2306
+ return [endsWith(value)];
2307
+ case "*=":
2308
+ return [includes(value)];
2309
+ default:
2310
+ return [];
2311
+ }
2312
+ }
2313
+ if (operator === "~=") return [];
2314
+ const first = segments[0];
2315
+ const last = segments[segments.length - 1];
2316
+ const predicates = [];
2317
+ if (first.length > 0) {
2318
+ predicates.push(operator === "$=" || operator === "*=" ? endsWith(first) : eq(first));
2319
+ }
2320
+ for (const middle of segments.slice(1, -1)) predicates.push(eq(middle));
2321
+ if (last.length > 0) {
2322
+ if (operator === "^=" || operator === "*=") predicates.push(startsWith(last));
2323
+ else if (operator === "|=") predicates.push(dashPrefix(last));
2324
+ else predicates.push(eq(last));
2325
+ }
2326
+ return predicates;
2327
+ }
2328
+ function renderClassAttributeSelector(selector) {
2329
+ const flag = selector.insensitive ? " i" : "";
2330
+ return `[class${selector.operator}"${selector.value}"${flag}]`;
2331
+ }
2332
+ function selectorHazardFor(mangleMap, selector) {
2333
+ const value = selector.insensitive ? asciiLower(selector.value) : selector.value;
2334
+ const predicates = attributeSelectorPredicates(selector.operator, value);
2335
+ if (predicates.length === 0) return null;
2336
+ const fold = selector.insensitive ? asciiLower : (name) => name;
2337
+ const matches = (name) => {
2338
+ const folded = fold(name);
2339
+ return predicates.some((predicate) => predicate(folded));
2340
+ };
2341
+ const renamed = compiler.sortStrings(Object.keys(mangleMap).filter(matches));
2342
+ const matchedTokens = compiler.sortStrings(Object.values(mangleMap).filter(matches));
2343
+ if (renamed.length === 0 && matchedTokens.length === 0) return null;
2344
+ return {
2345
+ selector: renderClassAttributeSelector(selector),
2346
+ value: selector.value,
2347
+ renamed,
2348
+ matchedTokens
2349
+ };
2350
+ }
2351
+ function collectMangleHybridHazards(mangleMap, mangledSources, externalClasses, attributeSelectors = []) {
2233
2352
  const tokenValues = new Set(Object.values(mangleMap));
2234
2353
  const collisions = compiler.sortStrings([...tokenValues].filter((token) => externalClasses.has(token)));
2235
2354
  const orphans = compiler.sortStrings(
2236
2355
  Object.keys(mangleMap).filter((source) => !mangledSources.has(source))
2237
2356
  );
2238
- return { collisions, orphans };
2357
+ const hazards = /* @__PURE__ */ new Map();
2358
+ for (const selector of attributeSelectors) {
2359
+ const hazard = selectorHazardFor(mangleMap, selector);
2360
+ if (hazard !== null) hazards.set(hazard.selector, hazard);
2361
+ }
2362
+ const selectorMatches = compiler.sortStrings([...hazards.keys()]).map(
2363
+ (key) => hazards.get(key)
2364
+ );
2365
+ return { collisions, orphans, selectorMatches };
2366
+ }
2367
+ const BACKSLASH = String.fromCodePoint(92);
2368
+ const SINGLE_QUOTE = String.fromCodePoint(39);
2369
+ function quoteForConfig(entry) {
2370
+ const escaped = entry.replaceAll(BACKSLASH, BACKSLASH + BACKSLASH).replaceAll(SINGLE_QUOTE, BACKSLASH + SINGLE_QUOTE);
2371
+ return SINGLE_QUOTE + escaped + SINGLE_QUOTE;
2372
+ }
2373
+ function preserveEntriesFor(hazard) {
2374
+ const quote = quoteForConfig;
2375
+ const { value } = hazard;
2376
+ if (hazard.renamed.every((name) => name === value)) return [quote(value)];
2377
+ if (hazard.renamed.every((name) => name.startsWith(value))) return [quote(`${value}*`)];
2378
+ return hazard.renamed.map(quote);
2239
2379
  }
2240
2380
  function mangleHybridHazardMessage(hazards) {
2241
- const { collisions, orphans } = hazards;
2242
- if (collisions.length === 0 && orphans.length === 0) {
2381
+ const { collisions, orphans, selectorMatches = [] } = hazards;
2382
+ if (collisions.length === 0 && orphans.length === 0 && selectorMatches.length === 0) {
2243
2383
  return null;
2244
2384
  }
2245
2385
  const parts = ["[csszyx] production mangle found hybrid hazards:"];
@@ -2255,15 +2395,35 @@ function mangleHybridHazardMessage(hazards) {
2255
2395
  ` ${orphans.length} mangled class(es) have no emitted CSS rule (e.g. ${sample}) \u2014 those elements lose styling.`
2256
2396
  );
2257
2397
  }
2398
+ const broken = selectorMatches.filter((hazard) => hazard.renamed.length > 0);
2399
+ const newlyMatched = selectorMatches.filter((hazard) => hazard.matchedTokens.length > 0);
2400
+ if (broken.length > 0) {
2401
+ const sample = broken.slice(0, 3).map((hazard) => `${hazard.selector} \u2192 ${hazard.renamed.slice(0, 4).join(", ")}`).join("; ");
2402
+ parts.push(
2403
+ ` ${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.`
2404
+ );
2405
+ }
2406
+ if (newlyMatched.length > 0) {
2407
+ const sample = newlyMatched.slice(0, 3).map((hazard) => `${hazard.selector} \u2192 ${hazard.matchedTokens.slice(0, 4).join(", ")}`).join("; ");
2408
+ parts.push(
2409
+ ` ${newlyMatched.length} attribute selector(s) would start matching mangled tokens instead (e.g. ${sample}).`
2410
+ );
2411
+ }
2258
2412
  if (collisions.length > 0) {
2259
2413
  parts.push(
2260
2414
  " 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."
2261
2415
  );
2262
- } else {
2416
+ } else if (orphans.length > 0) {
2263
2417
  parts.push(
2264
2418
  " 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."
2265
2419
  );
2266
2420
  }
2421
+ if (broken.length > 0) {
2422
+ const entries = [...new Set(broken.flatMap(preserveEntriesFor))].join(", ");
2423
+ parts.push(
2424
+ ` 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.`
2425
+ );
2426
+ }
2267
2427
  return parts.join("");
2268
2428
  }
2269
2429
  const TAILWIND_IMPORT_SPECIFIER = /@import\s+["']tailwindcss(?:\/[^"']*)?["']/g;
@@ -3065,7 +3225,14 @@ function mangleCodeClassesSync(code, mangleMap) {
3065
3225
  result = out + result.slice(copiedTo);
3066
3226
  }
3067
3227
  }
3068
- result = result.replace(/([,(]|&&)(\s*)"([^"]+)"/g, (match, sep, ws, inner) => {
3228
+ const objectKeyColon = /\s*:/y;
3229
+ result = result.replace(/([,([{]|&&)(\s*)"([^"]+)"/g, (match, sep, ws, inner, offset) => {
3230
+ if (sep === "{") {
3231
+ objectKeyColon.lastIndex = offset + match.length;
3232
+ if (!objectKeyColon.test(result)) {
3233
+ return match;
3234
+ }
3235
+ }
3069
3236
  const tokens = inner.split(/\s+/).filter(Boolean);
3070
3237
  if (tokens.length === 0) {
3071
3238
  return match;
@@ -3161,6 +3328,8 @@ function createCsszyxPlugins(options = {}) {
3161
3328
  const szObjectProvidersExamined = /* @__PURE__ */ new Set();
3162
3329
  let specifierAliases = [];
3163
3330
  const mangleReserved = new Set(options.production?.mangleExclude ?? []);
3331
+ const manglePreserve = compileManglePreserve(options.production?.manglePreserve);
3332
+ let manglePreserveNoMatchWarned = false;
3164
3333
  let mangleMapFrozen = false;
3165
3334
  let prescanSawServerModule = false;
3166
3335
  let skipRscRecords = false;
@@ -3171,6 +3340,7 @@ function createCsszyxPlugins(options = {}) {
3171
3340
  let projectCssFiles = [];
3172
3341
  const transformMangledSources = /* @__PURE__ */ new Set();
3173
3342
  const transformExternalClasses = /* @__PURE__ */ new Set();
3343
+ const transformAttributeSelectors = /* @__PURE__ */ new Map();
3174
3344
  if (options.production?.mangleMapDelivery !== void 0) {
3175
3345
  console.warn(removedMangleMapDeliveryMessage());
3176
3346
  }
@@ -3200,6 +3370,10 @@ function createCsszyxPlugins(options = {}) {
3200
3370
  if (unknownConfigKeys.length > 0) {
3201
3371
  emitWarning(unknownConfigKeysMessage(unknownConfigKeys));
3202
3372
  }
3373
+ const excludeNeverTokens = mangleExcludeNeverTokenEntries(mangleReserved);
3374
+ if (excludeNeverTokens.length > 0) {
3375
+ emitWarning(mangleExcludeNeverTokenMessage(excludeNeverTokens));
3376
+ }
3203
3377
  function emitWarning(message, opts = {}) {
3204
3378
  if (shouldEmitWarning(quiet, opts.devOnly ?? false, process.env.NODE_ENV === "production")) {
3205
3379
  console.warn(message);
@@ -4060,10 +4234,15 @@ function createCsszyxPlugins(options = {}) {
4060
4234
  ...state.authoredClasses,
4061
4235
  ...state.ownedClasses
4062
4236
  ]);
4063
- return allocateMangleTokens(
4064
- mangleEligibleClasses(state.ownedClasses, state.authoredClasses),
4065
- forbiddenTokens
4237
+ const eligible = mangleEligibleClasses(state.ownedClasses, state.authoredClasses).filter(
4238
+ (className) => !manglePreserve.test(className)
4066
4239
  );
4240
+ if (!manglePreserveNoMatchWarned && manglingEnabled && state.ownedClasses.size > 0) {
4241
+ manglePreserveNoMatchWarned = true;
4242
+ const unmatched = manglePreserve.unmatched(state.ownedClasses);
4243
+ if (unmatched.length > 0) emitWarning(manglePreserveNoMatchMessage(unmatched));
4244
+ }
4245
+ return allocateMangleTokens(eligible, forbiddenTokens);
4067
4246
  }
4068
4247
  function freezeMangleMap() {
4069
4248
  state.mangleMap = computeMangleMap();
@@ -4216,6 +4395,38 @@ function createCsszyxPlugins(options = {}) {
4216
4395
  }
4217
4396
  return modules;
4218
4397
  }
4398
+ function handleHotFile(pass, ctx) {
4399
+ const { isClientPass } = pass;
4400
+ if (isClientPass) {
4401
+ prescanResultHandoff.clear();
4402
+ }
4403
+ const scanCss = options.build?.scanCss;
4404
+ const reloadThemeGroupsModule = () => {
4405
+ const themeGroupsModule = ctx.server.moduleGraph.getModuleById(
4406
+ themeGroupsFile.RESOLVED_THEME_GROUPS_VIRTUAL_ID
4407
+ );
4408
+ if (themeGroupsModule) {
4409
+ ctx.server.moduleGraph.invalidateModule(themeGroupsModule);
4410
+ }
4411
+ };
4412
+ if (isClientPass && ctx.file.endsWith(".css")) {
4413
+ const root = state.rootDir;
4414
+ const before = themeGroupsFile.createThemeGroupsModule(themeGroupTokens());
4415
+ if (scanCss && matchesAnyPattern(ctx.file, scanCss, root)) {
4416
+ state.scanCssTheme = runThemeScan(root, scanCss) ?? state.scanCssTheme;
4417
+ }
4418
+ runAutoThemeScan(root);
4419
+ reloadThemeGroupsModule();
4420
+ if (themeGroupsFile.createThemeGroupsModule(themeGroupTokens()) !== before) {
4421
+ ctx.server.ws.send({ type: "full-reload" });
4422
+ }
4423
+ }
4424
+ if (safelistSource.normalizePathSeparators(ctx.file) === safelistSource.normalizePathSeparators(path__namespace.join(state.rootDir, SAFELIST_FILENAME))) {
4425
+ return tailwindEntryModules(pass.moduleGraph());
4426
+ }
4427
+ if (isClientPass) discoverHotFileClasses(ctx.file, ctx.server.watcher);
4428
+ return void 0;
4429
+ }
4219
4430
  function transformTailwindCssEntry(code, id) {
4220
4431
  state.sawAnyCss = true;
4221
4432
  const [cssFile] = id.split("?");
@@ -4717,38 +4928,39 @@ ${transformedCode}`;
4717
4928
  * which leaves Vite's own handling in place.
4718
4929
  */
4719
4930
  hotUpdate(ctx) {
4720
- const isClientPass = (this.environment?.name ?? "client") === "client";
4721
- if (isClientPass) {
4722
- prescanResultHandoff.clear();
4723
- }
4724
- const scanCss = options.build?.scanCss;
4725
- const reloadThemeGroupsModule = () => {
4726
- const themeGroupsModule = ctx.server.moduleGraph.getModuleById(
4727
- themeGroupsFile.RESOLVED_THEME_GROUPS_VIRTUAL_ID
4728
- );
4729
- if (themeGroupsModule) {
4730
- ctx.server.moduleGraph.invalidateModule(themeGroupsModule);
4731
- }
4732
- };
4733
- if (isClientPass && ctx.file.endsWith(".css")) {
4734
- const root = ctx.server.config.root || process.cwd();
4735
- const before = themeGroupsFile.createThemeGroupsModule(themeGroupTokens());
4736
- if (scanCss && matchesAnyPattern(ctx.file, scanCss, root)) {
4737
- state.scanCssTheme = runThemeScan(root, scanCss) ?? state.scanCssTheme;
4738
- }
4739
- runAutoThemeScan(root);
4740
- reloadThemeGroupsModule();
4741
- if (themeGroupsFile.createThemeGroupsModule(themeGroupTokens()) !== before) {
4742
- ctx.server.ws.send({ type: "full-reload" });
4743
- }
4744
- }
4745
- if (safelistSource.normalizePathSeparators(ctx.file) === safelistSource.normalizePathSeparators(path__namespace.join(state.rootDir, SAFELIST_FILENAME))) {
4746
- return tailwindEntryModules(
4747
- this.environment?.moduleGraph ?? ctx.server.environments.client.moduleGraph
4748
- );
4749
- }
4750
- if (isClientPass) discoverHotFileClasses(ctx.file, ctx.server.watcher);
4751
- return void 0;
4931
+ return handleHotFile(
4932
+ {
4933
+ isClientPass: (this.environment?.name ?? "client") === "client",
4934
+ // `server.moduleGraph` holds the backward-compatible
4935
+ // nodes Vite mapped back for the legacy hook only,
4936
+ // so they are the wrong objects to hand this one —
4937
+ // TypeScript says so too.
4938
+ moduleGraph: () => this.environment?.moduleGraph ?? ctx.server.environments.client.moduleGraph
4939
+ },
4940
+ ctx
4941
+ );
4942
+ },
4943
+ /**
4944
+ * The hook Vite 5 calls. Vite 6 and later never run it while
4945
+ * `hotUpdate` is declared, so declaring both costs nothing
4946
+ * there and keeps a Vite 5 dev server compiling `sz` live.
4947
+ *
4948
+ * Vite 5 routes only a file CHANGE here, never a create, so a
4949
+ * project with no `sz` at prescan still sees its first `sz`
4950
+ * edit answered by `@tailwindcss/vite`'s full reload on that
4951
+ * generation — the behaviour it always had there. The hook
4952
+ * runs once, with no environment, so it is the client pass
4953
+ * and answers from the server-wide graph.
4954
+ *
4955
+ * @param ctx - HMR context: the changed file and the dev server.
4956
+ * @returns The modules a safelist write affects; undefined
4957
+ * otherwise.
4958
+ */
4959
+ handleHotUpdate(ctx) {
4960
+ return handleHotFile(
4961
+ { isClientPass: true, moduleGraph: () => ctx.server.moduleGraph },
4962
+ ctx
4963
+ );
4752
4964
  },
4753
4965
  transformIndexHtml: {
4754
4966
  order: "pre",
@@ -4837,7 +5049,7 @@ ${transformedCode}`;
4837
5049
  recordCodePair(sizeAccount, before, after);
4838
5050
  return after;
4839
5051
  }
4840
- function rewriteOutputCss(source, file, shouldMangle, mangledSources, externalClasses) {
5052
+ function rewriteOutputCss(source, file, shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4841
5053
  const css = rewriteCssWithValidatedGlobalVarPlan(
4842
5054
  source,
4843
5055
  file,
@@ -4851,6 +5063,9 @@ ${transformedCode}`;
4851
5063
  });
4852
5064
  for (const className of result.mangledClasses) mangledSources.add(className);
4853
5065
  for (const className of result.unmangledClasses) externalClasses.add(className);
5066
+ for (const selector of result.classAttributeSelectors) {
5067
+ attributeSelectors.set(cssMangler.classAttributeSelectorKey(selector), selector);
5068
+ }
4854
5069
  const mangled = result.transformedCount > 0 ? result.css : css;
4855
5070
  recordCssPair(sizeAccount, css, mangled);
4856
5071
  return mangled;
@@ -4859,10 +5074,12 @@ ${transformedCode}`;
4859
5074
  throw error;
4860
5075
  }
4861
5076
  }
4862
- function reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses) {
5077
+ function reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4863
5078
  if (!shouldMangle) return;
4864
5079
  const message = mangleHybridHazardMessage(
4865
- collectMangleHybridHazards(state.mangleMap, mangledSources, externalClasses)
5080
+ collectMangleHybridHazards(state.mangleMap, mangledSources, externalClasses, [
5081
+ ...attributeSelectors.values()
5082
+ ])
4866
5083
  );
4867
5084
  if (message) console.warn(message);
4868
5085
  }
@@ -4923,6 +5140,7 @@ ${transformedCode}`;
4923
5140
  }
4924
5141
  const mangledSources = /* @__PURE__ */ new Set();
4925
5142
  const externalClasses = /* @__PURE__ */ new Set();
5143
+ const attributeSelectors = /* @__PURE__ */ new Map();
4926
5144
  for (const file in assets) {
4927
5145
  const source = assets[file].source().toString();
4928
5146
  if (file.endsWith(".css")) {
@@ -4931,7 +5149,8 @@ ${transformedCode}`;
4931
5149
  file,
4932
5150
  shouldMangle,
4933
5151
  mangledSources,
4934
- externalClasses
5152
+ externalClasses,
5153
+ attributeSelectors
4935
5154
  );
4936
5155
  if (css !== source) {
4937
5156
  compilation.updateAsset(file, new compiler.webpack.sources.RawSource(css));
@@ -4940,10 +5159,15 @@ ${transformedCode}`;
4940
5159
  rewriteWebpackCodeAsset(file, source, shouldMangle, compilation, compiler);
4941
5160
  }
4942
5161
  }
4943
- reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
5162
+ reportOutputMangleHazards(
5163
+ shouldMangle,
5164
+ mangledSources,
5165
+ externalClasses,
5166
+ attributeSelectors
5167
+ );
4944
5168
  reportBuildSummary();
4945
5169
  }
4946
- function rewriteViteBundleEntry(chunk, file, shouldMangle, mangledSources, externalClasses) {
5170
+ function rewriteViteBundleEntry(chunk, file, shouldMangle, mangledSources, externalClasses, attributeSelectors) {
4947
5171
  if (chunk.type !== "asset" || !chunk.fileName.endsWith(".css") || chunk.source === void 0) {
4948
5172
  return;
4949
5173
  }
@@ -4953,7 +5177,8 @@ ${transformedCode}`;
4953
5177
  file,
4954
5178
  shouldMangle,
4955
5179
  mangledSources,
4956
- externalClasses
5180
+ externalClasses,
5181
+ attributeSelectors
4957
5182
  );
4958
5183
  if (css !== originalCss) chunk.source = css;
4959
5184
  }
@@ -4964,7 +5189,8 @@ ${transformedCode}`;
4964
5189
  id,
4965
5190
  shouldMangle,
4966
5191
  transformMangledSources,
4967
- transformExternalClasses
5192
+ transformExternalClasses,
5193
+ transformAttributeSelectors
4968
5194
  );
4969
5195
  return css === code ? null : { code: css, map: null };
4970
5196
  }
@@ -4989,6 +5215,7 @@ ${transformedCode}`;
4989
5215
  }
4990
5216
  const mangledSources = new Set(transformMangledSources);
4991
5217
  const externalClasses = new Set(transformExternalClasses);
5218
+ const attributeSelectors = new Map(transformAttributeSelectors);
4992
5219
  if (!cssRewrittenInTransform) {
4993
5220
  for (const file in bundle) {
4994
5221
  rewriteViteBundleEntry(
@@ -4996,11 +5223,17 @@ ${transformedCode}`;
4996
5223
  file,
4997
5224
  shouldMangle,
4998
5225
  mangledSources,
4999
- externalClasses
5226
+ externalClasses,
5227
+ attributeSelectors
5000
5228
  );
5001
5229
  }
5002
5230
  }
5003
- reportOutputMangleHazards(shouldMangle, mangledSources, externalClasses);
5231
+ reportOutputMangleHazards(
5232
+ shouldMangle,
5233
+ mangledSources,
5234
+ externalClasses,
5235
+ attributeSelectors
5236
+ );
5004
5237
  }
5005
5238
  const postPlugin = unplugin$1.createUnplugin(() => ({
5006
5239
  name: "csszyx:post",
@@ -4,6 +4,7 @@ import { Plugin } from 'esbuild';
4
4
  import { InputPluginOption } from 'rollup';
5
5
  import { UnpluginInstance, WebpackPluginInstance } from 'unplugin';
6
6
  import { PluginOption } from 'vite';
7
+ import { ClassAttributeSelector } from '../css-mangler.cjs';
7
8
 
8
9
  /** Source location for one CSS custom-property occurrence. */
9
10
  interface CssVarLocation {
@@ -655,6 +656,25 @@ interface MangleHybridHazards {
655
656
  * DOM class is rewritten to a token with no rule → the element loses styling.
656
657
  */
657
658
  orphans: string[];
659
+ /**
660
+ * Attribute selectors on `class` whose match depends on a name the map
661
+ * renamed. `[class*="bg-tag"]` matched `bg-tag-blue-bg` by text; after
662
+ * the rename the element carries `y4`, the rule stops applying, and
663
+ * nothing in the build says so — the CSS is intact and the page renders.
664
+ * Absent when the caller has no selector census.
665
+ */
666
+ selectorMatches?: MangleSelectorHazard[];
667
+ }
668
+ /** One attribute selector the mangle map breaks or newly satisfies. */
669
+ interface MangleSelectorHazard {
670
+ /** The selector as written: `[class*="bg-tag"]`, with ` i` when case-insensitive. */
671
+ selector: string;
672
+ /** The attribute value the selector compares against, unescaped. */
673
+ value: string;
674
+ /** Map keys the selector was matching by name, sorted; renamed, so it no longer does. */
675
+ renamed: string[];
676
+ /** Tokens the selector would start matching that no class name did before, sorted. */
677
+ matchedTokens: string[];
658
678
  }
659
679
  /**
660
680
  * Detect hybrid-mangle hazards from the accumulated per-asset mangle results.
@@ -662,9 +682,10 @@ interface MangleHybridHazards {
662
682
  * @param mangleMap - the full original→token map injected into the runtime.
663
683
  * @param mangledSources - map keys that were actually found and renamed in some CSS asset.
664
684
  * @param externalClasses - class names found in CSS that are NOT in the mangle map (non-csszyx).
665
- * @returns the colliding tokens and orphan sources (each sorted, deduped).
685
+ * @param attributeSelectors - every `[class …]` selector the emitted CSS contains.
686
+ * @returns the colliding tokens, orphan sources and selector hazards (each sorted, deduped).
666
687
  */
667
- declare function collectMangleHybridHazards(mangleMap: Record<string, string>, mangledSources: ReadonlySet<string>, externalClasses: ReadonlySet<string>): MangleHybridHazards;
688
+ declare function collectMangleHybridHazards(mangleMap: Record<string, string>, mangledSources: ReadonlySet<string>, externalClasses: ReadonlySet<string>, attributeSelectors?: readonly ClassAttributeSelector[]): MangleHybridHazards;
668
689
  /**
669
690
  * Build the hybrid-mangle hazard warning, or null when there is nothing to warn.
670
691
  *
@@ -1042,5 +1063,5 @@ declare const rollupPlugin: (options?: PartialCsszyxConfig) => InputPluginOption
1042
1063
  */
1043
1064
  declare const esbuildPlugin: (options?: PartialCsszyxConfig) => Plugin;
1044
1065
 
1045
- export { lateMangleCensusMessage as $, deleteRSCModuleRecord as A, emitMissingCssFallback as B, esbuildPlugin as D, extractGlobalVarAliasesForManifest as E, fileMayContainSafelistableSz as F, findLocalImportSources as H, findRSCBoundaryViolation as I, findRSCGraphViolation as J, hasInjectableTailwindCandidate as K, hasTokens as L, hasUseClientDirective as N, hasUseServerDirective as O, isAdvisoryDiagnostic as T, isCompileSourceOptedIn as U, isHardIgnoredPath as W, isMangleableCssId as X, isMonorepoPackage as Y, isPackagesSkippedSource as Z, isRSCServerModule as _, mangleCodeClassesSync as a0, mangleEligibleClasses as a1, mangleHybridHazardMessage as a2, mergeThemes as a3, missingTailwindEntryMessage as a4, normalizeGlobalVarAliasesForCache as a5, parseThemeBlocks as a6, parseUtilityBlocks as a7, realContentHashDisabledMessage as a8, recordGlobalVarSourceFile as a9, resolveCompileSourceDirs as aa, resolveNativeCacheIdentity as ab, resolveQuietMode as ac, rollupPlugin as ad, scanCustomPropertyNames as ae, shouldEmitMissingCssFallback as af, shouldEmitWarning as ag, shouldTrackGlobalVarSources as ah, shouldWarnMissingTailwindEntry as ai, shouldWarnUnscopedMonorepo as aj, skippedSzFilesMessage as ak, suppressedAdvisoryMessage as al, unscopedMonorepoMessage as am, vitePlugin as an, watchModeMangleMessage as ao, webpackPlugin as ap, allocateMangleTokens as s, assertNoRSCBoundaryViolation as t, assertNoRSCGraphViolation as u, collectMangleHybridHazards as v, createGlobalVarMapAssetSource as w, createRSCModuleRecord as x, cssHasContentScope as y, unplugin as z };
1046
- export type { CssVarScanResult as C, GlobalVarScanCacheKeyInput as G, MangleHybridHazards as M, PlanGlobalVarAliasesInput as P, QuietMode as Q, RewriteGlobalVarCssAliasesOptions as R, ScanGlobalVarCssOptions as S, ValidateGlobalVarAliasInputsOptions as V, GlobalVarAliasPlan as a, GlobalVarCssAliasRewriteResult as b, CreateGlobalVarAliasValidationOptionsInput as c, GlobalVarAliasValidationResult as d, CssVarDefinition as e, CssVarLocation as f, CssVarReference as g, GlobalVarAliasDiagnostic as h, GlobalVarAliasDiagnosticSeverity as i, GlobalVarAliasEntry as j, GlobalVarCodeSource as k, GlobalVarCssAssetSource as l, GlobalVarCssSource as m, GlobalVarScanCacheEntry as n, ParsedTheme as o, ParsedUtilities as p, RSCBoundaryViolation as q, RSCModuleRecord as r };
1066
+ export { isRSCServerModule as $, unplugin as A, deleteRSCModuleRecord as B, emitMissingCssFallback as D, esbuildPlugin as E, extractGlobalVarAliasesForManifest as F, fileMayContainSafelistableSz as H, findLocalImportSources as I, findRSCBoundaryViolation as J, findRSCGraphViolation as K, hasInjectableTailwindCandidate as L, hasTokens as N, hasUseClientDirective as O, hasUseServerDirective as T, isAdvisoryDiagnostic as U, isCompileSourceOptedIn as W, isHardIgnoredPath as X, isMangleableCssId as Y, isMonorepoPackage as Z, isPackagesSkippedSource as _, lateMangleCensusMessage as a0, mangleCodeClassesSync as a1, mangleEligibleClasses as a2, mangleHybridHazardMessage as a3, mergeThemes as a4, missingTailwindEntryMessage as a5, normalizeGlobalVarAliasesForCache as a6, parseThemeBlocks as a7, parseUtilityBlocks as a8, realContentHashDisabledMessage as a9, recordGlobalVarSourceFile as aa, resolveCompileSourceDirs as ab, resolveNativeCacheIdentity as ac, resolveQuietMode as ad, rollupPlugin as ae, scanCustomPropertyNames as af, shouldEmitMissingCssFallback as ag, shouldEmitWarning as ah, shouldTrackGlobalVarSources as ai, shouldWarnMissingTailwindEntry as aj, shouldWarnUnscopedMonorepo as ak, skippedSzFilesMessage as al, suppressedAdvisoryMessage as am, unscopedMonorepoMessage as an, vitePlugin as ao, watchModeMangleMessage as ap, webpackPlugin as aq, allocateMangleTokens as t, assertNoRSCBoundaryViolation as u, assertNoRSCGraphViolation as v, collectMangleHybridHazards as w, createGlobalVarMapAssetSource as x, createRSCModuleRecord as y, cssHasContentScope as z };
1067
+ export type { CssVarScanResult as C, GlobalVarScanCacheKeyInput as G, MangleHybridHazards as M, PlanGlobalVarAliasesInput as P, QuietMode as Q, RewriteGlobalVarCssAliasesOptions as R, ScanGlobalVarCssOptions as S, ValidateGlobalVarAliasInputsOptions as V, GlobalVarAliasPlan as a, GlobalVarCssAliasRewriteResult as b, CreateGlobalVarAliasValidationOptionsInput as c, GlobalVarAliasValidationResult as d, CssVarDefinition as e, CssVarLocation as f, CssVarReference as g, GlobalVarAliasDiagnostic as h, GlobalVarAliasDiagnosticSeverity as i, GlobalVarAliasEntry as j, GlobalVarCodeSource as k, GlobalVarCssAssetSource as l, GlobalVarCssSource as m, GlobalVarScanCacheEntry as n, MangleSelectorHazard as o, ParsedTheme as p, ParsedUtilities as q, RSCBoundaryViolation as r, RSCModuleRecord as s };
package/dist/vite.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- const unplugin = require('./shared/unplugin.8KYddE6x.cjs');
5
+ const unplugin = require('./shared/unplugin.ZqxgHk5F.cjs');
6
6
  require('node:crypto');
7
7
  require('node:fs');
8
8
  require('node:module');
package/dist/vite.d.cts CHANGED
@@ -1,8 +1,10 @@
1
- import { an } from './shared/unplugin.BT7PqYuK.cjs';
2
- export = an;
1
+ import { ao } from './shared/unplugin.nvme_dSL.cjs';
2
+ export = ao;
3
3
  import '@csszyx/compiler';
4
4
  import '@csszyx/types';
5
5
  import 'esbuild';
6
6
  import 'rollup';
7
7
  import 'unplugin';
8
8
  import 'vite';
9
+ import './css-mangler.cjs';
10
+ import 'postcss';
package/dist/vite.d.mts CHANGED
@@ -1,7 +1,9 @@
1
- export { an as default } from './shared/unplugin.BT7PqYuK.mjs';
1
+ export { ao as default } from './shared/unplugin.CInSoHdQ.mjs';
2
2
  import '@csszyx/compiler';
3
3
  import '@csszyx/types';
4
4
  import 'esbuild';
5
5
  import 'rollup';
6
6
  import 'unplugin';
7
7
  import 'vite';
8
+ import './css-mangler.mjs';
9
+ import 'postcss';
package/dist/vite.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { a1 as default } from './shared/unplugin.D68hyqgb.mjs';
1
+ export { a1 as default } from './shared/unplugin.CPo2xrEy.mjs';
2
2
  import 'node:crypto';
3
3
  import 'node:fs';
4
4
  import 'node:module';
@@ -13,10 +13,10 @@ import '@csszyx/types';
13
13
  import '@csszyx/vue-adapter';
14
14
  import 'unplugin';
15
15
  import './shared/unplugin.DH_ij6cf.mjs';
16
- import './shared/unplugin.Bk9o8_RZ.mjs';
16
+ import './shared/unplugin.C-oQU1jl.mjs';
17
17
  import './css-mangler.mjs';
18
18
  import 'postcss';
19
19
  import 'postcss-selector-parser';
20
- import './shared/unplugin.DK0b4fr7.mjs';
20
+ import './shared/unplugin.Ceq-N4jI.mjs';
21
21
  import 'node:zlib';
22
22
  import 'postcss-value-parser';