mnfst-render 0.2.3 → 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.
@@ -482,11 +482,15 @@ function extractUtilityStyleBlocks(html) {
482
482
  return { html: out, blocks };
483
483
  }
484
484
 
485
- function injectAfterHeadOpen(html, snippet) {
485
+ function injectBeforeHeadClose(html, snippet) {
486
486
  if (!snippet) return html;
487
487
  const hrefMatch = snippet.match(/href=["']([^"']+)["']/);
488
- if (hrefMatch && html.includes(hrefMatch[1])) return html;
489
- return html.replace(/<head([^>]*)>/i, `<head$1>\n${snippet}\n`);
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 = injectAfterHeadOpen(html, tag);
673
+ html = injectBeforeHeadClose(html, tag);
671
674
  writeFileSync(file, html, 'utf8');
672
675
  }
673
676
  }
@@ -1450,6 +1453,46 @@ async function runPrerender(config) {
1450
1453
  });
1451
1454
  });
1452
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
+
1453
1496
  // Remove elements marked data-dynamic (so they are not in static HTML; client will render them).
1454
1497
  // Skip <template> since we only collapse those above; other elements and their subtree are removed.
1455
1498
  await page.evaluate(() => {
@@ -1481,7 +1524,7 @@ async function runPrerender(config) {
1481
1524
  for (const b of extracted.blocks) utilityBlocks.push(b);
1482
1525
  }
1483
1526
  if (tailwindBuilt) {
1484
- html = injectAfterHeadOpen(html, '<link rel="stylesheet" href="/prerender.tailwind.css">');
1527
+ html = injectBeforeHeadClose(html, '<link rel="stylesheet" href="/prerender.tailwind.css">');
1485
1528
  }
1486
1529
  html = stripDuplicatedLoopDirectives(html);
1487
1530
  html = stripPrerenderedXDataDirectives(html);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {