mnfst 0.5.203 → 0.5.204

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.203';
5
+ const MANIFEST_BUILD_VERSION = '0.5.204';
6
6
 
7
7
  /* Manifest Data Sources - Appwrite Integration */
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  (function () {
4
4
 
5
- const MANIFEST_BUILD_VERSION = '0.5.203';
5
+ const MANIFEST_BUILD_VERSION = '0.5.204';
6
6
 
7
7
  /* Manifest Data Sources - Configuration */
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
3
- "manifest.appwrite.data.js": "sha384-GWqV1tsC6nmAReFVHu/Ks/0BGFrP/dx4kl1Us0p+c4V5JRzgt64+hRlkhqE03EKC",
3
+ "manifest.appwrite.data.js": "sha384-4mU2U/aADGu/axzp+8iGdn2Ckz0vpnPb6dLVkbkU+SN2MKE8Td8Ayj+jsIa4lnfg",
4
4
  "manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
5
5
  "manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
6
6
  "manifest.charts.js": "sha384-qQrjhWkIDi4oU5uQrpy8DdlMjY+gfiivBuX6mssNiFdkawgkVBvKneGgp+nd3PYJ",
@@ -11,7 +11,7 @@
11
11
  "manifest.combobox.js": "sha384-SN3XHumoebxcduFQqx5zt65/Cd10y8nuf9Gkco6UnsZ42sadRd+gvVLfWPdkkuPH",
12
12
  "manifest.components.js": "sha384-DLGSqCbnGkS87AWJ8GpKoAFP4nCosgC5SFSg6C10hH/nWu/MxI+0ntHlnFEfs++u",
13
13
  "manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
14
- "manifest.data.js": "sha384-9g/81jTkiYnBiRW3e1s0wxmtTTUYeGcrch3ry7ki7S/Gv6B3IMEtB7F9N4zk4U9H",
14
+ "manifest.data.js": "sha384-xbpINo3TIpOj+PcdJ3bjNLFq+OFJWn7+F7RIRYOzrk9SJj9cmZpUfe5LLI+NS3j8",
15
15
  "manifest.datepicker.js": "sha384-8vTJYkLqqvgXgm1w1becuaRR/jUEpiLBjBJ6RsRbUr+ZkypvspD6EsssuDrX4SQq",
16
16
  "manifest.defer.js": "sha384-wXa60M2OHigi5rBurXQ7Oa8eyr5WTKs7EUn2GOru62KMdfs/LDyNegxCc9MNJFSe",
17
17
  "manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
@@ -28,14 +28,14 @@
28
28
  "manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
29
29
  "manifest.status.js": "sha384-nwvUSbT+gpRMqhYx6MeBYswUMH72rY92mjgxv0vrSG7Ht7l83XYpEB6fCTbXjjfK",
30
30
  "manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
31
- "manifest.sw.js": "sha384-Te2cSGBZQmK79BME5Zg5i7Hle5ncBSvJFI94CiGduupVSuDdBBTUstBTXpqp/QgY",
31
+ "manifest.sw.js": "sha384-8hvTjUEMILGzSsus1RfsHDgkpre+UFp7pr7vN0gCtw1fzTrvPauDokRuuoGur6pt",
32
32
  "manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
33
33
  "manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
34
34
  "manifest.text.edit.js": "sha384-pfhc4IPKNJokj6ldlgD+KwGLN4UoM21CNeVf2sVq/34X1Un2PUhEuyTg5itmtsXS",
35
35
  "manifest.toasts.js": "sha384-/OV32LLMJl0Iw8NoNQJ+5lgntLxjIJq/YNj5JAKS/+nUhVx4MMB9h1FOqP7zuj2M",
36
36
  "manifest.tooltips.js": "sha384-uXVVQADl6pY2Pj9i/UaSclZN7dzbKhLkmevv7Ad0+wXR8LgqodALNFrc5jZJjlwm",
37
37
  "manifest.url.parameters.js": "sha384-J2aSVBOPsoqsbafHndrWLHEDpKb4AKJAoQnVmKA4gZh5P/8HHjFWHO/lkusmG84l",
38
- "manifest.utilities.js": "sha384-frvYhLP8Arx+smhzQr1nCvxuVfnjRhoG+ce8pI0H0iYqJwO7km1/3QLdBYt3uKol",
38
+ "manifest.utilities.js": "sha384-3PdKl/ARgpezheDE9thLyrN3sEuacROPmBALAuo/SKGHDHDBqQzXBD6ilvoMn95F",
39
39
  "manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
40
- "manifest.js": "sha384-z6bjoNHfkJwD3bRcjChzuzM3y/3LrWVfc8nsApLtSXcshgJc8L4+Vz7XaV3MN6N8"
40
+ "manifest.js": "sha384-jH0veyUdnBMoGwHxgUQ9Yjs0UneDMI/fYIsg1U58YPZwriKJ6Rv/aoA7RbLepoGa"
41
41
  }
package/lib/manifest.js CHANGED
@@ -187,6 +187,11 @@
187
187
  // Configuration
188
188
  const DEFAULT_VERSION = 'latest';
189
189
 
190
+ // The version the page booted with (data-version). Every later load —
191
+ // Manifest.loadPlugin(), usage sniffing — resolves against it, so a runtime
192
+ // load can't pull a second copy of the framework at another version.
193
+ let RESOLVED_VERSION = DEFAULT_VERSION;
194
+
190
195
  // CDN fallback chain (first-party first). Each origin serves the npm scheme
191
196
  // `<origin>/<pkg>@<version>/<path>`. Override order with `data-cdn`
192
197
  // (comma-separated origins).
@@ -298,7 +303,7 @@
298
303
  // candidate per CDN host, tried in order.
299
304
  let _pluginBase = null;
300
305
  function setPluginBase(b) { _pluginBase = b || null; }
301
- function getPluginUrlCandidates(pluginName, version = DEFAULT_VERSION) {
306
+ function getPluginUrlCandidates(pluginName, version = RESOLVED_VERSION) {
302
307
  // Hyphenated API name → dotted file name (`appwrite-auth` → appwrite.auth).
303
308
  const fileName = pluginName.replace(/-/g, '.');
304
309
  if (_pluginBase) {
@@ -307,7 +312,7 @@
307
312
  }
308
313
  return CDN_HOSTS.map(h => `${getBaseUrl(version, h)}/${hostFile(h, `manifest.${fileName}.min.js`)}`);
309
314
  }
310
- function getPluginUrl(pluginName, version = DEFAULT_VERSION) {
315
+ function getPluginUrl(pluginName, version = RESOLVED_VERSION) {
311
316
  return getPluginUrlCandidates(pluginName, version)[0];
312
317
  }
313
318
 
@@ -388,14 +393,8 @@
388
393
  return false;
389
394
  }
390
395
 
391
- // One fetch per URL: concurrent callers share the first call's promise
392
- const inflightScripts = new Map();
393
-
394
396
  function injectScript(url) {
395
- const shared = inflightScripts.get(url);
396
- if (shared) return shared;
397
-
398
- const promise = new Promise((resolve, reject) => {
397
+ return new Promise((resolve, reject) => {
399
398
  // Same src already in the DOM (author tag, prerendered HTML, second loader run)
400
399
  const existing = document.querySelector(`script[src="${url}"]`);
401
400
  if (existing) {
@@ -418,16 +417,12 @@
418
417
  script.onerror = () => { script.removeAttribute('data-mnfst-loading'); script.remove(); reject(new Error(`Failed to load ${url}`)); };
419
418
  document.head.appendChild(script);
420
419
  });
421
-
422
- inflightScripts.set(url, promise);
423
- promise.then(() => inflightScripts.delete(url), () => inflightScripts.delete(url)); // dedupe only while in flight
424
- return promise;
425
420
  }
426
421
 
427
422
  // Load a plugin, falling through the CDN chain on error. A fallback insert
428
423
  // lands after already-inserted scripts, so strict cross-plugin execution
429
424
  // order is traded for availability in the (already degraded) fallback case.
430
- async function addScript(pluginName, version = DEFAULT_VERSION) {
425
+ async function fetchPluginScript(pluginName, version) {
431
426
  const urls = getPluginUrlCandidates(pluginName, version);
432
427
  let lastErr = null;
433
428
  for (const url of urls) {
@@ -441,6 +436,26 @@
441
436
  throw lastErr || new Error(`Failed to load ${pluginName}`);
442
437
  }
443
438
 
439
+ // One execution per plugin per page. injectScript dedupes by URL, which two
440
+ // versions of the same plugin slip past — and a second copy of a plugin that
441
+ // owns page-level state (utilities' style element and its order observer)
442
+ // fights the first. A failed load is forgotten so a retry can still run.
443
+ const pluginLoads = new Map();
444
+
445
+ async function addScript(pluginName, version = RESOLVED_VERSION) {
446
+ const prior = pluginLoads.get(pluginName);
447
+ if (prior) {
448
+ if (prior.version !== version) {
449
+ console.warn(`[Manifest Loader] ${pluginName} already loaded at ${prior.version} — ignoring request for ${version}`);
450
+ }
451
+ return prior.promise;
452
+ }
453
+ const promise = fetchPluginScript(pluginName, version);
454
+ pluginLoads.set(pluginName, { version, promise });
455
+ promise.catch(() => pluginLoads.delete(pluginName));
456
+ return promise;
457
+ }
458
+
444
459
  // Resolve plugin dependencies (auto-inject required dependencies)
445
460
  function resolveDependencies(pluginList) {
446
461
  const resolved = [];
@@ -635,8 +650,26 @@
635
650
  };
636
651
  }
637
652
 
653
+ // A publish/render bake stamps `data-mnfst-utilities-complete` on its
654
+ // `<style data-mnfst-utilities>` sheet once it has verified every class
655
+ // scanned from the page's own HTML got a rule (Manifest's + the baked
656
+ // Tailwind pass — see compileUtilities in manifest.utilities.node.mjs).
657
+ // Only the inline `<style>` case is checked: it's synchronous, so this can
658
+ // run before deciding whether to fetch the Tailwind engine at all — a
659
+ // `<link>` sheet would need an async load first, so it's left alone
660
+ // (fail open: Tailwind still loads). No flag, no attribute, any read
661
+ // error → false, so this never skips loading the engine speculatively.
662
+ function staticUtilitiesFullyCovered() {
663
+ try {
664
+ const el = document.querySelector('style[data-mnfst-utilities][data-mnfst-utilities-complete]');
665
+ return !!el;
666
+ } catch (e) {
667
+ return false;
668
+ }
669
+ }
670
+
638
671
  // Load custom Tailwind CDN script, falling through the CDN chain on error
639
- async function loadTailwind(version = DEFAULT_VERSION) {
672
+ async function loadTailwind(version = RESOLVED_VERSION) {
640
673
  const urls = CDN_HOSTS.map(h => `${getBaseUrl(version, h)}/${hostFile(h, 'manifest.tailwind.min.js')}`);
641
674
  let lastErr = null;
642
675
  for (const url of urls) {
@@ -652,7 +685,7 @@
652
685
 
653
686
  // Expose API
654
687
  window.Manifest = {
655
- loadPlugin: function (pluginName, version = DEFAULT_VERSION) {
688
+ loadPlugin: function (pluginName, version = RESOLVED_VERSION) {
656
689
  const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, ...AUTHORING_PLUGINS, 'payments', 'chat', 'device'];
657
690
  if (!allPlugins.includes(pluginName)) {
658
691
  console.warn(`[Manifest Loader] Unknown plugin: ${pluginName}`);
@@ -780,6 +813,7 @@
780
813
 
781
814
  // Parse config and load plugins
782
815
  const config = parseDataAttributes();
816
+ if (config && config.version) RESOLVED_VERSION = config.version;
783
817
  if (config && config.pluginBase) setPluginBase(config.pluginBase);
784
818
  if (config && config.cdn) setCdnHosts(config.cdn);
785
819
  armServiceWorker(config);
@@ -890,7 +924,7 @@
890
924
  console.warn(`[Manifest Loader] Failed to load plugin ${pluginName}:`, error);
891
925
  });
892
926
  });
893
- if (config.tailwind) {
927
+ if (config.tailwind && !staticUtilitiesFullyCovered()) {
894
928
  pluginPromises.push(loadTailwind(config.version).catch(() => { }));
895
929
  }
896
930
  await Promise.all(pluginPromises);
@@ -16,7 +16,7 @@
16
16
 
17
17
  // Build stamps the published version here; unstamped (dev) falls back to the
18
18
  // registration URL's `v`, then 'dev'.
19
- const BUILD_VERSION = '0.5.203';
19
+ const BUILD_VERSION = '0.5.204';
20
20
  const CACHE_PREFIX = 'mnfst-sw:';
21
21
  const META_CACHE = CACHE_PREFIX + 'meta';
22
22
  const SWR_CAP = 200;
@@ -558,8 +558,10 @@ class TailwindCompiler {
558
558
 
559
559
  // Read any publish/render-provided static utilities sheet before the
560
560
  // first compile — its classes are skipped rather than regenerated.
561
+ // Fails open (never "everything covered") until this resolves —
562
+ // compile()'s first run awaits it, capped at 2s (see static.js).
561
563
  this.staticUtilitiesCoveredClasses = null;
562
- this.detectStaticUtilitiesSheet();
564
+ this.staticUtilitiesReady = this.detectStaticUtilitiesSheet();
563
565
 
564
566
  // Add critical styles IMMEDIATELY - don't wait for anything
565
567
  this.addCriticalBlockingStylesSync();
@@ -1488,9 +1490,8 @@ TailwindCompiler.prototype.scanStaticClasses = async function () {
1488
1490
 
1489
1491
  // Extract classes from HTML content
1490
1492
  TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
1491
- // Plain class attributes only — the leading boundary excludes Alpine's
1492
- // :class / x-bind:class bindings (JS expressions, not token lists)
1493
- const classRegex = /(?:^|[\s"'`>/])(?:[a-zA-Z][\w.-]*-)?class=["']([^"']+)["']/g;
1493
+ // Match class attributes: class="..." or class='...'
1494
+ const classRegex = /class=["']([^"']+)["']/g;
1494
1495
  let match;
1495
1496
 
1496
1497
  while ((match = classRegex.exec(html)) !== null) {
@@ -1701,8 +1702,11 @@ TailwindCompiler.prototype.fetchThemeContent = async function () {
1701
1702
  TailwindCompiler.prototype.extractThemeVariables = function (cssText) {
1702
1703
  const variables = new Map();
1703
1704
 
1704
- // Extract ALL CSS custom properties from ANY declaration block
1705
- const varRegex = /--([\w-]+):\s*([^;]+);/g;
1705
+ // Extract ALL CSS custom properties from ANY declaration block. Terminator
1706
+ // is a lookahead (`;` or `}`) rather than a consumed `;` so the last
1707
+ // declaration in a block still matches when the author omits the
1708
+ // trailing semicolon (valid CSS, e.g. `:root{--x:1rem}`).
1709
+ const varRegex = /--([\w-]+):\s*([^;}]+)(?=[;}])/g;
1706
1710
 
1707
1711
  let varMatch;
1708
1712
  while ((varMatch = varRegex.exec(cssText)) !== null) {
@@ -3180,6 +3184,11 @@ TailwindCompiler.prototype.compile = async function () {
3180
3184
  if (!this.hasScannedStatic) {
3181
3185
  await this.scanStaticClasses();
3182
3186
 
3187
+ // Wait for the static utilities sheet's covered-class read to
3188
+ // settle (capped at 2s in detectStaticUtilitiesSheet) so this
3189
+ // first compile decision isn't a guess — see static.js.
3190
+ if (this.staticUtilitiesReady) await this.staticUtilitiesReady;
3191
+
3183
3192
  // Fetch CSS content once for initial compilation
3184
3193
  const themeCss = await this.fetchThemeContent();
3185
3194
  if (themeCss) {
@@ -3429,40 +3438,70 @@ TailwindCompiler.prototype.classNamesFromCssText = function (cssText) {
3429
3438
  return classSet;
3430
3439
  };
3431
3440
 
3432
- // Read the static sheet's covered classes once. Same-origin `<link>`/`<style>`
3433
- // rules are read synchronously via CSSOM; if the sheet isn't accessible yet
3434
- // (cross-origin, or not finished loading) this falls back to fetching the
3435
- // `href` as text and re-populating the covered set when it resolves.
3441
+ // Attempt a synchronous CSSOM read of the static link's rules. Returns a Set
3442
+ // of covered classes, or null when nothing could be read — a security error
3443
+ // (cross-origin), an exception, a missing sheet, or zero rules (the link
3444
+ // hasn't finished loading, or its sheet is genuinely empty). null is never
3445
+ // distinguished from "empty": both fail open in filterStaticallyCoveredClasses
3446
+ // / stripCoveredRulesFromCss, and neither is ever treated as "everything
3447
+ // covered".
3448
+ TailwindCompiler.prototype.readStaticUtilitiesRules = function (el) {
3449
+ try {
3450
+ const sheet = Array.from(document.styleSheets).find(s => s.ownerNode === el);
3451
+ const rules = sheet && sheet.cssRules;
3452
+ if (rules && rules.length > 0) {
3453
+ const cssText = Array.from(rules).map(r => r.cssText).join('\n');
3454
+ return this.classNamesFromCssText(cssText);
3455
+ }
3456
+ } catch (e) {
3457
+ // Cross-origin, or not yet parsed.
3458
+ }
3459
+ return null;
3460
+ };
3461
+
3462
+ // Read the static sheet's covered classes once per page load. `<style>` and
3463
+ // an already-loaded `<link>` resolve synchronously. A `<link>` that hasn't
3464
+ // finished loading (or whose sheet can't be read yet) is awaited via its
3465
+ // `load`/`error` event, capped at 2s, then re-read once more; the caller
3466
+ // (compile()'s first run) awaits the returned promise so the very first
3467
+ // compile decision sees an accurate covered set rather than a guess. Fails
3468
+ // open the whole time — staticUtilitiesCoveredClasses stays null (nothing
3469
+ // covered) until a read actually succeeds, never cached as "covered".
3436
3470
  TailwindCompiler.prototype.detectStaticUtilitiesSheet = function () {
3437
3471
  this.staticUtilitiesCoveredClasses = null;
3438
3472
  const el = this.findStaticUtilitiesElement();
3439
- if (!el) return;
3473
+ if (!el) return Promise.resolve();
3440
3474
 
3441
3475
  if (el.tagName === 'STYLE') {
3442
3476
  this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(el.textContent || '');
3443
- return;
3477
+ return Promise.resolve();
3444
3478
  }
3445
3479
 
3446
- try {
3447
- const sheet = Array.from(document.styleSheets).find(s => s.ownerNode === el);
3448
- const rules = sheet && sheet.cssRules;
3449
- if (rules && rules.length > 0) {
3450
- const cssText = Array.from(rules).map(r => r.cssText).join('\n');
3451
- this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(cssText);
3452
- return;
3453
- }
3454
- } catch (e) {
3455
- // Cross-origin (or not yet parsed) — fall through to fetch.
3480
+ const covered = this.readStaticUtilitiesRules(el);
3481
+ if (covered) {
3482
+ this.staticUtilitiesCoveredClasses = covered;
3483
+ return Promise.resolve();
3456
3484
  }
3457
3485
 
3458
- // Placeholder so callers treat "not yet known" as "nothing covered" rather
3459
- // than null, until the fetch below resolves and replaces it.
3460
- this.staticUtilitiesCoveredClasses = new Set();
3461
- const href = el.getAttribute('href');
3462
- if (!href || typeof fetch !== 'function') return;
3463
- fetch(href).then(r => (r.ok ? r.text() : '')).then(text => {
3464
- if (text) this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(text);
3465
- }).catch(() => {});
3486
+ return new Promise((resolve) => {
3487
+ let settled = false;
3488
+ const finish = () => {
3489
+ if (settled) return;
3490
+ settled = true;
3491
+ clearTimeout(timer);
3492
+ el.removeEventListener('load', onLoad);
3493
+ el.removeEventListener('error', onError);
3494
+ // staticUtilitiesCoveredClasses stays null (fail open) if this
3495
+ // still can't be read — e.g. a cross-origin sheet without CORS.
3496
+ this.staticUtilitiesCoveredClasses = this.readStaticUtilitiesRules(el);
3497
+ resolve();
3498
+ };
3499
+ const onLoad = () => finish();
3500
+ const onError = () => finish();
3501
+ const timer = setTimeout(finish, 2000);
3502
+ el.addEventListener('load', onLoad, { once: true });
3503
+ el.addEventListener('error', onError, { once: true });
3504
+ });
3466
3505
  };
3467
3506
 
3468
3507
  // Drop classes already covered by the static sheet before generating rules
@@ -3831,8 +3870,13 @@ function injectTailwindVariants() {
3831
3870
  }
3832
3871
  injectTailwindVariants();
3833
3872
 
3834
- // Initialize immediately without waiting for DOMContentLoaded
3835
- const compiler = new TailwindCompiler();
3873
+ // One compiler per page: a second copy would own a rival #manifest-styles and
3874
+ // the two order observers would re-append their own style to <head> forever.
3875
+ // A compiler whose style element left the document is stale, so it is rebuilt.
3876
+ const existingCompiler = window.ManifestUtilities;
3877
+ const compiler = (existingCompiler && existingCompiler.styleElement && existingCompiler.styleElement.isConnected)
3878
+ ? existingCompiler
3879
+ : new TailwindCompiler();
3836
3880
 
3837
3881
  // Expose utilities compiler for optional integration
3838
3882
  window.ManifestUtilities = compiler;
@@ -3856,11 +3900,14 @@ if ('PerformanceObserver' in window) {
3856
3900
  }
3857
3901
 
3858
3902
  // Recompile on DOMContentLoaded for late-added elements
3859
- document.addEventListener('DOMContentLoaded', () => {
3860
- if (!compiler.usesStaticPrerenderUtilities && !compiler.isCompiling) {
3861
- compiler.compile();
3862
- }
3863
- });
3903
+ if (!compiler.__mnfstDomReadyBound) {
3904
+ compiler.__mnfstDomReadyBound = true;
3905
+ document.addEventListener('DOMContentLoaded', () => {
3906
+ if (!compiler.usesStaticPrerenderUtilities && !compiler.isCompiling) {
3907
+ compiler.compile();
3908
+ }
3909
+ });
3910
+ }
3864
3911
 
3865
3912
 
3866
3913
 
@@ -1,5 +1,25 @@
1
1
  /* manifest.utilities.node.mjs — built from scripts/utilities/ (DOM-free) */
2
2
 
3
+ if (typeof globalThis.window === 'undefined') globalThis.window = globalThis;
4
+ if (typeof globalThis.document === 'undefined') {
5
+ const inertElement = () => ({ style: {}, textContent: '', classList: { add(){}, remove(){}, contains: () => false }, setAttribute(){}, appendChild(){}, insertBefore(){} });
6
+ globalThis.document = {
7
+ documentElement: { outerHTML: '' },
8
+ head: { firstChild: null, lastElementChild: null, innerHTML: '', appendChild(){}, insertBefore(){} },
9
+ querySelectorAll: () => [],
10
+ querySelector: () => null,
11
+ getElementById: () => null,
12
+ getElementsByTagName: () => [],
13
+ createElement: inertElement,
14
+ addEventListener(){},
15
+ styleSheets: []
16
+ };
17
+ }
18
+ if (typeof globalThis.getComputedStyle === 'undefined') globalThis.getComputedStyle = () => ({ getPropertyValue: () => '' });
19
+ if (typeof globalThis.localStorage === 'undefined') globalThis.localStorage = { getItem: () => null, setItem(){}, removeItem(){} };
20
+ if (typeof globalThis.performance === 'undefined') globalThis.performance = { now: () => Date.now() };
21
+ if (typeof globalThis.requestAnimationFrame === 'undefined') globalThis.requestAnimationFrame = (cb) => setTimeout(cb, 0);
22
+
3
23
  // Utility generators
4
24
  // Functions that generate CSS utilities from CSS variable suffixes
5
25
 
@@ -556,8 +576,10 @@ class TailwindCompiler {
556
576
 
557
577
  // Read any publish/render-provided static utilities sheet before the
558
578
  // first compile — its classes are skipped rather than regenerated.
579
+ // Fails open (never "everything covered") until this resolves —
580
+ // compile()'s first run awaits it, capped at 2s (see static.js).
559
581
  this.staticUtilitiesCoveredClasses = null;
560
- this.detectStaticUtilitiesSheet();
582
+ this.staticUtilitiesReady = this.detectStaticUtilitiesSheet();
561
583
 
562
584
  // Add critical styles IMMEDIATELY - don't wait for anything
563
585
  this.addCriticalBlockingStylesSync();
@@ -902,9 +924,8 @@ TailwindCompiler.prototype.scanStaticClasses = async function () {
902
924
 
903
925
  // Extract classes from HTML content
904
926
  TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
905
- // Plain class attributes only — the leading boundary excludes Alpine's
906
- // :class / x-bind:class bindings (JS expressions, not token lists)
907
- const classRegex = /(?:^|[\s"'`>/])(?:[a-zA-Z][\w.-]*-)?class=["']([^"']+)["']/g;
927
+ // Match class attributes: class="..." or class='...'
928
+ const classRegex = /class=["']([^"']+)["']/g;
908
929
  let match;
909
930
 
910
931
  while ((match = classRegex.exec(html)) !== null) {
@@ -1115,8 +1136,11 @@ TailwindCompiler.prototype.fetchThemeContent = async function () {
1115
1136
  TailwindCompiler.prototype.extractThemeVariables = function (cssText) {
1116
1137
  const variables = new Map();
1117
1138
 
1118
- // Extract ALL CSS custom properties from ANY declaration block
1119
- const varRegex = /--([\w-]+):\s*([^;]+);/g;
1139
+ // Extract ALL CSS custom properties from ANY declaration block. Terminator
1140
+ // is a lookahead (`;` or `}`) rather than a consumed `;` so the last
1141
+ // declaration in a block still matches when the author omits the
1142
+ // trailing semicolon (valid CSS, e.g. `:root{--x:1rem}`).
1143
+ const varRegex = /--([\w-]+):\s*([^;}]+)(?=[;}])/g;
1120
1144
 
1121
1145
  let varMatch;
1122
1146
  while ((varMatch = varRegex.exec(cssText)) !== null) {
@@ -2594,6 +2618,11 @@ TailwindCompiler.prototype.compile = async function () {
2594
2618
  if (!this.hasScannedStatic) {
2595
2619
  await this.scanStaticClasses();
2596
2620
 
2621
+ // Wait for the static utilities sheet's covered-class read to
2622
+ // settle (capped at 2s in detectStaticUtilitiesSheet) so this
2623
+ // first compile decision isn't a guess — see static.js.
2624
+ if (this.staticUtilitiesReady) await this.staticUtilitiesReady;
2625
+
2597
2626
  // Fetch CSS content once for initial compilation
2598
2627
  const themeCss = await this.fetchThemeContent();
2599
2628
  if (themeCss) {
@@ -2775,6 +2804,13 @@ TailwindCompiler.prototype.compile = async function () {
2775
2804
  // buildUtilitiesNodeModule) with the DOM-free subset of the browser plugin's
2776
2805
  // subscripts (generators, variants, main, helpers, compile) so this exports
2777
2806
  // the exact same generation logic the browser JIT runs, with no document.
2807
+ //
2808
+ // compileUtilities() also runs the real `tailwindcss` engine (see the
2809
+ // "Tailwind engine pass" section below) — a real, lazily-imported dependency
2810
+ // used only there, via Node's fs/require by default, or via the `tailwind`
2811
+ // option for runtimes (e.g. Workers) that bundle the engine/CSS themselves
2812
+ // instead; every other export in this file stays dependency-free and
2813
+ // DOM-free as before.
2778
2814
 
2779
2815
  /** A bare TailwindCompiler instance carrying only what the pure generation
2780
2816
  * methods (generateUtilitiesFromVars, generateCustomUtilities, parseClassName,
@@ -2798,23 +2834,190 @@ function createNodeCompiler() {
2798
2834
  return instance;
2799
2835
  }
2800
2836
 
2837
+ // --- Tailwind engine pass -------------------------------------------------
2838
+ // Bakes the plain Tailwind-style utilities (flex, gap-2, rounded-full,
2839
+ // w-[37px], md:, hover:, …) that the runtime's bundled Tailwind engine
2840
+ // (lib/manifest.tailwind.js — a Tailwind v4 "Play CDN" browser build) would
2841
+ // otherwise generate live. Uses the real `tailwindcss` package's compile()/
2842
+ // build() — the same core compiler that browser build wraps, not a fork —
2843
+ // which is pure JS with no native binding and no DOM requirement.
2844
+ //
2845
+ // The browser engine reads only `<style type="text/tailwindcss">` tags
2846
+ // (default: `@import "tailwindcss";` plus whatever such tags are on the
2847
+ // page) and derives its candidates from `document.querySelectorAll("[class]")`
2848
+ // — it does not see Manifest's theme vars, so this mirrors that: no bridging
2849
+ // of `themeCss` into Tailwind's `@theme`. The custom variants below mirror
2850
+ // injectTailwindVariants() in manifest.utilities.init.js — keep both in sync.
2851
+ const TAILWIND_CUSTOM_VARIANTS = [
2852
+ '@custom-variant touch (@media (pointer: coarse));',
2853
+ '@custom-variant cursor (@media (pointer: fine) and (hover: hover));',
2854
+ '@custom-variant pointer (@media (any-pointer: fine));',
2855
+ '@custom-variant mac (&:where([data-os="macos"] *));',
2856
+ '@custom-variant windows (&:where([data-os="windows"] *));',
2857
+ '@custom-variant linux (&:where([data-os="linux"] *));',
2858
+ '@custom-variant ios (&:where([data-os="ios"] *));',
2859
+ '@custom-variant android (&:where([data-os="android"] *));',
2860
+ '@custom-variant apple (&:where([data-os="macos"] *, [data-os="ios"] *));',
2861
+ '@custom-variant online (&:where([data-online="true"] *));',
2862
+ '@custom-variant offline (&:where([data-online="false"] *));',
2863
+ '@custom-variant standalone (&:where([data-standalone] *));',
2864
+ '@custom-variant native (&:where([data-native] *));',
2865
+ '@custom-variant web (&:where(html:not([data-native]) *));'
2866
+ ].join('\n');
2867
+
2868
+ /** Balanced-brace extraction of a top-level `@layer <name> { ... }` body —
2869
+ * the compiled CSS nests braces (media queries, `&:hover`), so a regex can't
2870
+ * safely find the matching close brace. Returns '' when the layer is absent. */
2871
+ function extractLayerBody(css, layerName) {
2872
+ const marker = `@layer ${layerName} {`;
2873
+ const start = css.indexOf(marker);
2874
+ if (start === -1) return '';
2875
+ let depth = 1;
2876
+ let i = start + marker.length;
2877
+ const bodyStart = i;
2878
+ for (; i < css.length && depth > 0; i++) {
2879
+ if (css[i] === '{') depth++;
2880
+ else if (css[i] === '}') depth--;
2881
+ }
2882
+ return css.slice(bodyStart, i - 1).trim();
2883
+ }
2884
+
2885
+ let _tailwindEnginePromise = null;
2886
+ /** Lazily loads tailwindcss's compile() plus its theme.css/utilities.css text
2887
+ * — the expensive, reusable I/O (dynamic import, two fs reads). Cached
2888
+ * across calls; resolves to null (fail open) when the package or Node's
2889
+ * fs/require aren't available in this runtime (e.g. a bare Workers isolate),
2890
+ * so callers degrade to Manifest-only utilities rather than throwing.
2891
+ *
2892
+ * `injected`, when given, skips the fs/require path entirely: a caller that
2893
+ * already has the engine and CSS in hand (a bundler-built Workers isolate,
2894
+ * which has no `fs`/`require` — see the `tailwind` param on compileUtilities)
2895
+ * supplies `{ engine, themeCss, utilitiesCss }`, where `engine` is the
2896
+ * imported `tailwindcss` module namespace (its `compile` export is used).
2897
+ * Not cached on `_tailwindEnginePromise`: cheap (no I/O, just wrapping
2898
+ * already-resident values) and the caller owns the instance's lifetime.
2899
+ *
2900
+ * Deliberately skips tailwindcss/preflight.css: the browser engine's own
2901
+ * default `@import "tailwindcss"` — see `jo`/`Io` in lib/manifest.tailwind.js
2902
+ * — wires up only theme.css and utilities.css and leaves preflight empty, so
2903
+ * matching it means never importing preflight here either (Manifest ships
2904
+ * its own reset; the runtime never asks Tailwind for one). */
2905
+ function loadTailwindEngine(injected) {
2906
+ if (injected) {
2907
+ const { engine, themeCss, utilitiesCss } = injected;
2908
+ if (!engine || typeof engine.compile !== 'function' || !themeCss || !utilitiesCss) {
2909
+ return Promise.resolve(null);
2910
+ }
2911
+ return Promise.resolve({ compile: engine.compile, base: '/', themeCss, utilitiesCss });
2912
+ }
2913
+ if (_tailwindEnginePromise) return _tailwindEnginePromise;
2914
+ _tailwindEnginePromise = (async () => {
2915
+ try {
2916
+ const { compile } = await import('tailwindcss');
2917
+ const fs = await import('node:fs');
2918
+ const path = await import('node:path');
2919
+ const { createRequire } = await import('node:module');
2920
+ const req = createRequire(import.meta.url);
2921
+ const themePath = req.resolve('tailwindcss/theme.css');
2922
+ const utilitiesPath = req.resolve('tailwindcss/utilities.css');
2923
+ const themeCss = fs.readFileSync(themePath, 'utf8');
2924
+ const utilitiesCss = fs.readFileSync(utilitiesPath, 'utf8');
2925
+ const base = path.dirname(themePath);
2926
+ return { compile, base, themeCss, utilitiesCss };
2927
+ } catch (e) {
2928
+ return null;
2929
+ }
2930
+ })();
2931
+ return _tailwindEnginePromise;
2932
+ }
2933
+
2934
+ /** Runs the real Tailwind engine over exactly `classes`, returning its
2935
+ * (tree-shaken, used-only) `theme` and `utilities` layer bodies as a single
2936
+ * CSS chunk. Returns '' when Tailwind can't be loaded here, or produced no
2937
+ * utility rules for these classes (the default theme's font/spacing tokens
2938
+ * would otherwise show up even for a class list with no real Tailwind match
2939
+ * — e.g. Manifest's own `.row`/`.col` — since they're always defined; only
2940
+ * emit the theme chunk alongside actual utility output).
2941
+ *
2942
+ * `injected` is passed straight through to loadTailwindEngine — see there.
2943
+ *
2944
+ * A fresh compiler is built per call (~a few ms — see loadTailwindEngine for
2945
+ * the cached, reusable part): tailwindcss's compiler.build() is an
2946
+ * incremental/watch-mode API that accumulates every candidate it has ever
2947
+ * seen, so reusing one compiler across calls with different class lists
2948
+ * would leak earlier calls' classes into later output — the same reason the
2949
+ * browser engine tracks its own "already seen" set (see the header comment)
2950
+ * before calling build(). */
2951
+ async function compileTailwindPass(classes, injected) {
2952
+ if (!classes.length) return '';
2953
+ const engine = await loadTailwindEngine(injected);
2954
+ if (!engine) return '';
2955
+ try {
2956
+ const { compile, base, themeCss, utilitiesCss } = engine;
2957
+ const input = `@layer theme, base, components, utilities;\n@import "tailwindcss/theme" layer(theme);\n@import "tailwindcss/utilities" layer(utilities);\n${TAILWIND_CUSTOM_VARIANTS}\n`;
2958
+ const compiler = await compile(input, {
2959
+ base,
2960
+ loadStylesheet: async (id) => {
2961
+ if (id === 'tailwindcss/theme') return { base, content: themeCss };
2962
+ if (id === 'tailwindcss/utilities') return { base, content: utilitiesCss };
2963
+ throw new Error(`manifest utilities: cannot load "${id}" for Tailwind bake`);
2964
+ },
2965
+ loadModule: async () => { throw new Error('manifest utilities: Tailwind plugins unsupported in bake'); }
2966
+ });
2967
+ const css = compiler.build(classes);
2968
+ const utilities = extractLayerBody(css, 'utilities');
2969
+ if (!utilities) return '';
2970
+ const theme = extractLayerBody(css, 'theme');
2971
+ const parts = [];
2972
+ if (theme) parts.push(`@layer theme {\n${theme}\n}`);
2973
+ parts.push(`@layer utilities {\n${utilities}\n}`);
2974
+ return parts.join('\n\n');
2975
+ } catch (e) {
2976
+ return '';
2977
+ }
2978
+ }
2979
+
2801
2980
  /**
2802
- * Compile the utility CSS the browser plugin would produce for exactly
2803
- * `classes` — variants, theme-variable-driven utilities (generateUtilitiesFromVars)
2804
- * and custom utilities (generateCustomUtilities) discovered in `themeCss`/`baseCss`.
2981
+ * Compile the utility CSS the browser plugin (Manifest's own generators) plus
2982
+ * the framework's bundled Tailwind engine would together produce for exactly
2983
+ * `classes`. Manifest side: variants, theme-variable-driven utilities
2984
+ * (generateUtilitiesFromVars) and custom utilities (generateCustomUtilities)
2985
+ * discovered in `themeCss`/`baseCss`. Tailwind side: whatever real Tailwind
2986
+ * utilities (`flex`, `gap-2`, `rounded-full`, arbitrary values like
2987
+ * `w-[37px]`, and their variants) the class list resolves to, via the actual
2988
+ * `tailwindcss` compiler (see compileTailwindPass above) — not a fork.
2805
2989
  * Deterministic for a given (classes, themeCss, baseCss) triple.
2806
2990
  *
2807
- * @param {{classes?: string[], themeCss?: string, baseCss?: string}} options
2991
+ * `baseCss` should include the framework's own utility CSS (lib/manifest.css)
2992
+ * whenever the caller isn't already loading it: the browser plugin's compile()
2993
+ * finds it for free via discoverCssFiles() scanning the page's own
2994
+ * stylesheets, so generateCustomUtilities() recognises Manifest's semantic
2995
+ * utilities (.row, .col, .center, …) and bakes variants like `md:row` or
2996
+ * `hover:col-wrap`. Without it those variants are silently skipped here —
2997
+ * scripts/utilities-static.mjs loads it automatically for this reason.
2998
+ *
2999
+ * `tailwind`, when given, is passed to the Tailwind engine pass instead of
3000
+ * its default lazy `fs`/`require` load — `{ engine, themeCss, utilitiesCss }`
3001
+ * where `engine` is the imported `tailwindcss` module namespace and the two
3002
+ * CSS strings are the contents of `tailwindcss/theme.css` and
3003
+ * `tailwindcss/utilities.css`. For runtimes that can statically import
3004
+ * `tailwindcss` and inline its CSS at bundle time but have no `fs`/`require`
3005
+ * to read them from node_modules at runtime (e.g. a Cloudflare Workers
3006
+ * isolate) — see loadTailwindEngine. Omit it (the default) to keep using the
3007
+ * Node/CLI fs-based load.
3008
+ *
3009
+ * @param {{classes?: string[], themeCss?: string, baseCss?: string, tailwind?: {engine: object, themeCss: string, utilitiesCss: string}}} options
2808
3010
  * @returns {Promise<string>}
2809
3011
  */
2810
- export async function compileUtilities({ classes = [], themeCss = '', baseCss = '' } = {}) {
3012
+ export async function compileUtilities({ classes = [], themeCss = '', baseCss = '', tailwind = null } = {}) {
2811
3013
  const compiler = createNodeCompiler();
2812
3014
  const cssText = [baseCss, themeCss].filter(Boolean).join('\n');
3015
+ const uniqueClasses = Array.from(new Set(classes));
2813
3016
 
2814
3017
  const discovered = compiler.extractCustomUtilities(cssText);
2815
3018
  for (const [name, value] of discovered) compiler.customUtilities.set(name, value);
2816
3019
 
2817
- const usedData = { classes: Array.from(new Set(classes)), variableSuffixes: [] };
3020
+ const usedData = { classes: uniqueClasses, variableSuffixes: [] };
2818
3021
 
2819
3022
  const varUtilities = compiler.generateUtilitiesFromVars(cssText, usedData);
2820
3023
  const customUtilities = compiler.generateCustomUtilities(usedData);
@@ -2822,7 +3025,10 @@ export async function compileUtilities({ classes = [], themeCss = '', baseCss =
2822
3025
  let allUtilities = [varUtilities, customUtilities].filter(Boolean).join('\n\n');
2823
3026
  allUtilities = compiler.sortUtilities(allUtilities);
2824
3027
 
2825
- return allUtilities ? `@layer utilities {\n${allUtilities}\n}` : '';
3028
+ const manifestLayer = allUtilities ? `@layer utilities {\n${allUtilities}\n}` : '';
3029
+ const tailwindLayer = await compileTailwindPass(uniqueClasses, tailwind);
3030
+
3031
+ return [manifestLayer, tailwindLayer].filter(Boolean).join('\n\n');
2826
3032
  }
2827
3033
 
2828
3034
  // `class:token=` (per-class conditional binding) and the `:class="..."`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.203",
3
+ "version": "0.5.204",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter",
@@ -20,6 +20,9 @@
20
20
  "README.md",
21
21
  "LICENSE"
22
22
  ],
23
+ "dependencies": {
24
+ "tailwindcss": "^4.2.4"
25
+ },
23
26
  "scripts": {
24
27
  "clean": "rimraf src/styles/manifest.css src/styles/manifest.min.css src/styles/manifest.theme.css src/styles/manifest.code.css src/styles/manifest.code.min.css lib",
25
28
  "build": "cd src && node scripts/build.mjs",
@@ -58,7 +61,6 @@
58
61
  "puppeteer": "^24.15.0",
59
62
  "rimraf": "^5.0.0",
60
63
  "rollup": "^4.24.2",
61
- "tailwindcss": "^4.2.4",
62
64
  "vitest": "^4.1.2"
63
65
  },
64
66
  "keywords": [