mnfst-render 0.2.2 → 0.2.4
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 +63 -8
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -482,11 +482,15 @@ function extractUtilityStyleBlocks(html) {
|
|
|
482
482
|
return { html: out, blocks };
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
function
|
|
485
|
+
function injectBeforeHeadClose(html, snippet) {
|
|
486
486
|
if (!snippet) return html;
|
|
487
487
|
const hrefMatch = snippet.match(/href=["']([^"']+)["']/);
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
const href = hrefMatch ? hrefMatch[1] : null;
|
|
489
|
+
let out = html;
|
|
490
|
+
if (href) {
|
|
491
|
+
out = out.replace(new RegExp(`\\s*<link[^>]*href=["']${href.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}["'][^>]*>\\s*`, 'gi'), '\n');
|
|
492
|
+
}
|
|
493
|
+
return out.replace(/<\/head>/i, `${snippet}\n</head>`);
|
|
490
494
|
}
|
|
491
495
|
|
|
492
496
|
function indexHtmlUsesTailwind(rootDir) {
|
|
@@ -663,11 +667,10 @@ function postProcessInjectStylesheetLink(outputDir, filename) {
|
|
|
663
667
|
const files = walkHtmlFiles(outputDir);
|
|
664
668
|
for (const file of files) {
|
|
665
669
|
let html = readFileSync(file, 'utf8');
|
|
666
|
-
if (html.includes(filename)) continue;
|
|
667
670
|
const depth = depthFromOutputRoot(outputDir, file);
|
|
668
671
|
const prefix = depth ? '../'.repeat(depth) : '';
|
|
669
672
|
const tag = `<link rel="stylesheet" href="${prefix}${filename}">`;
|
|
670
|
-
html =
|
|
673
|
+
html = injectBeforeHeadClose(html, tag);
|
|
671
674
|
writeFileSync(file, html, 'utf8');
|
|
672
675
|
}
|
|
673
676
|
}
|
|
@@ -1218,6 +1221,7 @@ async function runPrerender(config) {
|
|
|
1218
1221
|
const timeout = config.wait ?? 15000;
|
|
1219
1222
|
const concurrency = config.concurrency;
|
|
1220
1223
|
const pathTotal = pathList.length;
|
|
1224
|
+
const failedPaths = [];
|
|
1221
1225
|
process.stdout.write(`Prerendering ${pathTotal} path(s)...\n`);
|
|
1222
1226
|
|
|
1223
1227
|
async function processPath(pathSeg, pathIndex) {
|
|
@@ -1331,7 +1335,7 @@ async function runPrerender(config) {
|
|
|
1331
1335
|
// no-op
|
|
1332
1336
|
}
|
|
1333
1337
|
}, { allLocales: locales, currentLocale }).catch(() => { });
|
|
1334
|
-
await
|
|
1338
|
+
await new Promise((resolve) => setTimeout(resolve, 60));
|
|
1335
1339
|
|
|
1336
1340
|
// Optional extra delay so in-page async (e.g. fetch() in x-init for client logos) can complete before snapshot.
|
|
1337
1341
|
if (config.waitAfterIdle > 0) {
|
|
@@ -1449,6 +1453,46 @@ async function runPrerender(config) {
|
|
|
1449
1453
|
});
|
|
1450
1454
|
});
|
|
1451
1455
|
|
|
1456
|
+
// For static clones kept from x-for templates, remove loop-scope bindings (card/title/etc)
|
|
1457
|
+
// so Alpine doesn't re-evaluate them outside template scope in production.
|
|
1458
|
+
await page.evaluate(() => {
|
|
1459
|
+
const loopVarRegex = /^\s*(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s+in\s+/;
|
|
1460
|
+
const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-on:|@)/;
|
|
1461
|
+
const hasVar = (expr, varName) => varName && new RegExp(`\\b${varName}\\b`).test(expr || '');
|
|
1462
|
+
const stripLoopBindings = (el, itemVar, indexVar) => {
|
|
1463
|
+
const nodes = [el, ...Array.from(el.querySelectorAll('*'))];
|
|
1464
|
+
for (const node of nodes) {
|
|
1465
|
+
const attrs = node.attributes ? Array.from(node.attributes) : [];
|
|
1466
|
+
for (const attr of attrs) {
|
|
1467
|
+
if (!bindingAttrRegex.test(attr.name)) continue;
|
|
1468
|
+
const expr = attr.value || '';
|
|
1469
|
+
if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) {
|
|
1470
|
+
node.removeAttribute(attr.name);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
document.querySelectorAll('template[x-for]').forEach((tpl) => {
|
|
1477
|
+
const xFor = (tpl.getAttribute('x-for') || '').trim();
|
|
1478
|
+
const m = xFor.match(loopVarRegex);
|
|
1479
|
+
const itemVar = m ? (m[1] || m[3] || '') : '';
|
|
1480
|
+
const indexVar = m ? (m[2] || '') : '';
|
|
1481
|
+
if (!itemVar && !indexVar) return;
|
|
1482
|
+
|
|
1483
|
+
const first = tpl.content?.firstElementChild;
|
|
1484
|
+
if (!first) return;
|
|
1485
|
+
const tag = first.tagName;
|
|
1486
|
+
|
|
1487
|
+
let next = tpl.nextElementSibling;
|
|
1488
|
+
while (next) {
|
|
1489
|
+
if (next.tagName !== tag) break;
|
|
1490
|
+
stripLoopBindings(next, itemVar, indexVar);
|
|
1491
|
+
next = next.nextElementSibling;
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1452
1496
|
// Remove elements marked data-dynamic (so they are not in static HTML; client will render them).
|
|
1453
1497
|
// Skip <template> since we only collapse those above; other elements and their subtree are removed.
|
|
1454
1498
|
await page.evaluate(() => {
|
|
@@ -1480,7 +1524,7 @@ async function runPrerender(config) {
|
|
|
1480
1524
|
for (const b of extracted.blocks) utilityBlocks.push(b);
|
|
1481
1525
|
}
|
|
1482
1526
|
if (tailwindBuilt) {
|
|
1483
|
-
html =
|
|
1527
|
+
html = injectBeforeHeadClose(html, '<link rel="stylesheet" href="/prerender.tailwind.css">');
|
|
1484
1528
|
}
|
|
1485
1529
|
html = stripDuplicatedLoopDirectives(html);
|
|
1486
1530
|
html = stripPrerenderedXDataDirectives(html);
|
|
@@ -1501,7 +1545,13 @@ async function runPrerender(config) {
|
|
|
1501
1545
|
mkdirSync(outDir, { recursive: true });
|
|
1502
1546
|
writeFileSync(outFile, html, 'utf8');
|
|
1503
1547
|
} catch (err) {
|
|
1504
|
-
|
|
1548
|
+
failedPaths.push({
|
|
1549
|
+
path: displayPath,
|
|
1550
|
+
message: err && err.message ? err.message : String(err)
|
|
1551
|
+
});
|
|
1552
|
+
if (failedPaths.length <= 10) {
|
|
1553
|
+
process.stderr.write(`prerender: failed ${displayPath}: ${failedPaths[failedPaths.length - 1].message}\n`);
|
|
1554
|
+
}
|
|
1505
1555
|
} finally {
|
|
1506
1556
|
await page.close();
|
|
1507
1557
|
}
|
|
@@ -1523,6 +1573,11 @@ async function runPrerender(config) {
|
|
|
1523
1573
|
await browser.close();
|
|
1524
1574
|
}
|
|
1525
1575
|
|
|
1576
|
+
if (failedPaths.length > 0) {
|
|
1577
|
+
const sample = failedPaths.slice(0, 5).map((f) => `${f.path}: ${f.message}`).join(' | ');
|
|
1578
|
+
throw new Error(`prerender failed for ${failedPaths.length}/${pathTotal} paths. Sample: ${sample}`);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1526
1581
|
if (bundleUtilities) {
|
|
1527
1582
|
const utilMerged = mergeUtilityCssBlocks(utilityBlocks);
|
|
1528
1583
|
if (utilMerged.trim()) {
|