mnfst 0.5.203 → 0.5.205

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.
@@ -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,12 @@ 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
+ // Literal class="..."/class='...' only — lookbehind excludes `:class=`
928
+ // (Alpine binding), whose "class=" substring would otherwise match and
929
+ // truncate at the literal's first internal quote, leaking a stray '{'.
930
+ // Whitespace-only split below keeps every token Tailwind would accept
931
+ // (variants, arbitrary values, leading '-', '!'); only x-/$ tokens drop.
932
+ const classRegex = /(?<![:\w])class=["']([^"']+)["']/g;
908
933
  let match;
909
934
 
910
935
  while ((match = classRegex.exec(html)) !== null) {
@@ -1115,8 +1140,11 @@ TailwindCompiler.prototype.fetchThemeContent = async function () {
1115
1140
  TailwindCompiler.prototype.extractThemeVariables = function (cssText) {
1116
1141
  const variables = new Map();
1117
1142
 
1118
- // Extract ALL CSS custom properties from ANY declaration block
1119
- const varRegex = /--([\w-]+):\s*([^;]+);/g;
1143
+ // Extract ALL CSS custom properties from ANY declaration block. Terminator
1144
+ // is a lookahead (`;` or `}`) rather than a consumed `;` so the last
1145
+ // declaration in a block still matches when the author omits the
1146
+ // trailing semicolon (valid CSS, e.g. `:root{--x:1rem}`).
1147
+ const varRegex = /--([\w-]+):\s*([^;}]+)(?=[;}])/g;
1120
1148
 
1121
1149
  let varMatch;
1122
1150
  while ((varMatch = varRegex.exec(cssText)) !== null) {
@@ -2594,6 +2622,11 @@ TailwindCompiler.prototype.compile = async function () {
2594
2622
  if (!this.hasScannedStatic) {
2595
2623
  await this.scanStaticClasses();
2596
2624
 
2625
+ // Wait for the static utilities sheet's covered-class read to
2626
+ // settle (capped at 2s in detectStaticUtilitiesSheet) so this
2627
+ // first compile decision isn't a guess — see static.js.
2628
+ if (this.staticUtilitiesReady) await this.staticUtilitiesReady;
2629
+
2597
2630
  // Fetch CSS content once for initial compilation
2598
2631
  const themeCss = await this.fetchThemeContent();
2599
2632
  if (themeCss) {
@@ -2775,6 +2808,13 @@ TailwindCompiler.prototype.compile = async function () {
2775
2808
  // buildUtilitiesNodeModule) with the DOM-free subset of the browser plugin's
2776
2809
  // subscripts (generators, variants, main, helpers, compile) so this exports
2777
2810
  // the exact same generation logic the browser JIT runs, with no document.
2811
+ //
2812
+ // compileUtilities() also runs the real `tailwindcss` engine (see the
2813
+ // "Tailwind engine pass" section below) — a real, lazily-imported dependency
2814
+ // used only there, via Node's fs/require by default, or via the `tailwind`
2815
+ // option for runtimes (e.g. Workers) that bundle the engine/CSS themselves
2816
+ // instead; every other export in this file stays dependency-free and
2817
+ // DOM-free as before.
2778
2818
 
2779
2819
  /** A bare TailwindCompiler instance carrying only what the pure generation
2780
2820
  * methods (generateUtilitiesFromVars, generateCustomUtilities, parseClassName,
@@ -2798,23 +2838,190 @@ function createNodeCompiler() {
2798
2838
  return instance;
2799
2839
  }
2800
2840
 
2841
+ // --- Tailwind engine pass -------------------------------------------------
2842
+ // Bakes the plain Tailwind-style utilities (flex, gap-2, rounded-full,
2843
+ // w-[37px], md:, hover:, …) that the runtime's bundled Tailwind engine
2844
+ // (lib/manifest.tailwind.js — a Tailwind v4 "Play CDN" browser build) would
2845
+ // otherwise generate live. Uses the real `tailwindcss` package's compile()/
2846
+ // build() — the same core compiler that browser build wraps, not a fork —
2847
+ // which is pure JS with no native binding and no DOM requirement.
2848
+ //
2849
+ // The browser engine reads only `<style type="text/tailwindcss">` tags
2850
+ // (default: `@import "tailwindcss";` plus whatever such tags are on the
2851
+ // page) and derives its candidates from `document.querySelectorAll("[class]")`
2852
+ // — it does not see Manifest's theme vars, so this mirrors that: no bridging
2853
+ // of `themeCss` into Tailwind's `@theme`. The custom variants below mirror
2854
+ // injectTailwindVariants() in manifest.utilities.init.js — keep both in sync.
2855
+ const TAILWIND_CUSTOM_VARIANTS = [
2856
+ '@custom-variant touch (@media (pointer: coarse));',
2857
+ '@custom-variant cursor (@media (pointer: fine) and (hover: hover));',
2858
+ '@custom-variant pointer (@media (any-pointer: fine));',
2859
+ '@custom-variant mac (&:where([data-os="macos"] *));',
2860
+ '@custom-variant windows (&:where([data-os="windows"] *));',
2861
+ '@custom-variant linux (&:where([data-os="linux"] *));',
2862
+ '@custom-variant ios (&:where([data-os="ios"] *));',
2863
+ '@custom-variant android (&:where([data-os="android"] *));',
2864
+ '@custom-variant apple (&:where([data-os="macos"] *, [data-os="ios"] *));',
2865
+ '@custom-variant online (&:where([data-online="true"] *));',
2866
+ '@custom-variant offline (&:where([data-online="false"] *));',
2867
+ '@custom-variant standalone (&:where([data-standalone] *));',
2868
+ '@custom-variant native (&:where([data-native] *));',
2869
+ '@custom-variant web (&:where(html:not([data-native]) *));'
2870
+ ].join('\n');
2871
+
2872
+ /** Balanced-brace extraction of a top-level `@layer <name> { ... }` body —
2873
+ * the compiled CSS nests braces (media queries, `&:hover`), so a regex can't
2874
+ * safely find the matching close brace. Returns '' when the layer is absent. */
2875
+ function extractLayerBody(css, layerName) {
2876
+ const marker = `@layer ${layerName} {`;
2877
+ const start = css.indexOf(marker);
2878
+ if (start === -1) return '';
2879
+ let depth = 1;
2880
+ let i = start + marker.length;
2881
+ const bodyStart = i;
2882
+ for (; i < css.length && depth > 0; i++) {
2883
+ if (css[i] === '{') depth++;
2884
+ else if (css[i] === '}') depth--;
2885
+ }
2886
+ return css.slice(bodyStart, i - 1).trim();
2887
+ }
2888
+
2889
+ let _tailwindEnginePromise = null;
2890
+ /** Lazily loads tailwindcss's compile() plus its theme.css/utilities.css text
2891
+ * — the expensive, reusable I/O (dynamic import, two fs reads). Cached
2892
+ * across calls; resolves to null (fail open) when the package or Node's
2893
+ * fs/require aren't available in this runtime (e.g. a bare Workers isolate),
2894
+ * so callers degrade to Manifest-only utilities rather than throwing.
2895
+ *
2896
+ * `injected`, when given, skips the fs/require path entirely: a caller that
2897
+ * already has the engine and CSS in hand (a bundler-built Workers isolate,
2898
+ * which has no `fs`/`require` — see the `tailwind` param on compileUtilities)
2899
+ * supplies `{ engine, themeCss, utilitiesCss }`, where `engine` is the
2900
+ * imported `tailwindcss` module namespace (its `compile` export is used).
2901
+ * Not cached on `_tailwindEnginePromise`: cheap (no I/O, just wrapping
2902
+ * already-resident values) and the caller owns the instance's lifetime.
2903
+ *
2904
+ * Deliberately skips tailwindcss/preflight.css: the browser engine's own
2905
+ * default `@import "tailwindcss"` — see `jo`/`Io` in lib/manifest.tailwind.js
2906
+ * — wires up only theme.css and utilities.css and leaves preflight empty, so
2907
+ * matching it means never importing preflight here either (Manifest ships
2908
+ * its own reset; the runtime never asks Tailwind for one). */
2909
+ function loadTailwindEngine(injected) {
2910
+ if (injected) {
2911
+ const { engine, themeCss, utilitiesCss } = injected;
2912
+ if (!engine || typeof engine.compile !== 'function' || !themeCss || !utilitiesCss) {
2913
+ return Promise.resolve(null);
2914
+ }
2915
+ return Promise.resolve({ compile: engine.compile, base: '/', themeCss, utilitiesCss });
2916
+ }
2917
+ if (_tailwindEnginePromise) return _tailwindEnginePromise;
2918
+ _tailwindEnginePromise = (async () => {
2919
+ try {
2920
+ const { compile } = await import('tailwindcss');
2921
+ const fs = await import('node:fs');
2922
+ const path = await import('node:path');
2923
+ const { createRequire } = await import('node:module');
2924
+ const req = createRequire(import.meta.url);
2925
+ const themePath = req.resolve('tailwindcss/theme.css');
2926
+ const utilitiesPath = req.resolve('tailwindcss/utilities.css');
2927
+ const themeCss = fs.readFileSync(themePath, 'utf8');
2928
+ const utilitiesCss = fs.readFileSync(utilitiesPath, 'utf8');
2929
+ const base = path.dirname(themePath);
2930
+ return { compile, base, themeCss, utilitiesCss };
2931
+ } catch (e) {
2932
+ return null;
2933
+ }
2934
+ })();
2935
+ return _tailwindEnginePromise;
2936
+ }
2937
+
2938
+ /** Runs the real Tailwind engine over exactly `classes`, returning its
2939
+ * (tree-shaken, used-only) `theme` and `utilities` layer bodies as a single
2940
+ * CSS chunk. Returns '' when Tailwind can't be loaded here, or produced no
2941
+ * utility rules for these classes (the default theme's font/spacing tokens
2942
+ * would otherwise show up even for a class list with no real Tailwind match
2943
+ * — e.g. Manifest's own `.row`/`.col` — since they're always defined; only
2944
+ * emit the theme chunk alongside actual utility output).
2945
+ *
2946
+ * `injected` is passed straight through to loadTailwindEngine — see there.
2947
+ *
2948
+ * A fresh compiler is built per call (~a few ms — see loadTailwindEngine for
2949
+ * the cached, reusable part): tailwindcss's compiler.build() is an
2950
+ * incremental/watch-mode API that accumulates every candidate it has ever
2951
+ * seen, so reusing one compiler across calls with different class lists
2952
+ * would leak earlier calls' classes into later output — the same reason the
2953
+ * browser engine tracks its own "already seen" set (see the header comment)
2954
+ * before calling build(). */
2955
+ async function compileTailwindPass(classes, injected) {
2956
+ if (!classes.length) return '';
2957
+ const engine = await loadTailwindEngine(injected);
2958
+ if (!engine) return '';
2959
+ try {
2960
+ const { compile, base, themeCss, utilitiesCss } = engine;
2961
+ const input = `@layer theme, base, components, utilities;\n@import "tailwindcss/theme" layer(theme);\n@import "tailwindcss/utilities" layer(utilities);\n${TAILWIND_CUSTOM_VARIANTS}\n`;
2962
+ const compiler = await compile(input, {
2963
+ base,
2964
+ loadStylesheet: async (id) => {
2965
+ if (id === 'tailwindcss/theme') return { base, content: themeCss };
2966
+ if (id === 'tailwindcss/utilities') return { base, content: utilitiesCss };
2967
+ throw new Error(`manifest utilities: cannot load "${id}" for Tailwind bake`);
2968
+ },
2969
+ loadModule: async () => { throw new Error('manifest utilities: Tailwind plugins unsupported in bake'); }
2970
+ });
2971
+ const css = compiler.build(classes);
2972
+ const utilities = extractLayerBody(css, 'utilities');
2973
+ if (!utilities) return '';
2974
+ const theme = extractLayerBody(css, 'theme');
2975
+ const parts = [];
2976
+ if (theme) parts.push(`@layer theme {\n${theme}\n}`);
2977
+ parts.push(`@layer utilities {\n${utilities}\n}`);
2978
+ return parts.join('\n\n');
2979
+ } catch (e) {
2980
+ return '';
2981
+ }
2982
+ }
2983
+
2801
2984
  /**
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`.
2985
+ * Compile the utility CSS the browser plugin (Manifest's own generators) plus
2986
+ * the framework's bundled Tailwind engine would together produce for exactly
2987
+ * `classes`. Manifest side: variants, theme-variable-driven utilities
2988
+ * (generateUtilitiesFromVars) and custom utilities (generateCustomUtilities)
2989
+ * discovered in `themeCss`/`baseCss`. Tailwind side: whatever real Tailwind
2990
+ * utilities (`flex`, `gap-2`, `rounded-full`, arbitrary values like
2991
+ * `w-[37px]`, and their variants) the class list resolves to, via the actual
2992
+ * `tailwindcss` compiler (see compileTailwindPass above) — not a fork.
2805
2993
  * Deterministic for a given (classes, themeCss, baseCss) triple.
2806
2994
  *
2807
- * @param {{classes?: string[], themeCss?: string, baseCss?: string}} options
2995
+ * `baseCss` should include the framework's own utility CSS (lib/manifest.css)
2996
+ * whenever the caller isn't already loading it: the browser plugin's compile()
2997
+ * finds it for free via discoverCssFiles() scanning the page's own
2998
+ * stylesheets, so generateCustomUtilities() recognises Manifest's semantic
2999
+ * utilities (.row, .col, .center, …) and bakes variants like `md:row` or
3000
+ * `hover:col-wrap`. Without it those variants are silently skipped here —
3001
+ * scripts/utilities-static.mjs loads it automatically for this reason.
3002
+ *
3003
+ * `tailwind`, when given, is passed to the Tailwind engine pass instead of
3004
+ * its default lazy `fs`/`require` load — `{ engine, themeCss, utilitiesCss }`
3005
+ * where `engine` is the imported `tailwindcss` module namespace and the two
3006
+ * CSS strings are the contents of `tailwindcss/theme.css` and
3007
+ * `tailwindcss/utilities.css`. For runtimes that can statically import
3008
+ * `tailwindcss` and inline its CSS at bundle time but have no `fs`/`require`
3009
+ * to read them from node_modules at runtime (e.g. a Cloudflare Workers
3010
+ * isolate) — see loadTailwindEngine. Omit it (the default) to keep using the
3011
+ * Node/CLI fs-based load.
3012
+ *
3013
+ * @param {{classes?: string[], themeCss?: string, baseCss?: string, tailwind?: {engine: object, themeCss: string, utilitiesCss: string}}} options
2808
3014
  * @returns {Promise<string>}
2809
3015
  */
2810
- export async function compileUtilities({ classes = [], themeCss = '', baseCss = '' } = {}) {
3016
+ export async function compileUtilities({ classes = [], themeCss = '', baseCss = '', tailwind = null } = {}) {
2811
3017
  const compiler = createNodeCompiler();
2812
3018
  const cssText = [baseCss, themeCss].filter(Boolean).join('\n');
3019
+ const uniqueClasses = Array.from(new Set(classes));
2813
3020
 
2814
3021
  const discovered = compiler.extractCustomUtilities(cssText);
2815
3022
  for (const [name, value] of discovered) compiler.customUtilities.set(name, value);
2816
3023
 
2817
- const usedData = { classes: Array.from(new Set(classes)), variableSuffixes: [] };
3024
+ const usedData = { classes: uniqueClasses, variableSuffixes: [] };
2818
3025
 
2819
3026
  const varUtilities = compiler.generateUtilitiesFromVars(cssText, usedData);
2820
3027
  const customUtilities = compiler.generateCustomUtilities(usedData);
@@ -2822,7 +3029,13 @@ export async function compileUtilities({ classes = [], themeCss = '', baseCss =
2822
3029
  let allUtilities = [varUtilities, customUtilities].filter(Boolean).join('\n\n');
2823
3030
  allUtilities = compiler.sortUtilities(allUtilities);
2824
3031
 
2825
- return allUtilities ? `@layer utilities {\n${allUtilities}\n}` : '';
3032
+ const manifestLayer = allUtilities ? `@layer utilities {\n${allUtilities}\n}` : '';
3033
+ const tailwindLayer = await compileTailwindPass(uniqueClasses, tailwind);
3034
+
3035
+ const body = [manifestLayer, tailwindLayer].filter(Boolean).join('\n\n');
3036
+ if (!body) return '';
3037
+ // Layer order preamble: wins over a later `base` reset regardless of load order.
3038
+ return `@layer base, components, utilities;\n\n${body}`;
2826
3039
  }
2827
3040
 
2828
3041
  // `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.205",
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": [