mnfst-render 0.2.5 → 0.2.7
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 +122 -1
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -39,6 +39,7 @@ function parseArgs() {
|
|
|
39
39
|
if (args[i] === '--wait-after-idle' && args[i + 1]) { out.waitAfterIdle = parseInt(args[++i], 10); continue; }
|
|
40
40
|
if (args[i] === '--concurrency' && args[i + 1]) { out.concurrency = parseInt(args[++i], 10); continue; }
|
|
41
41
|
if (args[i] === '--dry-run') { out.dryRun = true; continue; }
|
|
42
|
+
if (args[i] === '--debug-prerender') { out.debugPrerender = true; continue; }
|
|
42
43
|
}
|
|
43
44
|
return out;
|
|
44
45
|
}
|
|
@@ -86,6 +87,7 @@ function resolveConfig() {
|
|
|
86
87
|
waitAfterIdle: Math.max(0, cli.waitAfterIdle ?? pre.waitAfterIdle ?? 0),
|
|
87
88
|
concurrency: Math.max(1, cli.concurrency ?? pre.concurrency ?? 6),
|
|
88
89
|
dryRun: !!cli.dryRun,
|
|
90
|
+
debugPrerender: !!(cli.debugPrerender ?? pre.debugPrerender),
|
|
89
91
|
};
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -729,6 +731,29 @@ function stripPrerenderDynamicBindings(html) {
|
|
|
729
731
|
});
|
|
730
732
|
}
|
|
731
733
|
|
|
734
|
+
// Remove empty inline mask-image styles emitted before data resolves
|
|
735
|
+
// (e.g. style="mask-image: url()"), while keeping any :style/x-bind:style bindings.
|
|
736
|
+
function stripEmptyInlineMaskStyles(html) {
|
|
737
|
+
return html.replace(/<(\w+)([^>]*)>/g, (full, tag, attrs) => {
|
|
738
|
+
const styleMatch = attrs.match(/\sstyle=(["'])([\s\S]*?)\1/i);
|
|
739
|
+
if (!styleMatch) return full;
|
|
740
|
+
const quote = styleMatch[1];
|
|
741
|
+
const rawStyle = styleMatch[2] || '';
|
|
742
|
+
const cleaned = rawStyle
|
|
743
|
+
.replace(/\bmask-image\s*:\s*url\(\s*(?:''|""|)\s*\)\s*;?/gi, '')
|
|
744
|
+
.replace(/\b-webkit-mask-image\s*:\s*url\(\s*(?:''|""|)\s*\)\s*;?/gi, '')
|
|
745
|
+
.trim()
|
|
746
|
+
.replace(/^\s*;\s*|\s*;\s*$/g, '');
|
|
747
|
+
|
|
748
|
+
if (!cleaned) {
|
|
749
|
+
const newAttrs = attrs.replace(/\sstyle=(["'])[\s\S]*?\1/i, '');
|
|
750
|
+
return `<${tag}${newAttrs}>`;
|
|
751
|
+
}
|
|
752
|
+
const rebuilt = attrs.replace(/\sstyle=(["'])[\s\S]*?\1/i, ` style=${quote}${cleaned}${quote}`);
|
|
753
|
+
return `<${tag}${rebuilt}>`;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
732
757
|
// --- Rewrite asset URLs: depth = segments from this HTML file up to output root (website). ----
|
|
733
758
|
// All project assets are copied into output, so root-relative paths become relative within output.
|
|
734
759
|
// Do NOT rewrite href on <a> tags (navigation links); only rewrite link/script/img so router gets clean paths.
|
|
@@ -1222,8 +1247,14 @@ async function runPrerender(config) {
|
|
|
1222
1247
|
const concurrency = config.concurrency;
|
|
1223
1248
|
const pathTotal = pathList.length;
|
|
1224
1249
|
const failedPaths = [];
|
|
1250
|
+
const debugRows = [];
|
|
1225
1251
|
process.stdout.write(`Prerendering ${pathTotal} path(s)...\n`);
|
|
1226
1252
|
|
|
1253
|
+
function pushDebug(row) {
|
|
1254
|
+
if (!config.debugPrerender) return;
|
|
1255
|
+
debugRows.push(row);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1227
1258
|
async function processPath(pathSeg, pathIndex) {
|
|
1228
1259
|
const is404 = pathSeg === NOT_FOUND_PATH;
|
|
1229
1260
|
const pathname = is404 ? `/${NOT_FOUND_PATH}` : (pathSeg ? `/${pathSeg}` : '/');
|
|
@@ -1242,6 +1273,7 @@ async function runPrerender(config) {
|
|
|
1242
1273
|
|
|
1243
1274
|
const page = await browser.newPage();
|
|
1244
1275
|
try {
|
|
1276
|
+
pushDebug({ path: displayPath, stage: 'start' });
|
|
1245
1277
|
await page.goto(url, {
|
|
1246
1278
|
waitUntil: 'domcontentloaded',
|
|
1247
1279
|
timeout: Math.min(timeout, 30000),
|
|
@@ -1281,6 +1313,36 @@ async function runPrerender(config) {
|
|
|
1281
1313
|
});
|
|
1282
1314
|
}).catch(() => { });
|
|
1283
1315
|
|
|
1316
|
+
if (config.debugPrerender) {
|
|
1317
|
+
const before = await page.evaluate(() => {
|
|
1318
|
+
const templates = Array.from(document.querySelectorAll('template[x-for]'));
|
|
1319
|
+
const entries = templates.slice(0, 60).map((tpl) => {
|
|
1320
|
+
const first = tpl.content?.firstElementChild;
|
|
1321
|
+
const tag = first ? first.tagName : null;
|
|
1322
|
+
const cls = first ? (first.getAttribute('class') || '') : '';
|
|
1323
|
+
let cloneCount = 0;
|
|
1324
|
+
let next = tpl.nextElementSibling;
|
|
1325
|
+
while (next && (!tag || next.tagName === tag)) {
|
|
1326
|
+
if (tag && (next.getAttribute('class') || '') !== cls) break;
|
|
1327
|
+
cloneCount++;
|
|
1328
|
+
next = next.nextElementSibling;
|
|
1329
|
+
}
|
|
1330
|
+
return {
|
|
1331
|
+
xFor: (tpl.getAttribute('x-for') || '').slice(0, 140),
|
|
1332
|
+
collapsed: tpl.getAttribute('data-prerender-collapsed') === '1',
|
|
1333
|
+
staticGenerated: tpl.getAttribute('data-prerender-static-generated') === '1',
|
|
1334
|
+
cloneCount,
|
|
1335
|
+
};
|
|
1336
|
+
});
|
|
1337
|
+
return {
|
|
1338
|
+
templateCount: templates.length,
|
|
1339
|
+
nonCollapsedTemplateCount: templates.filter((t) => t.getAttribute('data-prerender-collapsed') !== '1').length,
|
|
1340
|
+
entries,
|
|
1341
|
+
};
|
|
1342
|
+
}).catch(() => null);
|
|
1343
|
+
pushDebug({ path: displayPath, stage: 'post-dom-settle', metrics: before });
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1284
1346
|
await page.waitForNetworkIdle({ idleTime: 1500, timeout: 10000 }).catch(() => { });
|
|
1285
1347
|
|
|
1286
1348
|
await page.evaluate(() => {
|
|
@@ -1390,10 +1452,13 @@ async function runPrerender(config) {
|
|
|
1390
1452
|
const first = tpl.content?.firstElementChild;
|
|
1391
1453
|
if (first) {
|
|
1392
1454
|
const tag = first.tagName;
|
|
1455
|
+
const cls = first.getAttribute('class') || '';
|
|
1393
1456
|
let next = tpl.nextElementSibling;
|
|
1394
1457
|
let generatedCount = 0;
|
|
1395
1458
|
while (next) {
|
|
1396
1459
|
if (next.tagName !== tag) break;
|
|
1460
|
+
const sameClass = (next.getAttribute('class') || '') === cls;
|
|
1461
|
+
if (!sameClass) break;
|
|
1397
1462
|
generatedCount++;
|
|
1398
1463
|
next = next.nextElementSibling;
|
|
1399
1464
|
}
|
|
@@ -1491,7 +1556,35 @@ async function runPrerender(config) {
|
|
|
1491
1556
|
if (!bindingAttrRegex.test(attr.name)) continue;
|
|
1492
1557
|
const expr = attr.value || '';
|
|
1493
1558
|
if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) {
|
|
1494
|
-
|
|
1559
|
+
const name = attr.name;
|
|
1560
|
+
// Remove text/html bindings only when static content already exists.
|
|
1561
|
+
if (name === 'x-text' || name === 'x-html') {
|
|
1562
|
+
if ((node.textContent || '').trim() || (node.innerHTML || '').trim()) {
|
|
1563
|
+
node.removeAttribute(name);
|
|
1564
|
+
}
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// Remove x-show/x-if if they reference loop vars; cloned node is now static.
|
|
1569
|
+
if (name === 'x-show' || name === 'x-if') {
|
|
1570
|
+
node.removeAttribute(name);
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// For :attr / x-bind:attr, only remove binding if a concrete attr is present.
|
|
1575
|
+
let boundAttr = '';
|
|
1576
|
+
if (name.startsWith(':')) boundAttr = name.slice(1);
|
|
1577
|
+
else if (name.startsWith('x-bind:')) boundAttr = name.slice('x-bind:'.length);
|
|
1578
|
+
if (boundAttr) {
|
|
1579
|
+
const concrete = node.getAttribute(boundAttr);
|
|
1580
|
+
if (concrete != null && String(concrete).trim() !== '') {
|
|
1581
|
+
node.removeAttribute(name);
|
|
1582
|
+
}
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
// Event/other loop-scoped bindings are unsafe on static clones.
|
|
1587
|
+
node.removeAttribute(name);
|
|
1495
1588
|
}
|
|
1496
1589
|
}
|
|
1497
1590
|
}
|
|
@@ -1537,6 +1630,15 @@ async function runPrerender(config) {
|
|
|
1537
1630
|
});
|
|
1538
1631
|
|
|
1539
1632
|
let html = await page.evaluate(() => document.documentElement.outerHTML);
|
|
1633
|
+
if (config.debugPrerender) {
|
|
1634
|
+
const post = await page.evaluate(() => {
|
|
1635
|
+
const templates = document.querySelectorAll('template[x-for]').length;
|
|
1636
|
+
const links = document.querySelectorAll('a[href="#"]').length;
|
|
1637
|
+
const hidden = document.querySelectorAll('[style*="display: none"]').length;
|
|
1638
|
+
return { templateCountAfterCleanup: templates, hashHrefCount: links, displayNoneCount: hidden };
|
|
1639
|
+
}).catch(() => null);
|
|
1640
|
+
pushDebug({ path: displayPath, stage: 'pre-serialize', metrics: post });
|
|
1641
|
+
}
|
|
1540
1642
|
html = stripDevOnlyContent(html);
|
|
1541
1643
|
html = stripInjectedPluginScripts(html);
|
|
1542
1644
|
if (tailwindBuilt) {
|
|
@@ -1556,6 +1658,7 @@ async function runPrerender(config) {
|
|
|
1556
1658
|
const xData = { manifest, content };
|
|
1557
1659
|
html = resolveHeadXBindings(html, xData);
|
|
1558
1660
|
html = stripPrerenderDynamicBindings(html);
|
|
1661
|
+
html = stripEmptyInlineMaskStyles(html);
|
|
1559
1662
|
html = rewriteHtmlAssetPaths(html, fileSegments.length);
|
|
1560
1663
|
const liveBase = config.liveUrl.replace(/\/$/, '');
|
|
1561
1664
|
const canonicalHreflang = buildCanonicalAndHreflang(is404 ? '' : pathSeg, locales, defaultLocale, liveBase);
|
|
@@ -1568,6 +1671,13 @@ async function runPrerender(config) {
|
|
|
1568
1671
|
html = html.replace('</head>', `${canonicalHreflang}${injectOgLocale ? ogLocale : ''}${baseMeta}${prerenderedMeta}<meta name="manifest:router-base-depth" content="${routeDepth}">\n</head>`);
|
|
1569
1672
|
mkdirSync(outDir, { recursive: true });
|
|
1570
1673
|
writeFileSync(outFile, html, 'utf8');
|
|
1674
|
+
pushDebug({
|
|
1675
|
+
path: displayPath,
|
|
1676
|
+
stage: 'wrote',
|
|
1677
|
+
outFile,
|
|
1678
|
+
htmlBytes: Buffer.byteLength(html, 'utf8'),
|
|
1679
|
+
hasXForTemplate: html.includes('template x-for') || html.includes('template[x-for]'),
|
|
1680
|
+
});
|
|
1571
1681
|
} catch (err) {
|
|
1572
1682
|
failedPaths.push({
|
|
1573
1683
|
path: displayPath,
|
|
@@ -1602,6 +1712,17 @@ async function runPrerender(config) {
|
|
|
1602
1712
|
throw new Error(`prerender failed for ${failedPaths.length}/${pathTotal} paths. Sample: ${sample}`);
|
|
1603
1713
|
}
|
|
1604
1714
|
|
|
1715
|
+
if (config.debugPrerender) {
|
|
1716
|
+
const reportPath = join(outputResolved, 'prerender.debug.json');
|
|
1717
|
+
writeFileSync(reportPath, JSON.stringify({
|
|
1718
|
+
generatedAt: new Date().toISOString(),
|
|
1719
|
+
totalPaths: pathTotal,
|
|
1720
|
+
failedPaths,
|
|
1721
|
+
rows: debugRows,
|
|
1722
|
+
}, null, 2), 'utf8');
|
|
1723
|
+
process.stdout.write(`prerender: debug report ${reportPath}\n`);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1605
1726
|
if (bundleUtilities) {
|
|
1606
1727
|
const utilMerged = mergeUtilityCssBlocks(utilityBlocks);
|
|
1607
1728
|
if (utilMerged.trim()) {
|