mnfst 0.5.202 → 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.202';
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.202';
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-RVDH7r+lH+uw6OnE5kvgszmjJdlZjtbpYXc5+CZMtzo1gWViE0AG9AlpE+N3NvtH",
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-WNojlmE9Z0vDcw+MgVQdUhQKdSjbz6ghJlHoQaJcGdaK1XCNmvx8G7kvEfozpaVl",
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-zUBmvVs8MVFHnt/sNWQJ16vWTaLZUJc6egsmhU4EVpvYLvOqWo28KmusamAO/rfi",
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-u8wB+Jf6SMVFSZs/Bbf15h4HpkKqWyqeri/xCCMgHbMqRu595Gu9wUBY5wafiJ87",
38
+ "manifest.utilities.js": "sha384-3PdKl/ARgpezheDE9thLyrN3sEuacROPmBALAuo/SKGHDHDBqQzXBD6ilvoMn95F",
39
39
  "manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
40
- "manifest.js": "sha384-AmLBpm1v/K9B93ZBGNTje0ceJHA4qnlciTrPms0zjcGGRJV8f8wQ7Pcr+sOLO38w"
40
+ "manifest.js": "sha384-jH0veyUdnBMoGwHxgUQ9Yjs0UneDMI/fYIsg1U58YPZwriKJ6Rv/aoA7RbLepoGa"
41
41
  }
package/lib/manifest.js CHANGED
@@ -650,6 +650,24 @@
650
650
  };
651
651
  }
652
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
+
653
671
  // Load custom Tailwind CDN script, falling through the CDN chain on error
654
672
  async function loadTailwind(version = RESOLVED_VERSION) {
655
673
  const urls = CDN_HOSTS.map(h => `${getBaseUrl(version, h)}/${hostFile(h, 'manifest.tailwind.min.js')}`);
@@ -906,7 +924,7 @@
906
924
  console.warn(`[Manifest Loader] Failed to load plugin ${pluginName}:`, error);
907
925
  });
908
926
  });
909
- if (config.tailwind) {
927
+ if (config.tailwind && !staticUtilitiesFullyCovered()) {
910
928
  pluginPromises.push(loadTailwind(config.version).catch(() => { }));
911
929
  }
912
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.202';
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();
@@ -3182,6 +3184,11 @@ TailwindCompiler.prototype.compile = async function () {
3182
3184
  if (!this.hasScannedStatic) {
3183
3185
  await this.scanStaticClasses();
3184
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
+
3185
3192
  // Fetch CSS content once for initial compilation
3186
3193
  const themeCss = await this.fetchThemeContent();
3187
3194
  if (themeCss) {
@@ -3431,40 +3438,70 @@ TailwindCompiler.prototype.classNamesFromCssText = function (cssText) {
3431
3438
  return classSet;
3432
3439
  };
3433
3440
 
3434
- // Read the static sheet's covered classes once. Same-origin `<link>`/`<style>`
3435
- // rules are read synchronously via CSSOM; if the sheet isn't accessible yet
3436
- // (cross-origin, or not finished loading) this falls back to fetching the
3437
- // `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".
3438
3470
  TailwindCompiler.prototype.detectStaticUtilitiesSheet = function () {
3439
3471
  this.staticUtilitiesCoveredClasses = null;
3440
3472
  const el = this.findStaticUtilitiesElement();
3441
- if (!el) return;
3473
+ if (!el) return Promise.resolve();
3442
3474
 
3443
3475
  if (el.tagName === 'STYLE') {
3444
3476
  this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(el.textContent || '');
3445
- return;
3477
+ return Promise.resolve();
3446
3478
  }
3447
3479
 
3448
- try {
3449
- const sheet = Array.from(document.styleSheets).find(s => s.ownerNode === el);
3450
- const rules = sheet && sheet.cssRules;
3451
- if (rules && rules.length > 0) {
3452
- const cssText = Array.from(rules).map(r => r.cssText).join('\n');
3453
- this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(cssText);
3454
- return;
3455
- }
3456
- } catch (e) {
3457
- // 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();
3458
3484
  }
3459
3485
 
3460
- // Placeholder so callers treat "not yet known" as "nothing covered" rather
3461
- // than null, until the fetch below resolves and replaces it.
3462
- this.staticUtilitiesCoveredClasses = new Set();
3463
- const href = el.getAttribute('href');
3464
- if (!href || typeof fetch !== 'function') return;
3465
- fetch(href).then(r => (r.ok ? r.text() : '')).then(text => {
3466
- if (text) this.staticUtilitiesCoveredClasses = this.classNamesFromCssText(text);
3467
- }).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
+ });
3468
3505
  };
3469
3506
 
3470
3507
  // Drop classes already covered by the static sheet before generating rules
@@ -576,8 +576,10 @@ class TailwindCompiler {
576
576
 
577
577
  // Read any publish/render-provided static utilities sheet before the
578
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).
579
581
  this.staticUtilitiesCoveredClasses = null;
580
- this.detectStaticUtilitiesSheet();
582
+ this.staticUtilitiesReady = this.detectStaticUtilitiesSheet();
581
583
 
582
584
  // Add critical styles IMMEDIATELY - don't wait for anything
583
585
  this.addCriticalBlockingStylesSync();
@@ -2616,6 +2618,11 @@ TailwindCompiler.prototype.compile = async function () {
2616
2618
  if (!this.hasScannedStatic) {
2617
2619
  await this.scanStaticClasses();
2618
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
+
2619
2626
  // Fetch CSS content once for initial compilation
2620
2627
  const themeCss = await this.fetchThemeContent();
2621
2628
  if (themeCss) {
@@ -2797,6 +2804,13 @@ TailwindCompiler.prototype.compile = async function () {
2797
2804
  // buildUtilitiesNodeModule) with the DOM-free subset of the browser plugin's
2798
2805
  // subscripts (generators, variants, main, helpers, compile) so this exports
2799
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.
2800
2814
 
2801
2815
  /** A bare TailwindCompiler instance carrying only what the pure generation
2802
2816
  * methods (generateUtilitiesFromVars, generateCustomUtilities, parseClassName,
@@ -2820,23 +2834,190 @@ function createNodeCompiler() {
2820
2834
  return instance;
2821
2835
  }
2822
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
+
2823
2980
  /**
2824
- * Compile the utility CSS the browser plugin would produce for exactly
2825
- * `classes` — variants, theme-variable-driven utilities (generateUtilitiesFromVars)
2826
- * 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.
2827
2989
  * Deterministic for a given (classes, themeCss, baseCss) triple.
2828
2990
  *
2829
- * @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
2830
3010
  * @returns {Promise<string>}
2831
3011
  */
2832
- export async function compileUtilities({ classes = [], themeCss = '', baseCss = '' } = {}) {
3012
+ export async function compileUtilities({ classes = [], themeCss = '', baseCss = '', tailwind = null } = {}) {
2833
3013
  const compiler = createNodeCompiler();
2834
3014
  const cssText = [baseCss, themeCss].filter(Boolean).join('\n');
3015
+ const uniqueClasses = Array.from(new Set(classes));
2835
3016
 
2836
3017
  const discovered = compiler.extractCustomUtilities(cssText);
2837
3018
  for (const [name, value] of discovered) compiler.customUtilities.set(name, value);
2838
3019
 
2839
- const usedData = { classes: Array.from(new Set(classes)), variableSuffixes: [] };
3020
+ const usedData = { classes: uniqueClasses, variableSuffixes: [] };
2840
3021
 
2841
3022
  const varUtilities = compiler.generateUtilitiesFromVars(cssText, usedData);
2842
3023
  const customUtilities = compiler.generateCustomUtilities(usedData);
@@ -2844,7 +3025,10 @@ export async function compileUtilities({ classes = [], themeCss = '', baseCss =
2844
3025
  let allUtilities = [varUtilities, customUtilities].filter(Boolean).join('\n\n');
2845
3026
  allUtilities = compiler.sortUtilities(allUtilities);
2846
3027
 
2847
- 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');
2848
3032
  }
2849
3033
 
2850
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.202",
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": [