mnfst-render 0.5.35 → 0.5.37
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/manifest.render.mjs +127 -1
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -1814,6 +1814,9 @@ function computeGlobalAssetSignature(rootDir) {
|
|
|
1814
1814
|
async function waitForVisualSettle(page, { settleMs = 300, mdWaitMs = 5000, capMs = 8000 } = {}) {
|
|
1815
1815
|
try {
|
|
1816
1816
|
await page.evaluate(async ({ settleMs, mdWaitMs, capMs }) => {
|
|
1817
|
+
// The framework's boot signal (loader ≥0.5.190) gates the start, but it
|
|
1818
|
+
// fires once per page — per-ROUTE work is tracked by the live markdown
|
|
1819
|
+
// pending counter and the DOM heuristic below, so all three AND together.
|
|
1817
1820
|
const state = () => {
|
|
1818
1821
|
let styleLen = 0;
|
|
1819
1822
|
for (const s of document.querySelectorAll('style')) styleLen += (s.textContent || '').length;
|
|
@@ -1824,14 +1827,58 @@ async function waitForVisualSettle(page, { settleMs = 300, mdWaitMs = 5000, capM
|
|
|
1824
1827
|
const sig = `${mdEmpty}:${document.body?.innerText.length || 0}:${styleLen}:${document.fonts ? document.fonts.status : ''}`;
|
|
1825
1828
|
return { sig, mdEmpty };
|
|
1826
1829
|
};
|
|
1830
|
+
// Alpine 3.x can strand already-flushed effects when a sibling effect
|
|
1831
|
+
// creates state mid-flush — a route's x-if chain then never stamps until
|
|
1832
|
+
// something re-queues it. Bumping the data store's version re-runs every
|
|
1833
|
+
// store-reading effect: a no-op on settled pages, an unstick on stuck ones.
|
|
1834
|
+
const nudge = () => {
|
|
1835
|
+
try {
|
|
1836
|
+
if (window.Alpine?.store) {
|
|
1837
|
+
const s = window.Alpine.store('data');
|
|
1838
|
+
if (s) window.Alpine.store('data', { ...s, _dataVersion: (s._dataVersion || 0) + 1 });
|
|
1839
|
+
}
|
|
1840
|
+
} catch { /* best-effort */ }
|
|
1841
|
+
};
|
|
1842
|
+
// Responsive/variant utilities (md:row, dark:bg-*) are compiled at
|
|
1843
|
+
// runtime and the compiler throttles recompiles — a late-discovered
|
|
1844
|
+
// variant class leaves the layout in its mobile/stacked state at capture.
|
|
1845
|
+
// Hold while any variant class used in the DOM has no matching CSS rule.
|
|
1846
|
+
const unresolvedVariants = () => {
|
|
1847
|
+
try {
|
|
1848
|
+
const used = new Set();
|
|
1849
|
+
for (const el of document.querySelectorAll('[class*=":"]')) {
|
|
1850
|
+
for (const c of el.classList) if (c.includes(':') && !c.startsWith('[')) used.add(c);
|
|
1851
|
+
if (used.size > 300) break;
|
|
1852
|
+
}
|
|
1853
|
+
if (!used.size) return 0;
|
|
1854
|
+
let selectors = '';
|
|
1855
|
+
for (const sheet of document.styleSheets) {
|
|
1856
|
+
try { for (const r of sheet.cssRules) if (r.selectorText) selectors += r.selectorText + '\n'; } catch { /* cross-origin */ }
|
|
1857
|
+
}
|
|
1858
|
+
let missing = 0;
|
|
1859
|
+
for (const c of used) {
|
|
1860
|
+
if (!selectors.includes('.' + CSS.escape(c))) missing++;
|
|
1861
|
+
}
|
|
1862
|
+
return missing;
|
|
1863
|
+
} catch { return 0; }
|
|
1864
|
+
};
|
|
1827
1865
|
const start = Date.now();
|
|
1828
1866
|
let last = state();
|
|
1829
1867
|
let stableSince = Date.now();
|
|
1868
|
+
let nudged = 0;
|
|
1830
1869
|
while (Date.now() - start < capMs) {
|
|
1831
1870
|
await new Promise((r) => setTimeout(r, 100));
|
|
1871
|
+
const elapsed = Date.now() - start;
|
|
1872
|
+
if ((nudged === 0 && elapsed > 300) || (nudged === 1 && elapsed > 2000)) { nudged++; nudge(); }
|
|
1832
1873
|
const cur = state();
|
|
1874
|
+
const bootPending = !!window.__manifestReadyCoordinator && !window.__manifestReady;
|
|
1875
|
+
const inflight = (window.__manifestMarkdownPending || 0) > 0;
|
|
1833
1876
|
const mdPending = cur.mdEmpty > 0 && Date.now() - start < mdWaitMs;
|
|
1834
|
-
|
|
1877
|
+
const utilPending = elapsed < mdWaitMs + 4000 && unresolvedVariants() > 0;
|
|
1878
|
+
// A throttled compiler can DROP the request that would resolve them —
|
|
1879
|
+
// a class mutation re-triggers its observer's schedule.
|
|
1880
|
+
if (utilPending && elapsed % 500 < 100) document.body.classList.toggle('mnfst-og-nudge');
|
|
1881
|
+
if (cur.sig !== last.sig || bootPending || inflight || mdPending || utilPending) {
|
|
1835
1882
|
last = cur;
|
|
1836
1883
|
stableSince = Date.now();
|
|
1837
1884
|
} else if (Date.now() - stableSince >= settleMs) {
|
|
@@ -1939,6 +1986,11 @@ async function takeOgSnapshot(page, outputDir, pathSeg, globalAssetSignature, ca
|
|
|
1939
1986
|
// body min-h-screen + flex grow) can otherwise screenshot as blank if
|
|
1940
1987
|
// the compositor doesn't repaint between setViewport and screenshot.
|
|
1941
1988
|
await page.evaluate(() => window.scrollTo(0, 0));
|
|
1989
|
+
// Throwaway shot first: it forces the compositor to produce a fresh frame,
|
|
1990
|
+
// so the real capture below reflects the current DOM rather than a stale
|
|
1991
|
+
// surface (headless pages can sit frames behind after heavy updates).
|
|
1992
|
+
await page.screenshot({ type: 'png', clip: { x: 0, y: 0, width: 8, height: 8 }, captureBeyondViewport: false }).catch(() => { });
|
|
1993
|
+
await new Promise((r) => setTimeout(r, 120));
|
|
1942
1994
|
await page.screenshot({
|
|
1943
1995
|
path: filePath,
|
|
1944
1996
|
type: 'png',
|
|
@@ -3308,6 +3360,24 @@ async function runPrerender(config) {
|
|
|
3308
3360
|
const apply = () => {
|
|
3309
3361
|
try {
|
|
3310
3362
|
if (locale && typeof locale === 'string') document.documentElement.lang = locale;
|
|
3363
|
+
// View transitions freeze a snapshot layer over the updated DOM in
|
|
3364
|
+
// headless — OG screenshots then capture stale pre-route pixels.
|
|
3365
|
+
// Router opt-out for SPA swaps, plus a late <style> so the reset's
|
|
3366
|
+
// cross-document @view-transition{navigation:auto} is overridden
|
|
3367
|
+
// (appended after stylesheets; last descriptor wins).
|
|
3368
|
+
document.documentElement.setAttribute('data-no-view-transitions', '');
|
|
3369
|
+
const killCrossDocVT = () => {
|
|
3370
|
+
try {
|
|
3371
|
+
const s = document.createElement('style');
|
|
3372
|
+
s.textContent = '@view-transition { navigation: none }';
|
|
3373
|
+
(document.head || document.documentElement).appendChild(s);
|
|
3374
|
+
} catch { /* no-op */ }
|
|
3375
|
+
};
|
|
3376
|
+
if (document.readyState === 'loading') {
|
|
3377
|
+
document.addEventListener('DOMContentLoaded', killCrossDocVT, { once: true });
|
|
3378
|
+
} else {
|
|
3379
|
+
killCrossDocVT();
|
|
3380
|
+
}
|
|
3311
3381
|
} catch {
|
|
3312
3382
|
/* no-op */
|
|
3313
3383
|
}
|
|
@@ -4352,6 +4422,62 @@ async function runPrerender(config) {
|
|
|
4352
4422
|
if (tpl.hasAttribute('data-hydrate') || tpl.closest('[data-hydrate]')) return;
|
|
4353
4423
|
tag(tpl._x_currentIfEl);
|
|
4354
4424
|
});
|
|
4425
|
+
|
|
4426
|
+
// Clones inside CLOSED popovers carry no crawler value and are wiped
|
|
4427
|
+
// at boot anyway (Alpine re-renders from the template) — drop them from
|
|
4428
|
+
// the output entirely.
|
|
4429
|
+
document.querySelectorAll('[data-mnfst-prerender-clone]').forEach((el) => {
|
|
4430
|
+
if (!document.contains(el)) return;
|
|
4431
|
+
const pop = el.closest('[popover]');
|
|
4432
|
+
if (pop && !pop.matches(':popover-open')) el.remove();
|
|
4433
|
+
});
|
|
4434
|
+
|
|
4435
|
+
// Containers stamped data-mnfst-generated are fully rebuilt by their
|
|
4436
|
+
// plugin at boot (e.g. a colorpicker's palette library — thousands of
|
|
4437
|
+
// swatch nodes with full palette JSON in x-data). Serialize them empty.
|
|
4438
|
+
document.querySelectorAll('[data-mnfst-generated]').forEach((el) => {
|
|
4439
|
+
if (document.contains(el)) el.innerHTML = '';
|
|
4440
|
+
});
|
|
4441
|
+
|
|
4442
|
+
// Legacy bridge for colorpickers older than the stamp (<0.5.190): the
|
|
4443
|
+
// palette library wrapper is rebuilt from scratch at boot regardless,
|
|
4444
|
+
// so drop it from the output. Remove once sites pin a stamped build.
|
|
4445
|
+
document.querySelectorAll('.library-wrapper').forEach((el) => {
|
|
4446
|
+
if (el.querySelector('.library-palette, [data-cp-library-group]')) el.remove();
|
|
4447
|
+
});
|
|
4448
|
+
});
|
|
4449
|
+
|
|
4450
|
+
// Deterministic output: randomized CSS anchor-positioning names
|
|
4451
|
+
// (--dropdown-x7f2, --popover-…, --anchor-…) are regenerated per run and
|
|
4452
|
+
// made every render a phantom diff. Renumber them sequentially in DOM
|
|
4453
|
+
// order so identical content serializes identically.
|
|
4454
|
+
await page.evaluate(() => {
|
|
4455
|
+
const RE = /--(dropdown|popover|anchor)-[a-z0-9]+/g;
|
|
4456
|
+
const map = new Map();
|
|
4457
|
+
const counters = { dropdown: 0, popover: 0, anchor: 0 };
|
|
4458
|
+
const rename = (m, kind) => {
|
|
4459
|
+
if (!map.has(m)) map.set(m, `--${kind}-p${++counters[kind]}`);
|
|
4460
|
+
return map.get(m);
|
|
4461
|
+
};
|
|
4462
|
+
for (const el of document.querySelectorAll('*')) {
|
|
4463
|
+
for (const attr of el.attributes) {
|
|
4464
|
+
if (!attr.value || attr.value.indexOf('--') === -1) continue;
|
|
4465
|
+
const next = attr.value.replace(RE, (m, kind) => rename(m, kind));
|
|
4466
|
+
if (next !== attr.value) el.setAttribute(attr.name, next);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
|
|
4470
|
+
// Component bookkeeping attrs land in whichever order the swap raced —
|
|
4471
|
+
// re-set the pair canonically so it can't flip between runs. (They are
|
|
4472
|
+
// not Alpine directives; position carries no behavior.)
|
|
4473
|
+
for (const el of document.querySelectorAll('[data-component][data-order]')) {
|
|
4474
|
+
const c = el.getAttribute('data-component');
|
|
4475
|
+
const o = el.getAttribute('data-order');
|
|
4476
|
+
el.removeAttribute('data-component');
|
|
4477
|
+
el.removeAttribute('data-order');
|
|
4478
|
+
el.setAttribute('data-component', c);
|
|
4479
|
+
el.setAttribute('data-order', o);
|
|
4480
|
+
}
|
|
4355
4481
|
});
|
|
4356
4482
|
|
|
4357
4483
|
// SEO / AEO meta injection — see resolveConfig().seo for precedence layers.
|