mnfst 0.5.218 → 0.5.220

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.
@@ -617,6 +617,7 @@ class TailwindCompiler {
617
617
  this.lastClassesHash = ''; // Track changes in used classes
618
618
  this.staticClassCache = new Set(); // Cache classes found in static HTML/components
619
619
  this.dynamicClassCache = new Set(); // Cache classes that appear dynamically
620
+ this.quietClasses = new Set(); // Seen only inside ignored elements, not yet compiled
620
621
  this.hasScannedStatic = false; // Track if we've done initial static scan
621
622
  this.staticScanPromise = null; // Promise for initial static scan
622
623
  this.ignoredClassPatterns = [ // Patterns for classes to ignore
@@ -772,6 +773,7 @@ class TailwindCompiler {
772
773
  TailwindCompiler.prototype.ensureUtilityStylesLast = function () {
773
774
  if (this.styleElement && this.styleElement.parentNode && document.head.lastElementChild !== this.styleElement) {
774
775
  document.head.appendChild(this.styleElement);
776
+ if (this.resyncUtilitiesText) this.resyncUtilitiesText();
775
777
  }
776
778
  };
777
779
 
@@ -1148,6 +1150,8 @@ TailwindCompiler.prototype.fetchThemeContent = async function () {
1148
1150
  // Wait for all fetches to complete
1149
1151
  await Promise.all(fetchPromises);
1150
1152
 
1153
+ // Order-free identity (fetches land in any order) for "theme unchanged" checks
1154
+ this.lastThemeKey = Array.from(themeContents).sort().join('\n');
1151
1155
  return Array.from(themeContents).join('\n');
1152
1156
  };
1153
1157
 
@@ -1884,6 +1888,10 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
1884
1888
  const generatedRules = new Set(); // Track generated rules to prevent duplicates
1885
1889
  const variables = this.extractThemeVariables(cssText);
1886
1890
  const { classes: usedClasses, variableSuffixes } = usedData;
1891
+ // Optional: raw rules + their full-regen order keys (incremental apply places deltas by them)
1892
+ const rulesOut = usedData.rulesOut, keysOut = usedData.keysOut;
1893
+ let slot = null;
1894
+ 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
1887
1895
 
1888
1896
  if (variables.size === 0) {
1889
1897
  return '';
@@ -1908,7 +1916,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
1908
1916
  if (usedClasses.includes(baseClass)) {
1909
1917
  const rule = `.${escapeClassName(baseClass)} { ${css} }`;
1910
1918
  if (!generatedRules.has(rule)) {
1911
- utilities.push(rule);
1919
+ emit(rule, 0);
1912
1920
  generatedRules.add(rule);
1913
1921
  }
1914
1922
  }
@@ -1919,7 +1927,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
1919
1927
  css + ' !important';
1920
1928
  const rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
1921
1929
  if (!generatedRules.has(rule)) {
1922
- utilities.push(rule);
1930
+ emit(rule, 1);
1923
1931
  generatedRules.add(rule);
1924
1932
  }
1925
1933
  }
@@ -1988,26 +1996,33 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
1988
1996
  rule;
1989
1997
 
1990
1998
  if (!generatedRules.has(finalRule)) {
1991
- utilities.push(finalRule);
1999
+ emit(finalRule, 2);
1992
2000
  generatedRules.add(finalRule);
1993
2001
  }
1994
2002
  }
1995
2003
  };
1996
2004
 
2005
+ // Every generated class ends in its variable's suffix: skip variables no used class mentions
2006
+ const usedBases = usedClasses.map(cls => this.parseClassName(cls).baseClass);
2007
+
1997
2008
  // Generate utilities based on variable prefix
2009
+ let vi = -1;
1998
2010
  for (const [varName, varValue] of variables.entries()) {
2011
+ vi++;
1999
2012
  if (!varName.match(this.regexPatterns.tailwindPrefix)) {
2000
2013
  continue;
2001
2014
  }
2002
2015
 
2003
2016
  const suffix = varName.split('-').slice(1).join('-');
2017
+ if (!usedBases.some(base => base.includes(suffix))) continue;
2004
2018
  const value = `var(--${varName})`;
2005
2019
  const prefix = varName.split('-')[0] + '-';
2006
2020
  const generator = this.utilityGenerators[prefix];
2007
2021
 
2008
2022
  if (generator) {
2009
2023
  const utilityPairs = generator(suffix, value);
2010
- for (const [className, css] of utilityPairs) {
2024
+ for (let pi = 0; pi < utilityPairs.length; pi++) {
2025
+ const [className, css] = utilityPairs[pi];
2011
2026
  // Check if this specific utility class is actually used (including variants and important)
2012
2027
  const isUsed = usedClasses.some(cls => {
2013
2028
  // Parse the class to extract the base utility name
@@ -2020,6 +2035,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2020
2035
  (baseClass.startsWith('!') && baseClass.slice(1) === className);
2021
2036
  });
2022
2037
  if (isUsed) {
2038
+ slot = [0, vi, pi, 0];
2023
2039
  generateUtility(className, css);
2024
2040
  }
2025
2041
 
@@ -2051,6 +2067,7 @@ TailwindCompiler.prototype.generateUtilitiesFromVars = function (cssText, usedDa
2051
2067
  const opacity = opacityBaseClass.split('/')[1];
2052
2068
  const opacityValue = `color-mix(in oklch, ${value} ${opacity}%, transparent)`;
2053
2069
  const opacityCss = css.replace(value, opacityValue);
2070
+ slot = [0, vi, pi, 1];
2054
2071
  generateUtility(opacityBaseClass, opacityCss);
2055
2072
  }
2056
2073
  }
@@ -2070,6 +2087,9 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2070
2087
  const utilities = [];
2071
2088
  const generatedRules = new Set();
2072
2089
  const { classes: usedClasses } = usedData;
2090
+ const rulesOut = usedData.rulesOut, keysOut = usedData.keysOut;
2091
+ let slot = null;
2092
+ const emit = (rule, sub) => { utilities.push(rule); if (rulesOut) { rulesOut.push(rule); keysOut.push(slot.concat(sub)); } };
2073
2093
 
2074
2094
  // Helper to clean up [object Object] from CSS strings
2075
2095
  const cleanCssString = (css) => {
@@ -2225,7 +2245,7 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2225
2245
  rule = `.${escapeClassName('!' + baseClass)} { ${importantCss} }`;
2226
2246
  }
2227
2247
  if (!generatedRules.has(rule)) {
2228
- utilities.push(rule);
2248
+ emit(rule, 1);
2229
2249
  generatedRules.add(rule);
2230
2250
  }
2231
2251
  }
@@ -2399,14 +2419,16 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2399
2419
  }
2400
2420
 
2401
2421
  if (!generatedRules.has(finalRule)) {
2402
- utilities.push(finalRule);
2422
+ emit(finalRule, 2);
2403
2423
  generatedRules.add(finalRule);
2404
2424
  }
2405
2425
  }
2406
2426
  };
2407
2427
 
2408
2428
  // Generate utilities for each custom class that's actually used
2429
+ let ci = -1;
2409
2430
  for (const [className, cssOrSelector] of this.customUtilities.entries()) {
2431
+ ci++;
2410
2432
  // Normalize class name: if it starts with !, extract the base name
2411
2433
  const hasImportantPrefix = className.startsWith('!');
2412
2434
  const baseClassName = hasImportantPrefix ? className.slice(1) : className;
@@ -2446,9 +2468,12 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2446
2468
  // Generate utility with base class name (without !)
2447
2469
  // The CSS already has !important if className started with !
2448
2470
  if (typeof cssOrSelector === 'string') {
2471
+ slot = [1, ci, 0, 0];
2449
2472
  generateUtility(baseClassName, normalizedCss, null);
2450
2473
  } else if (Array.isArray(cssOrSelector)) {
2451
- for (const entry of cssOrSelector) {
2474
+ for (let ai = 0; ai < cssOrSelector.length; ai++) {
2475
+ const entry = cssOrSelector[ai];
2476
+ slot = [1, ci, ai, 0];
2452
2477
  if (entry && entry.css && entry.selector) {
2453
2478
  // Ensure entry.css is a string (not an object)
2454
2479
  const extracted = typeof entry.css === 'string' ? entry.css :
@@ -2469,6 +2494,7 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2469
2494
  String(cssOrSelector.css));
2470
2495
  const selectorCss = cleanCssString(extracted);
2471
2496
 
2497
+ slot = [1, ci, 0, 0];
2472
2498
  generateUtility(baseClassName, selectorCss, {
2473
2499
  selector: cssOrSelector.selector,
2474
2500
  fullBlock: cssOrSelector.fullBlock || false
@@ -2484,49 +2510,169 @@ TailwindCompiler.prototype.generateCustomUtilities = function (usedData) {
2484
2510
  }
2485
2511
  };
2486
2512
 
2487
- // Helper function to sort utilities so base classes come before variants
2488
- TailwindCompiler.prototype.sortUtilities = function(utilitiesText) {
2489
- if (!utilitiesText) return '';
2490
-
2491
- // Split into individual rules (each rule starts with . or @media)
2513
+ // Split generated CSS into top-level rules (each starts with . or an at-rule)
2514
+ TailwindCompiler.prototype.splitUtilityRules = function (utilitiesText) {
2492
2515
  const rules = [];
2493
2516
  let currentRule = '';
2494
- const lines = utilitiesText.split('\n');
2495
-
2496
- for (const line of lines) {
2497
- // Check if this line starts a new rule
2517
+ for (const line of utilitiesText.split('\n')) {
2498
2518
  if (line.trim().match(/^(\.|@media|@layer|@supports)/)) {
2499
- // Save previous rule if exists
2500
- if (currentRule.trim()) {
2501
- rules.push(currentRule.trim());
2502
- }
2519
+ if (currentRule.trim()) rules.push(currentRule.trim());
2503
2520
  currentRule = line;
2504
2521
  } else {
2505
- // Continue current rule
2506
2522
  currentRule += '\n' + line;
2507
2523
  }
2508
2524
  }
2509
- // Add last rule
2510
- if (currentRule.trim()) {
2511
- rules.push(currentRule.trim());
2512
- }
2513
-
2514
- // Sort: base utilities (no @media) come before variants (with @media)
2525
+ if (currentRule.trim()) rules.push(currentRule.trim());
2526
+ return rules;
2527
+ };
2528
+
2529
+ // Sort utilities so base classes come before variants (@media)
2530
+ TailwindCompiler.prototype.sortUtilities = function (utilitiesText) {
2531
+ if (!utilitiesText) return '';
2532
+ const rules = this.splitUtilityRules(utilitiesText);
2515
2533
  rules.sort((a, b) => {
2516
2534
  const aHasMedia = a.startsWith('@media');
2517
2535
  const bHasMedia = b.startsWith('@media');
2518
-
2519
- // Base utilities come before variants
2520
2536
  if (!aHasMedia && bHasMedia) return -1;
2521
2537
  if (aHasMedia && !bHasMedia) return 1;
2522
-
2523
- // Same type, maintain order
2524
2538
  return 0;
2525
2539
  });
2526
-
2527
2540
  return rules.join('\n\n');
2528
2541
  };
2529
2542
 
2543
+ // ---- Incremental apply: new rules go in through CSSOM, never a whole-sheet rewrite ----
2544
+ // Entries mirror the layer's cssRules 1:1, each keyed by where a full regen would emit it
2545
+ // ([media, section, var/entry, pair/array, region, sub]); deltas are inserted at that index.
2546
+
2547
+ const utilitiesCssFrom = (applied) => `@layer utilities {\n${applied.entries.map(e => e.rule).join('\n\n')}\n}`;
2548
+ const isMediaRule = (rule) => rule.trim().startsWith('@media');
2549
+
2550
+ function compareKeys(a, b) {
2551
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return a[i] - b[i];
2552
+ return 0;
2553
+ }
2554
+
2555
+ // Raw rules + order keys for a class set, as a full regen would emit them (base before @media, stable)
2556
+ TailwindCompiler.prototype.generateKeyedUtilities = function (themeCss, classes) {
2557
+ const usedData = { classes, variableSuffixes: [], rulesOut: [], keysOut: [] };
2558
+ this.generateUtilitiesFromVars(themeCss, usedData);
2559
+ this.generateCustomUtilities(usedData);
2560
+ const out = usedData.rulesOut.map((rule, i) => {
2561
+ const media = isMediaRule(rule) ? 1 : 0;
2562
+ return { rule, key: [media].concat(usedData.keysOut[i]) };
2563
+ });
2564
+ return out.filter(e => !e.key[0]).concat(out.filter(e => e.key[0]));
2565
+ };
2566
+
2567
+ TailwindCompiler.prototype.utilitiesLayerRule = function () {
2568
+ const sheet = this.styleElement && this.styleElement.sheet;
2569
+ if (!sheet || typeof CSSLayerBlockRule === 'undefined') return null;
2570
+ try {
2571
+ for (const rule of sheet.cssRules) {
2572
+ if (rule instanceof CSSLayerBlockRule && rule.name === 'utilities') return rule;
2573
+ }
2574
+ } catch (e) { }
2575
+ return null;
2576
+ };
2577
+
2578
+ // Entries must match the parsed sheet rule-for-rule, or deltas could land at the wrong index
2579
+ TailwindCompiler.prototype.entriesMatchSheet = function (entries) {
2580
+ const layer = this.utilitiesLayerRule();
2581
+ if (!layer || layer.cssRules.length !== entries.length) return false;
2582
+ for (let i = 0; i < entries.length; i++) {
2583
+ if ((layer.cssRules[i] instanceof CSSMediaRule) !== !!entries[i].key[0]) return false;
2584
+ }
2585
+ return true;
2586
+ };
2587
+
2588
+ // After a full write: remember its rules so later compiles can insert beside them
2589
+ TailwindCompiler.prototype.recordAppliedUtilities = function (entries, themeCss) {
2590
+ this._applied = null;
2591
+ if (window.__manifestRender === true || !this.entriesMatchSheet(entries)) return;
2592
+ this._applied = {
2593
+ themeKey: this.lastThemeKey,
2594
+ themeCss, // keys index this exact variable order
2595
+ entries,
2596
+ rules: new Set(entries.map(e => e.rule)),
2597
+ classes: new Set(this.dynamicClassCache),
2598
+ dirty: false // inserted rules not yet in textContent
2599
+ };
2600
+ };
2601
+
2602
+ // Moving <style> rebuilds its sheet from textContent: write inserted rules back first
2603
+ TailwindCompiler.prototype.resyncUtilitiesText = function () {
2604
+ const applied = this._applied;
2605
+ if (!applied || !applied.dirty) return;
2606
+ applied.dirty = false;
2607
+ this.styleElement.textContent = utilitiesCssFrom(applied);
2608
+ if (!this.entriesMatchSheet(applied.entries)) this._applied = null;
2609
+ };
2610
+
2611
+ // Prerender snapshots read #manifest-styles text, so render passes always rewrite it
2612
+ TailwindCompiler.prototype.canCompileDelta = function () {
2613
+ return !!this._applied && window.__manifestRender !== true && !!this.utilitiesLayerRule();
2614
+ };
2615
+
2616
+ // Generate only never-compiled classes and insert each rule where a full regen would put it.
2617
+ // Returns false when a rule's place depends on class discovery order: caller does a full compile.
2618
+ TailwindCompiler.prototype.compileDelta = function (themeCss) {
2619
+ const applied = this._applied;
2620
+ const candidates = [];
2621
+ for (const cls of this.dynamicClassCache) if (!applied.classes.has(cls)) candidates.push(cls);
2622
+ const classes = this.filterStaticallyCoveredClasses(candidates);
2623
+ const fresh = classes.length ? this.generateKeyedUtilities(themeCss, classes).filter(e => !applied.rules.has(e.rule)) : [];
2624
+ const entries = applied.entries;
2625
+
2626
+ // Place every rule first; bail before touching the sheet if any is ambiguous
2627
+ const plan = [];
2628
+ for (const e of fresh) {
2629
+ let lo = 0, hi = entries.length;
2630
+ while (lo < hi) { const mid = (lo + hi) >> 1; if (compareKeys(entries[mid].key, e.key) <= 0) lo = mid + 1; else hi = mid; }
2631
+ // Same utility, same group, both variants: full regen orders them by discovery
2632
+ if (e.key[5] === 2) {
2633
+ const sameSlot = (x) => x && compareKeys(x.key.slice(0, 5), e.key.slice(0, 5)) === 0 && x.key[5] === 2;
2634
+ if (sameSlot(entries[lo - 1]) || sameSlot(entries[lo]) || plan.some(q => sameSlot(q.e))) return false;
2635
+ }
2636
+ plan.push({ e, at: lo });
2637
+ }
2638
+ for (const cls of candidates) applied.classes.add(cls);
2639
+ if (!plan.length) return true;
2640
+
2641
+ const layer = this.utilitiesLayerRule();
2642
+ try {
2643
+ // Insert in key order; each insert shifts later indices by one
2644
+ plan.sort((x, y) => compareKeys(x.e.key, y.e.key));
2645
+ plan.forEach((q, n) => {
2646
+ layer.insertRule(q.e.rule, q.at + n);
2647
+ entries.splice(q.at + n, 0, q.e);
2648
+ applied.rules.add(q.e.rule);
2649
+ });
2650
+ } catch (err) {
2651
+ // A rule the parser splits or drops: the planned indices no longer hold
2652
+ this._applied = null;
2653
+ return false;
2654
+ }
2655
+ applied.dirty = true;
2656
+ this.schedulePersistentSave(themeCss);
2657
+ return true;
2658
+ };
2659
+
2660
+ // Coalesce cache writes: localStorage serialisation is too slow to run per compile
2661
+ TailwindCompiler.prototype.schedulePersistentSave = function (themeCss) {
2662
+ clearTimeout(this._persistTimer);
2663
+ this._persistTimer = setTimeout(() => {
2664
+ if (!this._applied) return;
2665
+ const classesHash = Array.from(this.dynamicClassCache).sort().join(',');
2666
+ const themeHash = this.generateThemeHash(themeCss);
2667
+ this.cache.set(`${classesHash}-${themeHash}`, {
2668
+ css: utilitiesCssFrom(this._applied),
2669
+ timestamp: Date.now(),
2670
+ themeHash
2671
+ });
2672
+ this.savePersistentCache();
2673
+ }, 1000);
2674
+ };
2675
+
2530
2676
  // Helper function to filter critical utilities that are already in layer utilities
2531
2677
  TailwindCompiler.prototype.filterCriticalUtilities = function(criticalText, layerUtilities) {
2532
2678
  if (!criticalText || !layerUtilities) return '';
@@ -2630,6 +2776,7 @@ TailwindCompiler.prototype.compile = async function () {
2630
2776
  }
2631
2777
  this.lastCompileTime = now;
2632
2778
  this.isCompiling = true;
2779
+ this.quietClasses.clear(); // this compile covers them
2633
2780
  window.__manifestUtilitiesPending = (window.__manifestUtilitiesPending || 0) + 1;
2634
2781
  this._compileCounted = true;
2635
2782
 
@@ -2720,6 +2867,13 @@ TailwindCompiler.prototype.compile = async function () {
2720
2867
  return;
2721
2868
  }
2722
2869
 
2870
+ // Theme unchanged since the last full write: insert only the new classes
2871
+ let prefetchedTheme = null;
2872
+ if (this.canCompileDelta()) {
2873
+ prefetchedTheme = await this.fetchThemeContent();
2874
+ if (prefetchedTheme && this.lastThemeKey === this._applied.themeKey && this.compileDelta(this._applied.themeCss)) return;
2875
+ }
2876
+
2723
2877
  // For subsequent compilations, check for new dynamic classes
2724
2878
  const usedData = this.getUsedClasses();
2725
2879
  const dynamicClasses = Array.from(this.dynamicClassCache);
@@ -2730,7 +2884,7 @@ TailwindCompiler.prototype.compile = async function () {
2730
2884
  // Check if dynamic classes have actually changed
2731
2885
  if (dynamicClassesHash !== this.lastClassesHash || !this.hasInitialized) {
2732
2886
  // Fetch CSS content for dynamic compilation
2733
- const themeCss = await this.fetchThemeContent();
2887
+ const themeCss = prefetchedTheme || await this.fetchThemeContent();
2734
2888
  if (!themeCss) {
2735
2889
  this.isCompiling = false;
2736
2890
  return;
@@ -2757,8 +2911,10 @@ TailwindCompiler.prototype.compile = async function () {
2757
2911
  if (hasVariableChanges || dynamicClassesHash !== this.lastClassesHash) {
2758
2912
 
2759
2913
  // Generate both variable-based and custom utilities
2760
- const varUtilities = this.generateUtilitiesFromVars(themeCss, usedData);
2761
- const customUtilitiesGenerated = this.generateCustomUtilities(usedData);
2914
+ const rulesOut = [], keysOut = [];
2915
+ const keyedData = Object.assign({}, usedData, { rulesOut, keysOut });
2916
+ const varUtilities = this.generateUtilitiesFromVars(themeCss, keyedData);
2917
+ const customUtilitiesGenerated = this.generateCustomUtilities(keyedData);
2762
2918
 
2763
2919
  let allUtilities = [varUtilities, customUtilitiesGenerated].filter(Boolean).join('\n\n');
2764
2920
  // Sort utilities so base classes come before variants
@@ -2784,6 +2940,8 @@ TailwindCompiler.prototype.compile = async function () {
2784
2940
  });
2785
2941
  });
2786
2942
  this.lastClassesHash = dynamicClassesHash;
2943
+ const entries = rulesOut.map((rule, i) => ({ rule, key: [isMediaRule(rule) ? 1 : 0].concat(keysOut[i]) }));
2944
+ this.recordAppliedUtilities(entries.filter(e => !e.key[0]).concat(entries.filter(e => e.key[0])), themeCss);
2787
2945
 
2788
2946
  // Save to cache for next page load
2789
2947
  const themeHash = this.generateThemeHash(themeCss);
@@ -2795,6 +2953,8 @@ TailwindCompiler.prototype.compile = async function () {
2795
2953
  });
2796
2954
  this.savePersistentCache();
2797
2955
  }
2956
+ } else {
2957
+ this._applied = null; // theme text moved: order keys are stale until the next full write
2798
2958
  }
2799
2959
  }
2800
2960
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.218",
3
+ "version": "0.5.220",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",