mnfst-render 0.2.6 → 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.
@@ -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(() => {
@@ -1568,6 +1630,15 @@ async function runPrerender(config) {
1568
1630
  });
1569
1631
 
1570
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
+ }
1571
1642
  html = stripDevOnlyContent(html);
1572
1643
  html = stripInjectedPluginScripts(html);
1573
1644
  if (tailwindBuilt) {
@@ -1587,6 +1658,7 @@ async function runPrerender(config) {
1587
1658
  const xData = { manifest, content };
1588
1659
  html = resolveHeadXBindings(html, xData);
1589
1660
  html = stripPrerenderDynamicBindings(html);
1661
+ html = stripEmptyInlineMaskStyles(html);
1590
1662
  html = rewriteHtmlAssetPaths(html, fileSegments.length);
1591
1663
  const liveBase = config.liveUrl.replace(/\/$/, '');
1592
1664
  const canonicalHreflang = buildCanonicalAndHreflang(is404 ? '' : pathSeg, locales, defaultLocale, liveBase);
@@ -1599,6 +1671,13 @@ async function runPrerender(config) {
1599
1671
  html = html.replace('</head>', `${canonicalHreflang}${injectOgLocale ? ogLocale : ''}${baseMeta}${prerenderedMeta}<meta name="manifest:router-base-depth" content="${routeDepth}">\n</head>`);
1600
1672
  mkdirSync(outDir, { recursive: true });
1601
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
+ });
1602
1681
  } catch (err) {
1603
1682
  failedPaths.push({
1604
1683
  path: displayPath,
@@ -1633,6 +1712,17 @@ async function runPrerender(config) {
1633
1712
  throw new Error(`prerender failed for ${failedPaths.length}/${pathTotal} paths. Sample: ${sample}`);
1634
1713
  }
1635
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
+
1636
1726
  if (bundleUtilities) {
1637
1727
  const utilMerged = mergeUtilityCssBlocks(utilityBlocks);
1638
1728
  if (utilMerged.trim()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {