mnfst 0.5.206 → 0.5.207
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.
- package/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.d.ts +11 -0
- package/lib/manifest.data.js +1 -1
- package/lib/manifest.integrity.json +5 -5
- package/lib/manifest.js +5 -1
- package/lib/manifest.schema.json +17 -0
- package/lib/manifest.sw.js +1 -1
- package/lib/manifest.utilities.js +188 -19
- package/lib/manifest.utilities.node.mjs +45 -14
- package/package.json +1 -1
package/lib/manifest.d.ts
CHANGED
|
@@ -510,6 +510,17 @@ declare global {
|
|
|
510
510
|
Alpine?: unknown;
|
|
511
511
|
__manifestLoaded?: unknown;
|
|
512
512
|
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Fired when the utilities runtime sees a class the baked sheet (and
|
|
516
|
+
* `manifest.json`'s `utilities.safelist`/`patterns`) doesn't cover, after
|
|
517
|
+
* publish stamped that sheet as covering every class — see
|
|
518
|
+
* manifest.utilities.static.js. `engineLoaded` is true once the lazily
|
|
519
|
+
* loaded Tailwind engine has taken over, false if that load failed.
|
|
520
|
+
*/
|
|
521
|
+
interface WindowEventMap {
|
|
522
|
+
'manifest:utilities-uncovered': CustomEvent<{ classes: string[]; engineLoaded: boolean }>;
|
|
523
|
+
}
|
|
513
524
|
}
|
|
514
525
|
|
|
515
526
|
// ---------------------------------------------------------------------------
|
package/lib/manifest.data.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest.appwrite.auth.js": "sha384-HMJmRXnvdDBWyuteXL+DTxWM2llSCdjl4EDamEI7iHNAVlqeOCE4GDTm8ZHipPRj",
|
|
3
|
-
"manifest.appwrite.data.js": "sha384
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-/1T3gJYo2B7Ct4s24VFKgle5LpTdnZ2kWrMReqamsSzUg1nCqwOjS7T8Z/qgJSoV",
|
|
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-
|
|
14
|
+
"manifest.data.js": "sha384-dq1Hp751qrHoMVX1rDGjj09KkiKb3jzrbW6rCMdwzPWKjzoIQT22InmU6QVP0JhO",
|
|
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-
|
|
31
|
+
"manifest.sw.js": "sha384-a4rb/5qNtjrIQEP6oMusHWeJVtWZ9LIXR/uIpBholtH1808et2NibzgtM1GTEpgq",
|
|
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-
|
|
38
|
+
"manifest.utilities.js": "sha384-pIKcTvk9lrlXZK0OXld6SA6wrmlaFTCmTpLn67nOCE+XhNw4fuGSDX4Z3C2eR6CJ",
|
|
39
39
|
"manifest.virtual.js": "sha384-YwwmAsZNddajnAkLhBWtrK8edHMJOhD7cJkQi3qmm5o1Thg0wbtDU0dsfrCHKTIQ",
|
|
40
|
-
"manifest.js": "sha384-
|
|
40
|
+
"manifest.js": "sha384-fyktZSCaKJ267Hj5ORpbS/Dk11vtT+J8BN1XkA8zG+kXG1wPDwcA1hmB/40fY5Pq"
|
|
41
41
|
}
|
package/lib/manifest.js
CHANGED
|
@@ -686,7 +686,11 @@
|
|
|
686
686
|
// Expose API
|
|
687
687
|
window.Manifest = {
|
|
688
688
|
loadPlugin: function (pluginName, version = RESOLVED_VERSION) {
|
|
689
|
-
|
|
689
|
+
// 'tailwind' isn't a core/Appwrite/authoring plugin — it's the Play-CDN
|
|
690
|
+
// browser engine (lib/manifest.tailwind.js) — but the utilities plugin's
|
|
691
|
+
// uncovered-class watcher lazily loads it this way (see
|
|
692
|
+
// manifest.utilities.static.js setupUncoveredClassWatcher).
|
|
693
|
+
const allPlugins = [...AVAILABLE_PLUGINS, ...APPWRITE_PLUGINS, ...AUTHORING_PLUGINS, 'payments', 'chat', 'device', 'tailwind'];
|
|
690
694
|
if (!allPlugins.includes(pluginName)) {
|
|
691
695
|
console.warn(`[Manifest Loader] Unknown plugin: ${pluginName}`);
|
|
692
696
|
return Promise.reject(new Error(`Unknown plugin: ${pluginName}`));
|
package/lib/manifest.schema.json
CHANGED
|
@@ -431,6 +431,23 @@
|
|
|
431
431
|
"type": "object",
|
|
432
432
|
"description": "Deprecated alias of `device` (renamed in 0.5.199); still accepted.",
|
|
433
433
|
"additionalProperties": true
|
|
434
|
+
},
|
|
435
|
+
"utilities": {
|
|
436
|
+
"type": "object",
|
|
437
|
+
"description": "Utilities-plugin overrides for classes a plain HTML/component scan can't find on its own — e.g. built from a runtime value (`:class=\"ok ? 'bg-green-500' : 'bg-amber-500'\"`). `safelist` classes are baked into the static utilities sheet (scanClasses/compileUtilities) and treated as covered by the runtime; `patterns` classes are trusted as covered by the runtime only (a regex can't be baked).",
|
|
438
|
+
"properties": {
|
|
439
|
+
"safelist": {
|
|
440
|
+
"type": "array",
|
|
441
|
+
"items": { "type": "string" },
|
|
442
|
+
"description": "Exact utility class names, e.g. [\"bg-amber-500\", \"bg-green-500\"]."
|
|
443
|
+
},
|
|
444
|
+
"patterns": {
|
|
445
|
+
"type": "array",
|
|
446
|
+
"items": { "type": "string" },
|
|
447
|
+
"description": "Regex source strings matched against each class token, e.g. \"^bg-(red|green|blue)-[0-9]+$\"."
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
"additionalProperties": false
|
|
434
451
|
}
|
|
435
452
|
},
|
|
436
453
|
"definitions": {
|
package/lib/manifest.sw.js
CHANGED
|
@@ -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.
|
|
19
|
+
const BUILD_VERSION = '0.5.207';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
const SWR_CAP = 200;
|
|
@@ -556,12 +556,19 @@ class TailwindCompiler {
|
|
|
556
556
|
// Cache for parsed class names (must be before addCriticalBlockingStylesSync)
|
|
557
557
|
this.classCache = new Map();
|
|
558
558
|
|
|
559
|
-
// Read any publish/render-provided static utilities sheet
|
|
560
|
-
//
|
|
561
|
-
//
|
|
562
|
-
//
|
|
559
|
+
// Read any publish/render-provided static utilities sheet, plus
|
|
560
|
+
// manifest.json's utilities.safelist/patterns, before the first
|
|
561
|
+
// compile — those classes are skipped rather than regenerated. Fails
|
|
562
|
+
// open (never "everything covered") until this resolves — compile()'s
|
|
563
|
+
// first run awaits it, capped at 2s (see static.js). Once settled,
|
|
564
|
+
// arm the uncovered-class watcher (a no-op unless the loader actually
|
|
565
|
+
// skipped fetching the Tailwind engine for this page).
|
|
563
566
|
this.staticUtilitiesCoveredClasses = null;
|
|
564
|
-
this.staticUtilitiesReady =
|
|
567
|
+
this.staticUtilitiesReady = Promise.all([
|
|
568
|
+
this.detectStaticUtilitiesSheet(),
|
|
569
|
+
this.loadUtilitiesSafelist()
|
|
570
|
+
]);
|
|
571
|
+
this.staticUtilitiesReady.then(() => this.setupUncoveredClassWatcher());
|
|
565
572
|
|
|
566
573
|
// Add critical styles IMMEDIATELY - don't wait for anything
|
|
567
574
|
this.addCriticalBlockingStylesSync();
|
|
@@ -1493,13 +1500,17 @@ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
|
|
|
1493
1500
|
// Literal class="..."/class='...' only — lookbehind excludes `:class=`
|
|
1494
1501
|
// (Alpine binding), whose "class=" substring would otherwise match and
|
|
1495
1502
|
// truncate at the literal's first internal quote, leaking a stray '{'.
|
|
1503
|
+
// Backreference to the opening quote (rather than stopping at either
|
|
1504
|
+
// quote char) so an arbitrary Tailwind value containing the OTHER quote
|
|
1505
|
+
// character — `content-['*']`, `content-['']` — survives instead of
|
|
1506
|
+
// truncating the whole class list at its first `'`.
|
|
1496
1507
|
// Whitespace-only split below keeps every token Tailwind would accept
|
|
1497
1508
|
// (variants, arbitrary values, leading '-', '!'); only x-/$ tokens drop.
|
|
1498
|
-
const classRegex = /(?<![:\w])class=["']([
|
|
1509
|
+
const classRegex = /(?<![:\w])class=(["'])((?:(?!\1)[\s\S])*)\1/g;
|
|
1499
1510
|
let match;
|
|
1500
1511
|
|
|
1501
1512
|
while ((match = classRegex.exec(html)) !== null) {
|
|
1502
|
-
const classString = match[
|
|
1513
|
+
const classString = match[2];
|
|
1503
1514
|
const classes = classString.split(/\s+/).filter(Boolean);
|
|
1504
1515
|
for (const cls of classes) {
|
|
1505
1516
|
if (cls && !cls.startsWith('x-') && !cls.startsWith('$')) {
|
|
@@ -1517,7 +1528,11 @@ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
|
|
|
1517
1528
|
if (classMatches) {
|
|
1518
1529
|
for (const classMatch of classMatches) {
|
|
1519
1530
|
const cls = classMatch.replace(/['"`]/g, '');
|
|
1520
|
-
|
|
1531
|
+
// x-data is arbitrary JS — a plain string literal in there
|
|
1532
|
+
// (e.g. `location.pathname.split('/')`) matches this quoted-
|
|
1533
|
+
// token scan too, so require a letter/digit; a punctuation-only
|
|
1534
|
+
// "token" like a bare '/' is skipped (seen on a live page).
|
|
1535
|
+
if (cls && !cls.startsWith('$') && !cls.includes('(') && /[a-zA-Z0-9]/.test(cls)) {
|
|
1521
1536
|
classSet.add(cls);
|
|
1522
1537
|
}
|
|
1523
1538
|
}
|
|
@@ -3435,17 +3450,30 @@ TailwindCompiler.prototype.extractSelectorsFromCssText = function (cssText) {
|
|
|
3435
3450
|
return selectors;
|
|
3436
3451
|
};
|
|
3437
3452
|
|
|
3453
|
+
// Undo escapeClassName's per-char escaping, including the CSS hex-escape
|
|
3454
|
+
// form used for a class whose first character can't appear bare at the start
|
|
3455
|
+
// of an identifier — a leading digit. Real Tailwind/CSS.escape emit `\32 `
|
|
3456
|
+
// (backslash + hex codepoint + one trailing space) for the '2' in `2xl:*`;
|
|
3457
|
+
// a naive `\X` → `X` unescape corrupts this (leaves a stray "2" + space and
|
|
3458
|
+
// drops the rest of the selector), so `2xl:` variants silently stopped being
|
|
3459
|
+
// recognized as covered — caught by the corpus test (utilities-corpus.test.js).
|
|
3460
|
+
TailwindCompiler.prototype.unescapeClassToken = function (token) {
|
|
3461
|
+
return token.replace(/\\([0-9a-fA-F]{1,6}) ?|\\(.)/g, (m, hex, ch) => hex ? String.fromCodePoint(parseInt(hex, 16)) : ch);
|
|
3462
|
+
};
|
|
3463
|
+
|
|
3438
3464
|
// Selectors escape every non-alphanumeric/hyphen char (see escapeClassName),
|
|
3439
3465
|
// so `.hover\:bg-brand` unescapes back to the `hover:bg-brand` token form
|
|
3440
|
-
// used everywhere else (class attributes, usedClasses, parseClassName).
|
|
3466
|
+
// used everywhere else (class attributes, usedClasses, parseClassName). The
|
|
3467
|
+
// hex-escape alternative is tried first so `\32 ` is consumed as one unit
|
|
3468
|
+
// rather than stopping at the literal digits.
|
|
3441
3469
|
TailwindCompiler.prototype.classNamesFromCssText = function (cssText) {
|
|
3442
3470
|
const classSet = new Set();
|
|
3443
|
-
const classRe = /\.((
|
|
3471
|
+
const classRe = /\.((?:\\[0-9a-fA-F]{1,6} ?|\\.|[a-zA-Z0-9_-])+)/g;
|
|
3444
3472
|
for (const selector of this.extractSelectorsFromCssText(cssText)) {
|
|
3445
3473
|
let m;
|
|
3446
3474
|
classRe.lastIndex = 0;
|
|
3447
3475
|
while ((m = classRe.exec(selector)) !== null) {
|
|
3448
|
-
classSet.add(m[1]
|
|
3476
|
+
classSet.add(this.unescapeClassToken(m[1]));
|
|
3449
3477
|
}
|
|
3450
3478
|
}
|
|
3451
3479
|
return classSet;
|
|
@@ -3517,12 +3545,63 @@ TailwindCompiler.prototype.detectStaticUtilitiesSheet = function () {
|
|
|
3517
3545
|
});
|
|
3518
3546
|
};
|
|
3519
3547
|
|
|
3520
|
-
//
|
|
3521
|
-
//
|
|
3548
|
+
// manifest.json's `utilities.safelist` (exact class names) / `utilities.patterns`
|
|
3549
|
+
// (regex source strings) — classes a project knows are already baked (or
|
|
3550
|
+
// intentionally safe to leave unbaked) even though a plain HTML/component
|
|
3551
|
+
// scan can't find them, e.g. built from a runtime value
|
|
3552
|
+
// (`:class="ok ? 'bg-green-500' : 'bg-amber-500'"`). Read once from the
|
|
3553
|
+
// shared manifest.json fetch (already in flight for other plugins); fails
|
|
3554
|
+
// open to "no safelist" so a bad/missing config never blocks anything.
|
|
3555
|
+
TailwindCompiler.prototype.loadUtilitiesSafelist = async function () {
|
|
3556
|
+
this.safelistClasses = new Set();
|
|
3557
|
+
this.safelistPatterns = [];
|
|
3558
|
+
try {
|
|
3559
|
+
let manifest = window.__manifestLoaded || null;
|
|
3560
|
+
if (!manifest && window.__manifestPromise) manifest = await window.__manifestPromise;
|
|
3561
|
+
const cfg = manifest && typeof manifest === 'object' ? manifest.utilities : null;
|
|
3562
|
+
if (cfg && Array.isArray(cfg.safelist)) {
|
|
3563
|
+
for (const c of cfg.safelist) if (typeof c === 'string' && c) this.safelistClasses.add(c);
|
|
3564
|
+
}
|
|
3565
|
+
if (cfg && Array.isArray(cfg.patterns)) {
|
|
3566
|
+
for (const p of cfg.patterns) {
|
|
3567
|
+
if (typeof p !== 'string') continue;
|
|
3568
|
+
try { this.safelistPatterns.push(new RegExp(p)); } catch (e) { /* invalid pattern, ignore */ }
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
} catch (e) {
|
|
3572
|
+
// Fail open: no manifest.json, or it couldn't be read.
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
|
|
3576
|
+
TailwindCompiler.prototype.isClassSafelisted = function (cls) {
|
|
3577
|
+
if (this.safelistClasses && this.safelistClasses.has(cls)) return true;
|
|
3578
|
+
if (this.safelistPatterns) {
|
|
3579
|
+
for (const re of this.safelistPatterns) {
|
|
3580
|
+
if (re.test(cls)) return true;
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
return false;
|
|
3584
|
+
};
|
|
3585
|
+
|
|
3586
|
+
// Single source of truth for "covered" everywhere that concept is used: the
|
|
3587
|
+
// static sheet's own classes, or the safelist. Both mean "don't regenerate,
|
|
3588
|
+
// and don't treat as a signal that the Tailwind engine needs to load".
|
|
3589
|
+
TailwindCompiler.prototype.isClassCovered = function (cls) {
|
|
3590
|
+
const covered = this.staticUtilitiesCoveredClasses;
|
|
3591
|
+
if (covered && covered.has(cls)) return true;
|
|
3592
|
+
return this.isClassSafelisted(cls);
|
|
3593
|
+
};
|
|
3594
|
+
|
|
3595
|
+
TailwindCompiler.prototype.hasSafelistEntries = function () {
|
|
3596
|
+
return !!((this.safelistClasses && this.safelistClasses.size) || (this.safelistPatterns && this.safelistPatterns.length));
|
|
3597
|
+
};
|
|
3598
|
+
|
|
3599
|
+
// Drop classes already covered by the static sheet (or the safelist) before
|
|
3600
|
+
// generating rules for them again — the JIT should only ever patch what's left.
|
|
3522
3601
|
TailwindCompiler.prototype.filterStaticallyCoveredClasses = function (classes) {
|
|
3523
3602
|
const covered = this.staticUtilitiesCoveredClasses;
|
|
3524
|
-
if (!covered || covered.size === 0) return classes;
|
|
3525
|
-
return classes.filter(c => !
|
|
3603
|
+
if ((!covered || covered.size === 0) && !this.hasSafelistEntries()) return classes;
|
|
3604
|
+
return classes.filter(c => !this.isClassCovered(c));
|
|
3526
3605
|
};
|
|
3527
3606
|
|
|
3528
3607
|
// The localStorage cache (manifest.utilities.cache.js) stores a full compiled
|
|
@@ -3531,7 +3610,7 @@ TailwindCompiler.prototype.filterStaticallyCoveredClasses = function (classes) {
|
|
|
3531
3610
|
// rule the static sheet already covers, so strip those rules out first.
|
|
3532
3611
|
TailwindCompiler.prototype.stripCoveredRulesFromCss = function (cssText) {
|
|
3533
3612
|
const covered = this.staticUtilitiesCoveredClasses;
|
|
3534
|
-
if (!covered || covered.size === 0
|
|
3613
|
+
if (!cssText || ((!covered || covered.size === 0) && !this.hasSafelistEntries())) return cssText;
|
|
3535
3614
|
|
|
3536
3615
|
const strip = (text) => {
|
|
3537
3616
|
const out = [];
|
|
@@ -3584,14 +3663,14 @@ TailwindCompiler.prototype.stripCoveredRulesFromCss = function (cssText) {
|
|
|
3584
3663
|
// Match tokens directly against the bare selector text — it has no
|
|
3585
3664
|
// rule body for classNamesFromCssText's selector-then-`{` scan to key off.
|
|
3586
3665
|
const classes = [];
|
|
3587
|
-
const classRe = /\.((
|
|
3666
|
+
const classRe = /\.((?:\\[0-9a-fA-F]{1,6} ?|\\.|[a-zA-Z0-9_-])+)/g;
|
|
3588
3667
|
let cm;
|
|
3589
3668
|
while ((cm = classRe.exec(selector)) !== null) {
|
|
3590
|
-
classes.push(cm[1]
|
|
3669
|
+
classes.push(this.unescapeClassToken(cm[1]));
|
|
3591
3670
|
}
|
|
3592
3671
|
// Only drop a rule once every class it references is covered — a
|
|
3593
3672
|
// mixed selector (`:where(.row, .col)`) stays if either is new.
|
|
3594
|
-
const isFullyCovered = classes.length > 0 && classes.every(c =>
|
|
3673
|
+
const isFullyCovered = classes.length > 0 && classes.every(c => this.isClassCovered(c));
|
|
3595
3674
|
if (!isFullyCovered) out.push(fullRule);
|
|
3596
3675
|
}
|
|
3597
3676
|
return out.join('\n');
|
|
@@ -3600,6 +3679,96 @@ TailwindCompiler.prototype.stripCoveredRulesFromCss = function (cssText) {
|
|
|
3600
3679
|
return strip(cssText);
|
|
3601
3680
|
};
|
|
3602
3681
|
|
|
3682
|
+
// Runtime safety net for the loader's Tailwind-engine skip (manifest.js
|
|
3683
|
+
// staticUtilitiesFullyCovered): true only when this page (a) asked for the
|
|
3684
|
+
// Tailwind engine at all (`data-tailwind`), and (b) publish stamped the
|
|
3685
|
+
// static utilities sheet complete, so the loader chose not to fetch it
|
|
3686
|
+
// eagerly. Checked against the fuller link-or-style read
|
|
3687
|
+
// (findStaticUtilitiesElement) rather than manifest.js's synchronous
|
|
3688
|
+
// style-only check — this runs well after boot, so it isn't limited by that
|
|
3689
|
+
// decision's timing constraint.
|
|
3690
|
+
TailwindCompiler.prototype.tailwindEngineWasSkipped = function () {
|
|
3691
|
+
try {
|
|
3692
|
+
if (!document.querySelector('script[src*="manifest.js"][data-tailwind]')) return false;
|
|
3693
|
+
const el = this.findStaticUtilitiesElement();
|
|
3694
|
+
return !!(el && el.hasAttribute('data-mnfst-utilities-complete'));
|
|
3695
|
+
} catch (e) {
|
|
3696
|
+
return false;
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3699
|
+
|
|
3700
|
+
// Arms once staticUtilitiesReady (sheet + safelist) has settled. Watches for
|
|
3701
|
+
// any class token — already on the page, or added/changed after — that the
|
|
3702
|
+
// bake doesn't cover and the safelist doesn't excuse: the signal that a
|
|
3703
|
+
// "complete" stamp was wrong and the page would otherwise render unstyled.
|
|
3704
|
+
// A burst of nodes collapses into one lazy load; the observer disconnects
|
|
3705
|
+
// the moment that load succeeds, handing off to the real engine exactly as
|
|
3706
|
+
// if it had loaded eagerly.
|
|
3707
|
+
TailwindCompiler.prototype.setupUncoveredClassWatcher = function () {
|
|
3708
|
+
if (this.usesStaticPrerenderUtilities) return;
|
|
3709
|
+
if (!this.tailwindEngineWasSkipped()) return;
|
|
3710
|
+
if (window.__mnfstTailwindWatcherArmed) return; // one watcher per page load
|
|
3711
|
+
window.__mnfstTailwindWatcherArmed = true;
|
|
3712
|
+
|
|
3713
|
+
let pending = new Set();
|
|
3714
|
+
let loading = false;
|
|
3715
|
+
let observer = null;
|
|
3716
|
+
|
|
3717
|
+
const collectFrom = (el) => {
|
|
3718
|
+
const cls = el.getAttribute && el.getAttribute('class');
|
|
3719
|
+
if (!cls) return;
|
|
3720
|
+
for (const tok of cls.split(/\s+/)) {
|
|
3721
|
+
if (tok && !this.isClassCovered(tok)) pending.add(tok);
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3724
|
+
|
|
3725
|
+
const flush = this.debounce(() => {
|
|
3726
|
+
if (loading || pending.size === 0) return;
|
|
3727
|
+
const classes = Array.from(pending).sort();
|
|
3728
|
+
pending = new Set();
|
|
3729
|
+
loading = true;
|
|
3730
|
+
console.warn('[Manifest Utilities] Uncovered utility class(es) — loading the Tailwind engine:', classes.join(', '));
|
|
3731
|
+
window.Manifest.loadPlugin('tailwind').then(() => {
|
|
3732
|
+
if (observer) observer.disconnect();
|
|
3733
|
+
window.dispatchEvent(new CustomEvent('manifest:utilities-uncovered', { detail: { classes, engineLoaded: true } }));
|
|
3734
|
+
}).catch((err) => {
|
|
3735
|
+
loading = false; // let a further uncovered class try again
|
|
3736
|
+
console.error('[Manifest Utilities] Failed to load the Tailwind engine:', err);
|
|
3737
|
+
window.dispatchEvent(new CustomEvent('manifest:utilities-uncovered', { detail: { classes, engineLoaded: false } }));
|
|
3738
|
+
});
|
|
3739
|
+
}, this.options.debounceTime || 50);
|
|
3740
|
+
|
|
3741
|
+
observer = new MutationObserver((mutations) => {
|
|
3742
|
+
for (const mutation of mutations) {
|
|
3743
|
+
if (mutation.type === 'attributes') {
|
|
3744
|
+
if (mutation.target.nodeType === Node.ELEMENT_NODE) collectFrom(mutation.target);
|
|
3745
|
+
} else if (mutation.type === 'childList') {
|
|
3746
|
+
for (const node of mutation.addedNodes) {
|
|
3747
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
3748
|
+
collectFrom(node);
|
|
3749
|
+
if (node.querySelectorAll) {
|
|
3750
|
+
for (const desc of node.querySelectorAll('[class]')) collectFrom(desc);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
if (pending.size > 0) flush();
|
|
3756
|
+
});
|
|
3757
|
+
|
|
3758
|
+
observer.observe(document.documentElement, {
|
|
3759
|
+
childList: true,
|
|
3760
|
+
subtree: true,
|
|
3761
|
+
attributes: true,
|
|
3762
|
+
attributeFilter: ['class']
|
|
3763
|
+
});
|
|
3764
|
+
this.uncoveredClassObserver = observer; // exposed for tests/diagnostics
|
|
3765
|
+
|
|
3766
|
+
// A wrongly-stamped "complete" sheet may already have uncovered classes
|
|
3767
|
+
// in the initial markup, before any mutation ever fires — check once now.
|
|
3768
|
+
for (const el of document.querySelectorAll('[class]')) collectFrom(el);
|
|
3769
|
+
if (pending.size > 0) flush();
|
|
3770
|
+
};
|
|
3771
|
+
|
|
3603
3772
|
|
|
3604
3773
|
// DOM observation: watch for changes and trigger recompilation
|
|
3605
3774
|
|
|
@@ -574,12 +574,19 @@ class TailwindCompiler {
|
|
|
574
574
|
// Cache for parsed class names (must be before addCriticalBlockingStylesSync)
|
|
575
575
|
this.classCache = new Map();
|
|
576
576
|
|
|
577
|
-
// Read any publish/render-provided static utilities sheet
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
//
|
|
577
|
+
// Read any publish/render-provided static utilities sheet, plus
|
|
578
|
+
// manifest.json's utilities.safelist/patterns, before the first
|
|
579
|
+
// compile — those classes are skipped rather than regenerated. Fails
|
|
580
|
+
// open (never "everything covered") until this resolves — compile()'s
|
|
581
|
+
// first run awaits it, capped at 2s (see static.js). Once settled,
|
|
582
|
+
// arm the uncovered-class watcher (a no-op unless the loader actually
|
|
583
|
+
// skipped fetching the Tailwind engine for this page).
|
|
581
584
|
this.staticUtilitiesCoveredClasses = null;
|
|
582
|
-
this.staticUtilitiesReady =
|
|
585
|
+
this.staticUtilitiesReady = Promise.all([
|
|
586
|
+
this.detectStaticUtilitiesSheet(),
|
|
587
|
+
this.loadUtilitiesSafelist()
|
|
588
|
+
]);
|
|
589
|
+
this.staticUtilitiesReady.then(() => this.setupUncoveredClassWatcher());
|
|
583
590
|
|
|
584
591
|
// Add critical styles IMMEDIATELY - don't wait for anything
|
|
585
592
|
this.addCriticalBlockingStylesSync();
|
|
@@ -927,13 +934,17 @@ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
|
|
|
927
934
|
// Literal class="..."/class='...' only — lookbehind excludes `:class=`
|
|
928
935
|
// (Alpine binding), whose "class=" substring would otherwise match and
|
|
929
936
|
// truncate at the literal's first internal quote, leaking a stray '{'.
|
|
937
|
+
// Backreference to the opening quote (rather than stopping at either
|
|
938
|
+
// quote char) so an arbitrary Tailwind value containing the OTHER quote
|
|
939
|
+
// character — `content-['*']`, `content-['']` — survives instead of
|
|
940
|
+
// truncating the whole class list at its first `'`.
|
|
930
941
|
// Whitespace-only split below keeps every token Tailwind would accept
|
|
931
942
|
// (variants, arbitrary values, leading '-', '!'); only x-/$ tokens drop.
|
|
932
|
-
const classRegex = /(?<![:\w])class=["']([
|
|
943
|
+
const classRegex = /(?<![:\w])class=(["'])((?:(?!\1)[\s\S])*)\1/g;
|
|
933
944
|
let match;
|
|
934
945
|
|
|
935
946
|
while ((match = classRegex.exec(html)) !== null) {
|
|
936
|
-
const classString = match[
|
|
947
|
+
const classString = match[2];
|
|
937
948
|
const classes = classString.split(/\s+/).filter(Boolean);
|
|
938
949
|
for (const cls of classes) {
|
|
939
950
|
if (cls && !cls.startsWith('x-') && !cls.startsWith('$')) {
|
|
@@ -951,7 +962,11 @@ TailwindCompiler.prototype.extractClassesFromHTML = function (html, classSet) {
|
|
|
951
962
|
if (classMatches) {
|
|
952
963
|
for (const classMatch of classMatches) {
|
|
953
964
|
const cls = classMatch.replace(/['"`]/g, '');
|
|
954
|
-
|
|
965
|
+
// x-data is arbitrary JS — a plain string literal in there
|
|
966
|
+
// (e.g. `location.pathname.split('/')`) matches this quoted-
|
|
967
|
+
// token scan too, so require a letter/digit; a punctuation-only
|
|
968
|
+
// "token" like a bare '/' is skipped (seen on a live page).
|
|
969
|
+
if (cls && !cls.startsWith('$') && !cls.includes('(') && /[a-zA-Z0-9]/.test(cls)) {
|
|
955
970
|
classSet.add(cls);
|
|
956
971
|
}
|
|
957
972
|
}
|
|
@@ -3010,13 +3025,22 @@ async function compileTailwindPass(classes, injected) {
|
|
|
3010
3025
|
* isolate) — see loadTailwindEngine. Omit it (the default) to keep using the
|
|
3011
3026
|
* Node/CLI fs-based load.
|
|
3012
3027
|
*
|
|
3013
|
-
*
|
|
3028
|
+
* `safelist` adds literal class names as extra candidates alongside `classes`
|
|
3029
|
+
* — manifest.json's `utilities.safelist` (see scanClasses below), for classes
|
|
3030
|
+
* a project knows it needs baked even though they never turn up in a plain
|
|
3031
|
+
* HTML scan (e.g. built from a runtime value: `:class="ok ? 'bg-green-500' :
|
|
3032
|
+
* 'bg-amber-500'"`). `utilities.patterns` (regex source strings) has no
|
|
3033
|
+
* equivalent here — a pattern can't be enumerated into concrete candidates,
|
|
3034
|
+
* so it only ever excuses a class from the runtime's uncovered-class watcher
|
|
3035
|
+
* (manifest.utilities.static.js), never bakes one.
|
|
3036
|
+
*
|
|
3037
|
+
* @param {{classes?: string[], themeCss?: string, baseCss?: string, safelist?: string[], tailwind?: {engine: object, themeCss: string, utilitiesCss: string}}} options
|
|
3014
3038
|
* @returns {Promise<string>}
|
|
3015
3039
|
*/
|
|
3016
|
-
export async function compileUtilities({ classes = [], themeCss = '', baseCss = '', tailwind = null } = {}) {
|
|
3040
|
+
export async function compileUtilities({ classes = [], themeCss = '', baseCss = '', safelist = [], tailwind = null } = {}) {
|
|
3017
3041
|
const compiler = createNodeCompiler();
|
|
3018
3042
|
const cssText = [baseCss, themeCss].filter(Boolean).join('\n');
|
|
3019
|
-
const uniqueClasses = Array.from(new Set(classes));
|
|
3043
|
+
const uniqueClasses = Array.from(new Set([...classes, ...safelist]));
|
|
3020
3044
|
|
|
3021
3045
|
const discovered = compiler.extractCustomUtilities(cssText);
|
|
3022
3046
|
for (const [name, value] of discovered) compiler.customUtilities.set(name, value);
|
|
@@ -3052,12 +3076,17 @@ const SHORTHAND_CLASS_BINDING_RE = /\B:class=(["'])((?:(?!\1)[\s\S])*)\1/g;
|
|
|
3052
3076
|
* plugin uses (extractClassesFromHTML), plus `class:`/`:class` static tokens.
|
|
3053
3077
|
* Skips `x-`/`$` tokens. Returns a sorted, deduplicated array.
|
|
3054
3078
|
*
|
|
3079
|
+
* `safelist` (manifest.json's `utilities.safelist`) is folded into the result
|
|
3080
|
+
* so a caller that bakes per-file (scripts/utilities-static.mjs, or a
|
|
3081
|
+
* publish pipeline) doesn't need a separate union step — see compileUtilities.
|
|
3082
|
+
*
|
|
3055
3083
|
* @param {string} html
|
|
3084
|
+
* @param {{safelist?: string[]}} [options]
|
|
3056
3085
|
* @returns {string[]}
|
|
3057
3086
|
*/
|
|
3058
|
-
export function scanClasses(html) {
|
|
3087
|
+
export function scanClasses(html, { safelist = [] } = {}) {
|
|
3059
3088
|
const compiler = createNodeCompiler();
|
|
3060
|
-
const set = new Set();
|
|
3089
|
+
const set = new Set(safelist.filter(c => typeof c === 'string' && c));
|
|
3061
3090
|
compiler.extractClassesFromHTML(html, set);
|
|
3062
3091
|
|
|
3063
3092
|
let m;
|
|
@@ -3074,7 +3103,9 @@ export function scanClasses(html) {
|
|
|
3074
3103
|
if (quoted) {
|
|
3075
3104
|
for (const q of quoted) {
|
|
3076
3105
|
const cls = q.replace(/['"`]/g, '');
|
|
3077
|
-
|
|
3106
|
+
// Reject punctuation-only "tokens" (e.g. a bare '/' from an
|
|
3107
|
+
// unrelated string literal in the expression) — see helpers.js.
|
|
3108
|
+
if (cls && !cls.startsWith('$') && !cls.startsWith('x-') && !cls.includes('(') && /[a-zA-Z0-9]/.test(cls)) set.add(cls);
|
|
3078
3109
|
}
|
|
3079
3110
|
}
|
|
3080
3111
|
}
|