mnfst 0.5.217 → 0.5.219

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.
@@ -599,6 +599,7 @@ class TailwindCompiler {
599
599
  this.lastClassesHash = ''; // Track changes in used classes
600
600
  this.staticClassCache = new Set(); // Cache classes found in static HTML/components
601
601
  this.dynamicClassCache = new Set(); // Cache classes that appear dynamically
602
+ this.quietClasses = new Set(); // Seen only inside ignored elements, not yet compiled
602
603
  this.hasScannedStatic = false; // Track if we've done initial static scan
603
604
  this.staticScanPromise = null; // Promise for initial static scan
604
605
  this.ignoredClassPatterns = [ // Patterns for classes to ignore
@@ -1338,6 +1339,7 @@ TailwindCompiler.prototype.cleanupCache = function () {
1338
1339
  TailwindCompiler.prototype.ensureUtilityStylesLast = function () {
1339
1340
  if (this.styleElement && this.styleElement.parentNode && document.head.lastElementChild !== this.styleElement) {
1340
1341
  document.head.appendChild(this.styleElement);
1342
+ if (this.resyncUtilitiesText) this.resyncUtilitiesText();
1341
1343
  }
1342
1344
  };
1343
1345
 
@@ -1714,6 +1716,8 @@ TailwindCompiler.prototype.fetchThemeContent = async function () {
1714
1716
  // Wait for all fetches to complete
1715
1717
  await Promise.all(fetchPromises);
1716
1718
 
1719
+ // Order-free identity (fetches land in any order) for "theme unchanged" checks
1720
+ this.lastThemeKey = Array.from(themeContents).sort().join('\n');
1717
1721
  return Array.from(themeContents).join('\n');
1718
1722
  };
1719
1723
 
@@ -2450,6 +2454,10 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2450
2454
  const generatedRules = new Set(); // Track generated rules to prevent duplicates
2451
2455
  const variables = this.extractThemeVariables(cssText);
2452
2456
  const { classes: usedClasses, variableSuffixes } = usedData;
2457
+ // Optional: raw rules + their full-regen order keys (incremental apply places deltas by them)
2458
+ const rulesOut = usedData.rulesOut, keysOut = usedData.keysOut;
2459
+ let slot = null;
2460
+ const emit = (rule, sub) => { utilities.push(rule); if (rulesOut) { rulesOut.push(rule); keysOut.push(slot.concat(slot[3] ? 2 : sub)); } }; // opacity rules order by discovery
2453
2461
 
2454
2462
  if (variables.size === 0) {
2455
2463
  return '';
@@ -2474,7 +2482,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2474
2482
  if (usedClasses.includes(baseClass)) {
2475
2483
  const rule = `.${escapeClassName(baseClass)} { ${css} }`;
2476
2484
  if (!generatedRules.has(rule)) {
2477
- utilities.push(rule);
2485
+ emit(rule, 0);
2478
2486
  generatedRules.add(rule);
2479
2487
  }
2480
2488
  }
@@ -2485,7 +2493,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2485
2493
  css + ' !important';
2486
2494
  const rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2487
2495
  if (!generatedRules.has(rule)) {
2488
- utilities.push(rule);
2496
+ emit(rule, 1);
2489
2497
  generatedRules.add(rule);
2490
2498
  }
2491
2499
  }
@@ -2554,26 +2562,33 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2554
2562
  rule;
2555
2563
 
2556
2564
  if (!generatedRules.has(finalRule)) {
2557
- utilities.push(finalRule);
2565
+ emit(finalRule, 2);
2558
2566
  generatedRules.add(finalRule);
2559
2567
  }
2560
2568
  }
2561
2569
  };
2562
2570
 
2571
+ // Every generated class ends in its variable's suffix: skip variables no used class mentions
2572
+ const usedBases = usedClasses.map(cls => this.parseClassName(cls).baseClass);
2573
+
2563
2574
  // Generate utilities based on variable prefix
2575
+ let vi = -1;
2564
2576
  for (const [varName, varValue] of variables.entries()) {
2577
+ vi++;
2565
2578
  if (!varName.match(this.regexPatterns.tailwindPrefix)) {
2566
2579
  continue;
2567
2580
  }
2568
2581
 
2569
2582
  const suffix = varName.split('-').slice(1).join('-');
2583
+ if (!usedBases.some(base => base.includes(suffix))) continue;
2570
2584
  const value = `var(--${varName})`;
2571
2585
  const prefix = varName.split('-')[0] + '-';
2572
2586
  const generator = this.utilityGenerators[prefix];
2573
2587
 
2574
2588
  if (generator) {
2575
2589
  const utilityPairs = generator(suffix, value);
2576
- for (const [className, css] of utilityPairs) {
2590
+ for (let pi = 0; pi < utilityPairs.length; pi++) {
2591
+ const [className, css] = utilityPairs[pi];
2577
2592
  // Check if this specific utility class is actually used (including variants and important)
2578
2593
  const isUsed = usedClasses.some(cls => {
2579
2594
  // Parse the class to extract the base utility name
@@ -2586,6 +2601,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2586
2601
  (baseClass.startsWith('!') && baseClass.slice(1) === className);
2587
2602
  });
2588
2603
  if (isUsed) {
2604
+ slot = [0, vi, pi, 0];
2589
2605
  generateUtility(className, css);
2590
2606
  }
2591
2607
 
@@ -2617,6 +2633,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2617
2633
  const opacity = opacityBaseClass.split('/')[1];
2618
2634
  const opacityValue = `color-mix(in oklch, ${value} ${opacity}%, transparent)`;
2619
2635
  const opacityCss = css.replace(value, opacityValue);
2636
+ slot = [0, vi, pi, 1];
2620
2637
  generateUtility(opacityBaseClass, opacityCss);
2621
2638
  }
2622
2639
  }
@@ -2636,6 +2653,9 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2636
2653
  const utilities = [];
2637
2654
  const generatedRules = new Set();
2638
2655
  const { classes: usedClasses } = usedData;
2656
+ const rulesOut = usedData.rulesOut, keysOut = usedData.keysOut;
2657
+ let slot = null;
2658
+ const emit = (rule, sub) => { utilities.push(rule); if (rulesOut) { rulesOut.push(rule); keysOut.push(slot.concat(sub)); } };
2639
2659
 
2640
2660
  // Helper to clean up [object Object] from CSS strings
2641
2661
  const cleanCssString = (css) => {
@@ -2791,7 +2811,7 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2791
2811
  rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2792
2812
  }
2793
2813
  if (!generatedRules.has(rule)) {
2794
- utilities.push(rule);
2814
+ emit(rule, 1);
2795
2815
  generatedRules.add(rule);
2796
2816
  }
2797
2817
  }
@@ -2965,14 +2985,16 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2965
2985
  }
2966
2986
 
2967
2987
  if (!generatedRules.has(finalRule)) {
2968
- utilities.push(finalRule);
2988
+ emit(finalRule, 2);
2969
2989
  generatedRules.add(finalRule);
2970
2990
  }
2971
2991
  }
2972
2992
  };
2973
2993
 
2974
2994
  // Generate utilities for each custom class that's actually used
2995
+ let ci = -1;
2975
2996
  for (const [className, cssOrSelector] of this.customUtilities.entries()) {
2997
+ ci++;
2976
2998
  // Normalize class name: if it starts with !, extract the base name
2977
2999
  const hasImportantPrefix = className.startsWith('!');
2978
3000
  const baseClassName = hasImportantPrefix ? className.slice(1) : className;
@@ -3012,9 +3034,12 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
3012
3034
  // Generate utility with base class name (without !)
3013
3035
  // The CSS already has !important if className started with !
3014
3036
  if (typeof cssOrSelector === 'string') {
3037
+ slot = [1, ci, 0, 0];
3015
3038
  generateUtility(baseClassName, normalizedCss, null);
3016
3039
  } else if (Array.isArray(cssOrSelector)) {
3017
- for (const entry of cssOrSelector) {
3040
+ for (let ai = 0; ai < cssOrSelector.length; ai++) {
3041
+ const entry = cssOrSelector[ai];
3042
+ slot = [1, ci, ai, 0];
3018
3043
  if (entry && entry.css && entry.selector) {
3019
3044
  // Ensure entry.css is a string (not an object)
3020
3045
  const extracted = typeof entry.css === 'string' ? entry.css :
@@ -3035,6 +3060,7 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
3035
3060
  String(cssOrSelector.css));
3036
3061
  const selectorCss = cleanCssString(extracted);
3037
3062
 
3063
+ slot = [1, ci, 0, 0];
3038
3064
  generateUtility(baseClassName, selectorCss, {
3039
3065
  selector: cssOrSelector.selector,
3040
3066
  fullBlock: cssOrSelector.fullBlock || false
@@ -3050,49 +3076,169 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
3050
3076
  }
3051
3077
  };
3052
3078
 
3053
- // Helper function to sort utilities so base classes come before variants
3054
- TailwindCompiler.prototype.sortUtilities = function(utilitiesText) {
3055
- if (!utilitiesText) return '';
3056
-
3057
- // Split into individual rules (each rule starts with . or @media)
3079
+ // Split generated CSS into top-level rules (each starts with . or an at-rule)
3080
+ TailwindCompiler.prototype.splitUtilityRules = function (utilitiesText) {
3058
3081
  const rules = [];
3059
3082
  let currentRule = '';
3060
- const lines = utilitiesText.split('\n');
3061
-
3062
- for (const line of lines) {
3063
- // Check if this line starts a new rule
3083
+ for (const line of utilitiesText.split('\n')) {
3064
3084
  if (line.trim().match(/^(\.|@media|@layer|@supports)/)) {
3065
- // Save previous rule if exists
3066
- if (currentRule.trim()) {
3067
- rules.push(currentRule.trim());
3068
- }
3085
+ if (currentRule.trim()) rules.push(currentRule.trim());
3069
3086
  currentRule = line;
3070
3087
  } else {
3071
- // Continue current rule
3072
3088
  currentRule += '\n' + line;
3073
3089
  }
3074
3090
  }
3075
- // Add last rule
3076
- if (currentRule.trim()) {
3077
- rules.push(currentRule.trim());
3078
- }
3079
-
3080
- // Sort: base utilities (no @media) come before variants (with @media)
3091
+ if (currentRule.trim()) rules.push(currentRule.trim());
3092
+ return rules;
3093
+ };
3094
+
3095
+ // Sort utilities so base classes come before variants (@media)
3096
+ TailwindCompiler.prototype.sortUtilities = function (utilitiesText) {
3097
+ if (!utilitiesText) return '';
3098
+ const rules = this.splitUtilityRules(utilitiesText);
3081
3099
  rules.sort((a, b) => {
3082
3100
  const aHasMedia = a.startsWith('@media');
3083
3101
  const bHasMedia = b.startsWith('@media');
3084
-
3085
- // Base utilities come before variants
3086
3102
  if (!aHasMedia && bHasMedia) return -1;
3087
3103
  if (aHasMedia && !bHasMedia) return 1;
3088
-
3089
- // Same type, maintain order
3090
3104
  return 0;
3091
3105
  });
3092
-
3093
3106
  return rules.join('\n\n');
3094
3107
  };
3095
3108
 
3109
+ // ---- Incremental apply: new rules go in through CSSOM, never a whole-sheet rewrite ----
3110
+ // Entries mirror the layer's cssRules 1:1, each keyed by where a full regen would emit it
3111
+ // ([media, section, var/entry, pair/array, region, sub]); deltas are inserted at that index.
3112
+
3113
+ const utilitiesCssFrom = (applied) => `@layer utilities {\n${applied.entries.map(e => e.rule).join('\n\n')}\n}`;
3114
+ const isMediaRule = (rule) => rule.trim().startsWith('@media');
3115
+
3116
+ function compareKeys(a, b) {
3117
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i] - b[i];
3118
+ return 0;
3119
+ }
3120
+
3121
+ // Raw rules + order keys for a class set, as a full regen would emit them (base before @media, stable)
3122
+ TailwindCompiler.prototype.generateKeyedUtilities = function (themeCss, classes) {
3123
+ const usedData = { classes, variableSuffixes: [], rulesOut: [], keysOut: [] };
3124
+ this.generateUtilitiesFromVars(themeCss, usedData);
3125
+ this.generateCustomUtilities(usedData);
3126
+ const out = usedData.rulesOut.map((rule, i) => {
3127
+ const media = isMediaRule(rule) ? 1 : 0;
3128
+ return { rule, key: [media].concat(usedData.keysOut[i]) };
3129
+ });
3130
+ return out.filter(e => !e.key[0]).concat(out.filter(e => e.key[0]));
3131
+ };
3132
+
3133
+ TailwindCompiler.prototype.utilitiesLayerRule = function () {
3134
+ const sheet = this.styleElement && this.styleElement.sheet;
3135
+ if (!sheet || typeof CSSLayerBlockRule === 'undefined') return null;
3136
+ try {
3137
+ for (const rule of sheet.cssRules) {
3138
+ if (rule instanceof CSSLayerBlockRule && rule.name === 'utilities') return rule;
3139
+ }
3140
+ } catch (e) { }
3141
+ return null;
3142
+ };
3143
+
3144
+ // Entries must match the parsed sheet rule-for-rule, or deltas could land at the wrong index
3145
+ TailwindCompiler.prototype.entriesMatchSheet = function (entries) {
3146
+ const layer = this.utilitiesLayerRule();
3147
+ if (!layer || layer.cssRules.length !== entries.length) return false;
3148
+ for (let i = 0; i < entries.length; i++) {
3149
+ if ((layer.cssRules[i] instanceof CSSMediaRule) !== !!entries[i].key[0]) return false;
3150
+ }
3151
+ return true;
3152
+ };
3153
+
3154
+ // After a full write: remember its rules so later compiles can insert beside them
3155
+ TailwindCompiler.prototype.recordAppliedUtilities = function (entries, themeCss) {
3156
+ this._applied = null;
3157
+ if (window.__manifestRender === true || !this.entriesMatchSheet(entries)) return;
3158
+ this._applied = {
3159
+ themeKey: this.lastThemeKey,
3160
+ themeCss, // keys index this exact variable order
3161
+ entries,
3162
+ rules: new Set(entries.map(e => e.rule)),
3163
+ classes: new Set(this.dynamicClassCache),
3164
+ dirty: false // inserted rules not yet in textContent
3165
+ };
3166
+ };
3167
+
3168
+ // Moving <style> rebuilds its sheet from textContent: write inserted rules back first
3169
+ TailwindCompiler.prototype.resyncUtilitiesText = function () {
3170
+ const applied = this._applied;
3171
+ if (!applied || !applied.dirty) return;
3172
+ applied.dirty = false;
3173
+ this.styleElement.textContent = utilitiesCssFrom(applied);
3174
+ if (!this.entriesMatchSheet(applied.entries)) this._applied = null;
3175
+ };
3176
+
3177
+ // Prerender snapshots read #manifest-styles text, so render passes always rewrite it
3178
+ TailwindCompiler.prototype.canCompileDelta = function () {
3179
+ return !!this._applied && window.__manifestRender !== true && !!this.utilitiesLayerRule();
3180
+ };
3181
+
3182
+ // Generate only never-compiled classes and insert each rule where a full regen would put it.
3183
+ // Returns false when a rule's place depends on class discovery order: caller does a full compile.
3184
+ TailwindCompiler.prototype.compileDelta = function (themeCss) {
3185
+ const applied = this._applied;
3186
+ const candidates = [];
3187
+ for (const cls of this.dynamicClassCache) if (!applied.classes.has(cls)) candidates.push(cls);
3188
+ const classes = this.filterStaticallyCoveredClasses(candidates);
3189
+ const fresh = classes.length ? this.generateKeyedUtilities(themeCss, classes).filter(e => !applied.rules.has(e.rule)) : [];
3190
+ const entries = applied.entries;
3191
+
3192
+ // Place every rule first; bail before touching the sheet if any is ambiguous
3193
+ const plan = [];
3194
+ for (const e of fresh) {
3195
+ let lo = 0, hi = entries.length;
3196
+ while (lo < hi) { const mid = (lo + hi) >> 1; if (compareKeys(entries[mid].key, e.key) <= 0) lo = mid + 1; else hi = mid; }
3197
+ // Same utility, same group, both variants: full regen orders them by discovery
3198
+ if (e.key[5] === 2) {
3199
+ const sameSlot = (x) => x && compareKeys(x.key.slice(0, 5), e.key.slice(0, 5)) === 0 && x.key[5] === 2;
3200
+ if (sameSlot(entries[lo - 1]) || sameSlot(entries[lo]) || plan.some(q => sameSlot(q.e))) return false;
3201
+ }
3202
+ plan.push({ e, at: lo });
3203
+ }
3204
+ for (const cls of candidates) applied.classes.add(cls);
3205
+ if (!plan.length) return true;
3206
+
3207
+ const layer = this.utilitiesLayerRule();
3208
+ try {
3209
+ // Insert in key order; each insert shifts later indices by one
3210
+ plan.sort((x, y) => compareKeys(x.e.key, y.e.key));
3211
+ plan.forEach((q, n) => {
3212
+ layer.insertRule(q.e.rule, q.at + n);
3213
+ entries.splice(q.at + n, 0, q.e);
3214
+ applied.rules.add(q.e.rule);
3215
+ });
3216
+ } catch (err) {
3217
+ // A rule the parser splits or drops: the planned indices no longer hold
3218
+ this._applied = null;
3219
+ return false;
3220
+ }
3221
+ applied.dirty = true;
3222
+ this.schedulePersistentSave(themeCss);
3223
+ return true;
3224
+ };
3225
+
3226
+ // Coalesce cache writes: localStorage serialisation is too slow to run per compile
3227
+ TailwindCompiler.prototype.schedulePersistentSave = function (themeCss) {
3228
+ clearTimeout(this._persistTimer);
3229
+ this._persistTimer = setTimeout(() => {
3230
+ if (!this._applied) return;
3231
+ const classesHash = Array.from(this.dynamicClassCache).sort().join(',');
3232
+ const themeHash = this.generateThemeHash(themeCss);
3233
+ this.cache.set(`${classesHash}-${themeHash}`, {
3234
+ css: utilitiesCssFrom(this._applied),
3235
+ timestamp: Date.now(),
3236
+ themeHash
3237
+ });
3238
+ this.savePersistentCache();
3239
+ }, 1000);
3240
+ };
3241
+
3096
3242
  // Helper function to filter critical utilities that are already in layer utilities
3097
3243
  TailwindCompiler.prototype.filterCriticalUtilities = function(criticalText, layerUtilities) {
3098
3244
  if (!criticalText || !layerUtilities) return '';
@@ -3196,6 +3342,7 @@ TailwindCompiler.prototype.compile = async function () {
3196
3342
  }
3197
3343
  this.lastCompileTime = now;
3198
3344
  this.isCompiling = true;
3345
+ this.quietClasses.clear(); // this compile covers them
3199
3346
  window.__manifestUtilitiesPending = (window.__manifestUtilitiesPending || 0) + 1;
3200
3347
  this._compileCounted = true;
3201
3348
 
@@ -3286,6 +3433,13 @@ TailwindCompiler.prototype.compile = async function () {
3286
3433
  return;
3287
3434
  }
3288
3435
 
3436
+ // Theme unchanged since the last full write: insert only the new classes
3437
+ let prefetchedTheme = null;
3438
+ if (this.canCompileDelta()) {
3439
+ prefetchedTheme = await this.fetchThemeContent();
3440
+ if (prefetchedTheme && this.lastThemeKey === this._applied.themeKey && this.compileDelta(this._applied.themeCss)) return;
3441
+ }
3442
+
3289
3443
  // For subsequent compilations, check for new dynamic classes
3290
3444
  const usedData = this.getUsedClasses();
3291
3445
  const dynamicClasses = Array.from(this.dynamicClassCache);
@@ -3296,7 +3450,7 @@ TailwindCompiler.prototype.compile = async function () {
3296
3450
  // Check if dynamic classes have actually changed
3297
3451
  if (dynamicClassesHash !== this.lastClassesHash || !this.hasInitialized) {
3298
3452
  // Fetch CSS content for dynamic compilation
3299
- const themeCss = await this.fetchThemeContent();
3453
+ const themeCss = prefetchedTheme || await this.fetchThemeContent();
3300
3454
  if (!themeCss) {
3301
3455
  this.isCompiling = false;
3302
3456
  return;
@@ -3323,8 +3477,10 @@ TailwindCompiler.prototype.compile = async function () {
3323
3477
  if (hasVariableChanges || dynamicClassesHash !== this.lastClassesHash) {
3324
3478
 
3325
3479
  // Generate both variable-based and custom utilities
3326
- const varUtilities = this.generateUtilitiesFromVars(themeCss, usedData);
3327
- const customUtilitiesGenerated = this.generateCustomUtilities(usedData);
3480
+ const rulesOut = [], keysOut = [];
3481
+ const keyedData = Object.assign({}, usedData, { rulesOut, keysOut });
3482
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, keyedData);
3483
+ const customUtilitiesGenerated = this.generateCustomUtilities(keyedData);
3328
3484
 
3329
3485
  let allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
3330
3486
  // Sort utilities so base classes come before variants
@@ -3350,6 +3506,8 @@ TailwindCompiler.prototype.compile = async function () {
3350
3506
  });
3351
3507
  });
3352
3508
  this.lastClassesHash = dynamicClassesHash;
3509
+ const entries = rulesOut.map((rule, i) => ({ rule, key: [isMediaRule(rule) ? 1 : 0].concat(keysOut[i]) }));
3510
+ this.recordAppliedUtilities(entries.filter(e => !e.key[0]).concat(entries.filter(e => e.key[0])), themeCss);
3353
3511
 
3354
3512
  // Save to cache for next page load
3355
3513
  const themeHash = this.generateThemeHash(themeCss);
@@ -3361,6 +3519,8 @@ TailwindCompiler.prototype.compile = async function () {
3361
3519
  });
3362
3520
  this.savePersistentCache();
3363
3521
  }
3522
+ } else {
3523
+ this._applied = null; // theme text moved: order keys are stale until the next full write
3364
3524
  }
3365
3525
  }
3366
3526
 
@@ -3826,80 +3986,29 @@ TailwindCompiler.prototype.setupComponentLoadListener = function () {
3826
3986
  }
3827
3987
  });
3828
3988
 
3829
- // Single MutationObserver for all DOM changes
3989
+ // Single MutationObserver for all DOM changes: recompile only when a class not yet seen appears
3830
3990
  const observer = new MutationObserver((mutations) => {
3831
- let shouldRecompile = false;
3991
+ let fresh = false;
3832
3992
 
3833
3993
  for (const mutation of mutations) {
3834
- // Skip attribute changes that don't affect utilities
3835
3994
  if (mutation.type === 'attributes') {
3836
- const attributeName = mutation.attributeName;
3837
-
3838
- // Skip ignored attributes (like id changes from router)
3839
- if (this.ignoredAttributes.includes(attributeName)) {
3840
- continue;
3841
- }
3842
-
3843
- // Only care about class attribute changes
3844
- if (attributeName !== 'class') {
3845
- continue;
3846
- }
3847
-
3848
- // If it's a class change, check if we have new classes that need utilities
3849
- const element = mutation.target;
3850
- if (element.nodeType === Node.ELEMENT_NODE) {
3851
- const currentClasses = Array.from(element.classList || []);
3852
- const newClasses = currentClasses.filter(cls => {
3853
- // Skip ignored patterns
3854
- if (this.ignoredClassPatterns.some(pattern => pattern.test(cls))) {
3855
- return false;
3856
- }
3857
-
3858
- // Check if this class is new (not in our cache)
3859
- return !this.staticClassCache.has(cls) && !this.dynamicClassCache.has(cls);
3860
- });
3861
-
3862
- if (newClasses.length > 0) {
3863
- // Add new classes to dynamic cache
3864
- newClasses.forEach(cls => this.dynamicClassCache.add(cls));
3865
- shouldRecompile = true;
3866
- break;
3867
- }
3868
- }
3869
- }
3870
- else if (mutation.type === 'childList') {
3995
+ if (this.ignoredAttributes.includes(mutation.attributeName) || mutation.attributeName !== 'class') continue;
3996
+ if (mutation.target.nodeType === Node.ELEMENT_NODE && this.collectNewClasses(mutation.target)) fresh = true;
3997
+ } else if (mutation.type === 'childList') {
3871
3998
  for (const node of mutation.addedNodes) {
3872
- if (node.nodeType === Node.ELEMENT_NODE) {
3873
- // Skip ignored elements using configurable selectors
3874
- const isIgnoredElement = this.ignoredElementSelectors.some(selector =>
3875
- node.tagName?.toLowerCase() === selector.toLowerCase() ||
3876
- node.closest(selector)
3877
- );
3878
-
3879
- if (isIgnoredElement) {
3880
- continue;
3881
- }
3882
-
3883
- // Only recompile for significant changes using configurable selectors
3884
- const hasSignificantChange = this.significantChangeSelectors.some(selector => {
3885
- try {
3886
- return node.matches?.(selector) || node.querySelector?.(selector);
3887
- } catch (e) {
3888
- return false; // Invalid selector
3889
- }
3890
- });
3891
-
3892
- if (hasSignificantChange) {
3893
- shouldRecompile = true;
3894
- break;
3895
- }
3896
- }
3999
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
4000
+ const isIgnoredElement = this.ignoredElementSelectors.some(selector =>
4001
+ node.tagName?.toLowerCase() === selector.toLowerCase() ||
4002
+ node.closest(selector)
4003
+ );
4004
+ // Ignored subtrees (code blocks) are collected for the next compile but never trigger one
4005
+ if (this.collectSubtreeClasses(node, isIgnoredElement)) fresh = true;
4006
+ else if (!fresh && !isIgnoredElement && !this.hasInitialized && this.isSignificantNode(node)) fresh = true;
3897
4007
  }
3898
4008
  }
3899
- if (shouldRecompile) break;
3900
4009
  }
3901
4010
 
3902
- if (shouldRecompile) {
4011
+ if (fresh) {
3903
4012
  debouncedCompile();
3904
4013
  }
3905
4014
  });
@@ -3913,6 +4022,43 @@ TailwindCompiler.prototype.setupComponentLoadListener = function () {
3913
4022
  });
3914
4023
  };
3915
4024
 
4025
+ // Record classes not seen before; true when any were new. Quiet = collect without triggering
4026
+ TailwindCompiler.prototype.collectNewClasses = function (el, quiet) {
4027
+ const value = el.getAttribute && el.getAttribute('class');
4028
+ if (!value) return false;
4029
+ let fresh = false;
4030
+ for (const cls of value.split(/\s+/)) {
4031
+ if (!cls || this.staticClassCache.has(cls)) continue;
4032
+ if (this.dynamicClassCache.has(cls)) {
4033
+ if (!quiet && this.quietClasses.delete(cls)) fresh = true;
4034
+ continue;
4035
+ }
4036
+ if (this.ignoredClassPatterns.some(pattern => pattern.test(cls))) continue;
4037
+ this.dynamicClassCache.add(cls);
4038
+ if (quiet) this.quietClasses.add(cls); else fresh = true;
4039
+ }
4040
+ return fresh;
4041
+ };
4042
+
4043
+ TailwindCompiler.prototype.collectSubtreeClasses = function (node, quiet) {
4044
+ let fresh = this.collectNewClasses(node, quiet);
4045
+ const descendants = node.getElementsByTagName ? node.getElementsByTagName('*') : [];
4046
+ for (let i = 0; i < descendants.length; i++) {
4047
+ if (this.collectNewClasses(descendants[i], quiet)) fresh = true;
4048
+ }
4049
+ return fresh;
4050
+ };
4051
+
4052
+ TailwindCompiler.prototype.isSignificantNode = function (node) {
4053
+ return this.significantChangeSelectors.some(selector => {
4054
+ try {
4055
+ return node.matches?.(selector) || node.querySelector?.(selector);
4056
+ } catch (e) {
4057
+ return false;
4058
+ }
4059
+ });
4060
+ };
4061
+
3916
4062
  // Initial compilation only. DOM observation is owned by
3917
4063
  // setupComponentLoadListener (incremental, scales to thousands of elements);
3918
4064
  // don't add a per-mutation getUsedClasses() scan here — it froze the main