mnfst-render 0.2.9 → 0.3.0

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.
Files changed (2) hide show
  1. package/manifest.render.mjs +84 -62
  2. package/package.json +1 -1
@@ -76,6 +76,25 @@ async function flushAlpineEffects(page) {
76
76
  .catch(() => {});
77
77
  }
78
78
 
79
+ /**
80
+ * Same logical path → normalizedPath as waitForManifestPrerenderPipeline and
81
+ * manifest.router.visibility initialize (matchesCondition first argument).
82
+ */
83
+ function logicalPathToVisibilityNormalizedPath(pathSeg, locales) {
84
+ const pathname = pathSeg ? `/${pathSeg}` : '/';
85
+ const clean = String(pathname || '/').replace(/^\/+|\/+$/g, '');
86
+ const parts = clean ? clean.split('/') : [];
87
+ const localeList = Array.isArray(locales) ? locales : [];
88
+ const logical =
89
+ parts.length > 0 && localeList.includes(parts[0])
90
+ ? `/${parts.slice(1).join('/')}`
91
+ : clean
92
+ ? `/${clean}`
93
+ : '/';
94
+ const to = logical === '//' ? '/' : logical;
95
+ return typeof to === 'string' && to !== '/' ? to.replace(/^\/|\/$/g, '') : '/';
96
+ }
97
+
79
98
  /**
80
99
  * After locale + route sync: run component swapping explicitly, then wait until Alpine data stores
81
100
  * are settled. We call ManifestComponentsSwapping.processAll() directly because swapping only
@@ -1739,37 +1758,49 @@ async function runPrerender(config) {
1739
1758
  });
1740
1759
  });
1741
1760
 
1742
- // Remove static x-for templates once static clones are generated.
1743
- // This prevents Alpine from rendering duplicate lists at runtime.
1744
- await page.evaluate(() => {
1745
- document.querySelectorAll('template[x-for][data-prerender-static-generated="1"]').forEach((tpl) => {
1746
- tpl.remove();
1747
- });
1748
- });
1749
-
1750
- // Remove orphan x-for clones that still reference loop-scope vars (e.g. image/index)
1751
- // outside their template scope. These throw Alpine errors in live static hosting.
1761
+ // Strip loop-scope bindings from x-for clones while <template> nodes still exist.
1762
+ // (If we remove static templates first, querySelectorAll('template[x-for]') misses them and clones
1763
+ // keep x-text/x-bind referencing card/item — Alpine then mutates or errors on the static HTML.)
1752
1764
  await page.evaluate(() => {
1753
1765
  const loopVarRegex = /^\s*(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s+in\s+/;
1754
1766
  const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-on:|@)/;
1755
1767
  const hasVar = (expr, varName) => varName && new RegExp(`\\b${varName}\\b`).test(expr || '');
1756
- const elementReferencesLoopScope = (el, itemVar, indexVar) => {
1757
- if (!el) return false;
1768
+ const stripLoopBindings = (el, itemVar, indexVar) => {
1758
1769
  const nodes = [el, ...Array.from(el.querySelectorAll('*'))];
1759
1770
  for (const node of nodes) {
1760
1771
  const attrs = node.attributes ? Array.from(node.attributes) : [];
1761
1772
  for (const attr of attrs) {
1762
1773
  if (!bindingAttrRegex.test(attr.name)) continue;
1763
1774
  const expr = attr.value || '';
1764
- if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) return true;
1775
+ if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) {
1776
+ const name = attr.name;
1777
+ if (name === 'x-text' || name === 'x-html') {
1778
+ if ((node.textContent || '').trim() || (node.innerHTML || '').trim()) {
1779
+ node.removeAttribute(name);
1780
+ }
1781
+ continue;
1782
+ }
1783
+ if (name === 'x-show' || name === 'x-if') {
1784
+ node.removeAttribute(name);
1785
+ continue;
1786
+ }
1787
+ let boundAttr = '';
1788
+ if (name.startsWith(':')) boundAttr = name.slice(1);
1789
+ else if (name.startsWith('x-bind:')) boundAttr = name.slice('x-bind:'.length);
1790
+ if (boundAttr) {
1791
+ const concrete = node.getAttribute(boundAttr);
1792
+ if (concrete != null && String(concrete).trim() !== '') {
1793
+ node.removeAttribute(name);
1794
+ }
1795
+ continue;
1796
+ }
1797
+ node.removeAttribute(name);
1798
+ }
1765
1799
  }
1766
1800
  }
1767
- return false;
1768
1801
  };
1769
1802
 
1770
- // Only clean up templates we intentionally collapsed above.
1771
- // Running this on all x-for templates can remove valid prerendered list items.
1772
- document.querySelectorAll('template[x-for][data-prerender-collapsed="1"]').forEach((tpl) => {
1803
+ document.querySelectorAll('template[x-for]').forEach((tpl) => {
1773
1804
  const xFor = (tpl.getAttribute('x-for') || '').trim();
1774
1805
  const m = xFor.match(loopVarRegex);
1775
1806
  const itemVar = m ? (m[1] || m[3] || '') : '';
@@ -1782,68 +1813,44 @@ async function runPrerender(config) {
1782
1813
 
1783
1814
  let next = tpl.nextElementSibling;
1784
1815
  while (next) {
1785
- const sameTag = next.tagName === tag;
1786
- if (!sameTag) break;
1787
-
1788
- const referencesLoopScope = elementReferencesLoopScope(next, itemVar, indexVar);
1789
-
1790
- const toRemove = next;
1816
+ if (next.tagName !== tag) break;
1817
+ stripLoopBindings(next, itemVar, indexVar);
1791
1818
  next = next.nextElementSibling;
1792
- if (referencesLoopScope) toRemove.remove();
1793
- else break;
1794
1819
  }
1795
1820
  });
1796
1821
  });
1797
1822
 
1798
- // For static clones kept from x-for templates, remove loop-scope bindings (card/title/etc)
1799
- // so Alpine doesn't re-evaluate them outside template scope in production.
1823
+ // Remove static x-for templates once static clones are generated.
1824
+ // This prevents Alpine from rendering duplicate lists at runtime.
1825
+ await page.evaluate(() => {
1826
+ document.querySelectorAll('template[x-for][data-prerender-static-generated="1"]').forEach((tpl) => {
1827
+ tpl.remove();
1828
+ });
1829
+ });
1830
+
1831
+ // Remove orphan x-for clones that still reference loop-scope vars (e.g. image/index)
1832
+ // outside their template scope. These throw Alpine errors in live static hosting.
1800
1833
  await page.evaluate(() => {
1801
1834
  const loopVarRegex = /^\s*(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s+in\s+/;
1802
1835
  const bindingAttrRegex = /^(?:x-bind:|:|x-text|x-html|x-show|x-if|x-model|x-effect|x-on:|@)/;
1803
1836
  const hasVar = (expr, varName) => varName && new RegExp(`\\b${varName}\\b`).test(expr || '');
1804
- const stripLoopBindings = (el, itemVar, indexVar) => {
1837
+ const elementReferencesLoopScope = (el, itemVar, indexVar) => {
1838
+ if (!el) return false;
1805
1839
  const nodes = [el, ...Array.from(el.querySelectorAll('*'))];
1806
1840
  for (const node of nodes) {
1807
1841
  const attrs = node.attributes ? Array.from(node.attributes) : [];
1808
1842
  for (const attr of attrs) {
1809
1843
  if (!bindingAttrRegex.test(attr.name)) continue;
1810
1844
  const expr = attr.value || '';
1811
- if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) {
1812
- const name = attr.name;
1813
- // Remove text/html bindings only when static content already exists.
1814
- if (name === 'x-text' || name === 'x-html') {
1815
- if ((node.textContent || '').trim() || (node.innerHTML || '').trim()) {
1816
- node.removeAttribute(name);
1817
- }
1818
- continue;
1819
- }
1820
-
1821
- // Remove x-show/x-if if they reference loop vars; cloned node is now static.
1822
- if (name === 'x-show' || name === 'x-if') {
1823
- node.removeAttribute(name);
1824
- continue;
1825
- }
1826
-
1827
- // For :attr / x-bind:attr, only remove binding if a concrete attr is present.
1828
- let boundAttr = '';
1829
- if (name.startsWith(':')) boundAttr = name.slice(1);
1830
- else if (name.startsWith('x-bind:')) boundAttr = name.slice('x-bind:'.length);
1831
- if (boundAttr) {
1832
- const concrete = node.getAttribute(boundAttr);
1833
- if (concrete != null && String(concrete).trim() !== '') {
1834
- node.removeAttribute(name);
1835
- }
1836
- continue;
1837
- }
1838
-
1839
- // Event/other loop-scoped bindings are unsafe on static clones.
1840
- node.removeAttribute(name);
1841
- }
1845
+ if (hasVar(expr, itemVar) || hasVar(expr, indexVar)) return true;
1842
1846
  }
1843
1847
  }
1848
+ return false;
1844
1849
  };
1845
1850
 
1846
- document.querySelectorAll('template[x-for]').forEach((tpl) => {
1851
+ // Only clean up templates we intentionally collapsed above.
1852
+ // Running this on all x-for templates can remove valid prerendered list items.
1853
+ document.querySelectorAll('template[x-for][data-prerender-collapsed="1"]').forEach((tpl) => {
1847
1854
  const xFor = (tpl.getAttribute('x-for') || '').trim();
1848
1855
  const m = xFor.match(loopVarRegex);
1849
1856
  const itemVar = m ? (m[1] || m[3] || '') : '';
@@ -1856,9 +1863,15 @@ async function runPrerender(config) {
1856
1863
 
1857
1864
  let next = tpl.nextElementSibling;
1858
1865
  while (next) {
1859
- if (next.tagName !== tag) break;
1860
- stripLoopBindings(next, itemVar, indexVar);
1866
+ const sameTag = next.tagName === tag;
1867
+ if (!sameTag) break;
1868
+
1869
+ const referencesLoopScope = elementReferencesLoopScope(next, itemVar, indexVar);
1870
+
1871
+ const toRemove = next;
1861
1872
  next = next.nextElementSibling;
1873
+ if (referencesLoopScope) toRemove.remove();
1874
+ else break;
1862
1875
  }
1863
1876
  });
1864
1877
  });
@@ -1872,6 +1885,15 @@ async function runPrerender(config) {
1872
1885
  toRemove.forEach((el) => { if (document.contains(el)) el.remove(); });
1873
1886
  });
1874
1887
 
1888
+ const visibilityNormalizedPath = logicalPathToVisibilityNormalizedPath(pathSeg, locales);
1889
+ await page.evaluate((np) => {
1890
+ try {
1891
+ window.ManifestRoutingVisibility?.processRouteVisibility?.(np);
1892
+ } catch {
1893
+ /* no-op */
1894
+ }
1895
+ }, visibilityNormalizedPath);
1896
+
1875
1897
  // Remove route-hidden content ([x-route] with inline style display:none) so each prerendered page contains only that route's HTML.
1876
1898
  await page.evaluate(() => {
1877
1899
  const reDisplayNone = /\bdisplay\s*:\s*none\b/i;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {