mnfst-render 0.3.2 → 0.3.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.
@@ -904,6 +904,7 @@ function stripPrerenderDynamicBindings(html) {
904
904
  return html.replace(/<(\w+)([^>]*)>/g, (match, tagName, attrsStr) => {
905
905
  if (tagName.toLowerCase() === 'script') return match;
906
906
  const isAnchor = tagName.toLowerCase() === 'a';
907
+ const isImg = tagName.toLowerCase() === 'img';
907
908
  let workAttrs = attrsStr;
908
909
  workAttrs = workAttrs.replace(/\s+:style=(?:"([^"]*)"|'([^']*)')/gi, (sub, d, s) => {
909
910
  const val = (d !== undefined ? d : s) || '';
@@ -919,8 +920,9 @@ function stripPrerenderDynamicBindings(html) {
919
920
  let m;
920
921
  while ((m = bindingRegex.exec(workAttrs)) !== null) {
921
922
  const attrName = (m[1] || '').toLowerCase();
922
- // Keep href on anchors so prerendered static navigation stays valid.
923
- if (attrName === 'class' || attrName === 'style' || (isAnchor && attrName === 'href')) continue;
923
+ // Keep href on anchors and src on images: :href / :src often reference x-for iterators (e.g.
924
+ // article?.banner). Stripping the baked literal leaves only :src/:href and breaks static HTML.
925
+ if (attrName === 'class' || attrName === 'style' || (isAnchor && attrName === 'href') || (isImg && attrName === 'src')) continue;
924
926
  const val = (m[2] !== undefined ? m[2] : m[3]) || '';
925
927
  if (val.indexOf('$x') === -1) toStrip.add(attrName);
926
928
  }
@@ -937,6 +939,18 @@ function stripPrerenderDynamicBindings(html) {
937
939
  });
938
940
  }
939
941
 
942
+ // Drop :src / x-bind:src when img already has a baked src= (x-for / iterator expressions break hydrate).
943
+ function stripRedundantImgSrcBindings(html) {
944
+ return html.replace(/<img\b([^>]*)>/gi, (full, attrs) => {
945
+ const srcM = attrs.match(/\ssrc=(["'])([\s\S]*?)\1/i);
946
+ if (!srcM || !String(srcM[2] || '').trim()) return full;
947
+ if (!/\s:src\s*=|\sx-bind:src\s*=/i.test(attrs)) return full;
948
+ let next = attrs.replace(/\s:src=(?:"[^"]*"|'[^']*')/gi, '');
949
+ next = next.replace(/\sx-bind:src=(?:"[^"]*"|'[^']*')/gi, '');
950
+ return `<img${next}>`;
951
+ });
952
+ }
953
+
940
954
  // Remove empty inline mask-image styles emitted before data resolves
941
955
  // (e.g. style="mask-image: url()"), while keeping any :style/x-bind:style bindings.
942
956
  function stripEmptyInlineMaskStyles(html) {
@@ -1797,7 +1811,8 @@ async function runPrerender(config) {
1797
1811
  // keep x-text/x-bind referencing card/item — Alpine then mutates or errors on the static HTML.)
1798
1812
  await page.evaluate(() => {
1799
1813
  const loopVarRegex = /^\s*(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s+in\s+/;
1800
- const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-on:|@)/;
1814
+ // Include x-init: expanded clones still had x-init="getDescription(article)" etc.; Alpine then throws (article undefined).
1815
+ const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-init|x-on:|@)/;
1801
1816
  const hasVar = (expr, varName) => varName && new RegExp(`\\b${varName}\\b`).test(expr || '');
1802
1817
  const stripLoopBindings = (el, itemVar, indexVar) => {
1803
1818
  const nodes = [el, ...Array.from(el.querySelectorAll('*'))];
@@ -1891,7 +1906,7 @@ async function runPrerender(config) {
1891
1906
  // outside their template scope. These throw Alpine errors in live static hosting.
1892
1907
  await page.evaluate(() => {
1893
1908
  const loopVarRegex = /^\s*(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s+in\s+/;
1894
- const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-on:|@)/;
1909
+ const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-init|x-on:|@)/;
1895
1910
  const hasVar = (expr, varName) => varName && new RegExp(`\\b${varName}\\b`).test(expr || '');
1896
1911
  const elementReferencesLoopScope = (el, itemVar, indexVar) => {
1897
1912
  if (!el) return false;
@@ -1992,6 +2007,7 @@ async function runPrerender(config) {
1992
2007
  const xData = { manifest, content };
1993
2008
  html = resolveHeadXBindings(html, xData);
1994
2009
  html = stripPrerenderDynamicBindings(html);
2010
+ html = stripRedundantImgSrcBindings(html);
1995
2011
  html = stripEmptyInlineMaskStyles(html);
1996
2012
  html = rewriteHtmlAssetPaths(html, fileSegments.length);
1997
2013
  const liveBase = config.liveUrl.replace(/\/$/, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {