mnfst-render 0.5.12 → 0.5.15

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.
@@ -619,14 +619,20 @@ function stripDevOnlyContent(html) {
619
619
  // leaving it in place would cause Alpine to execute synchronously during HTML
620
620
  // parse, before plugins have a chance to register their directives.
621
621
  function stripInjectedPluginScripts(html) {
622
+ // Safety-net regex strip for any Manifest plugin scripts that survived the
623
+ // DOM-level removal in the Puppeteer evaluate phase. Uses a broad pattern
624
+ // that matches ANY `manifest.*.js` or `manifest.*.min.js` script — no need
625
+ // to enumerate individual plugin names. New plugins are covered automatically.
622
626
  const pluginPattern =
623
- /<script[^>]*\ssrc=["'][^"']*manifest\.(?:components|router|utilities|data|icons|localization|markdown|code|themes|toasts|tooltips|dropdowns|tabs|slides|resize|colorpicker|tailwind|appwrite\.(?:auth|data|presence))[^"']*\.(?:min\.)?js["'][^>]*>\s*<\/script>/gi;
627
+ /<script[^>]*\ssrc=["'][^"']*manifest\.[a-z][\w.-]*\.(?:min\.)?js["'][^>]*>\s*<\/script>/gi;
624
628
  let out = html.replace(pluginPattern, '');
629
+ // Alpine (re-injected by the loader at runtime after plugin registration)
625
630
  const alpinePattern =
626
631
  /<script[^>]*\ssrc=["'][^"']*\/alpinejs@[^"']*["'][^>]*>\s*<\/script>/gi;
627
632
  out = out.replace(alpinePattern, '');
633
+ // Runtime library scripts loaded by plugins (yaml parser, markdown, etc.)
628
634
  const runtimePattern =
629
- /<script[^>]*\ssrc=["'][^"']*(?:papaparse@[^"']*\/papaparse\.min\.js|marked\/marked\.min\.js|highlightjs\/cdn-release@[^"']*\/highlight\.min\.js)[^"']*["'][^>]*>\s*<\/script>/gi;
635
+ /<script[^>]*\ssrc=["'][^"']*(?:papaparse[^"']*\.min\.js|marked[^"']*\.min\.js|highlight[^"']*\.min\.js|js-yaml[^"']*\.min\.js)[^"']*["'][^>]*>\s*<\/script>/gi;
630
636
  out = out.replace(runtimePattern, '');
631
637
  return out;
632
638
  }
@@ -1993,6 +1999,22 @@ async function runPrerender(config) {
1993
1999
  // (`hydratePrerenderedPage` in manifest.js) reads the contract and
1994
2000
  // restores source attributes before Alpine starts.
1995
2001
  await page.evaluateOnNewDocument(() => {
2002
+ // Snapshot the script srcs that exist in the author's original HTML
2003
+ // BEFORE any loader/plugin injects additional scripts. Used later to
2004
+ // strip runtime-injected scripts from the serialized output while
2005
+ // keeping author-intentional ones (analytics, third-party widgets, etc.).
2006
+ window.__manifestOriginalScriptSrcs = new Set();
2007
+ const _snapScripts = () => {
2008
+ document.querySelectorAll('script[src]').forEach(s => {
2009
+ window.__manifestOriginalScriptSrcs.add(s.getAttribute('src'));
2010
+ });
2011
+ };
2012
+ if (document.readyState === 'loading') {
2013
+ document.addEventListener('DOMContentLoaded', _snapScripts, { once: true });
2014
+ }
2015
+ // Also snap immediately for any scripts already parsed
2016
+ _snapScripts();
2017
+
1996
2018
  // element -> { attrName: originalValue (null if attribute was absent) }
1997
2019
  // Keyed by reference so detached elements drop out naturally.
1998
2020
  const sourceAttrs = new Map();
@@ -2461,7 +2483,7 @@ async function runPrerender(config) {
2461
2483
  // The baked value IS the correct initial render.
2462
2484
  if (src === null) {
2463
2485
  const hasBinding =
2464
- (name === 'style' && (':style' in source || 'x-bind:style' in source)) ||
2486
+ (name === 'style' && (':style' in source || 'x-bind:style' in source || 'x-show' in source)) ||
2465
2487
  (name === 'class' && (':class' in source || 'x-bind:class' in source));
2466
2488
  if (hasBinding) continue;
2467
2489
  }
@@ -2722,6 +2744,28 @@ async function runPrerender(config) {
2722
2744
  runBatch(() => {
2723
2745
  document.querySelectorAll('template[x-for][data-prerender-static-generated="1"]').forEach((tpl) => {
2724
2746
  if (tpl.hasAttribute('data-hydrate') || tpl.closest('[data-hydrate]')) return;
2747
+ // $x-driven x-for: keep the template so Alpine can re-render the
2748
+ // list at runtime (locale switching, filtering, etc.), but remove
2749
+ // the static clones — Alpine creates fresh clones on init and does
2750
+ // NOT adopt existing DOM nodes, so leaving them produces duplicates.
2751
+ // Individual article/pricing pages still have full baked content
2752
+ // (via x-text/x-html); the x-for list is only the index/grid view.
2753
+ const xFor = (tpl.getAttribute('x-for') || '');
2754
+ if (xFor.includes('$x')) {
2755
+ const first = tpl.content?.firstElementChild;
2756
+ if (first) {
2757
+ const tag = first.tagName;
2758
+ const cls = first.getAttribute('class') || '';
2759
+ let n = tpl.nextElementSibling;
2760
+ while (n && n.tagName === tag && (n.getAttribute('class') || '') === cls) {
2761
+ const next = n.nextElementSibling;
2762
+ n.remove();
2763
+ n = next;
2764
+ }
2765
+ }
2766
+ tpl.removeAttribute('data-prerender-static-generated');
2767
+ return;
2768
+ }
2725
2769
  const parent = tpl.parentNode;
2726
2770
  if (!parent) {
2727
2771
  tpl.remove();
@@ -2813,6 +2857,20 @@ async function runPrerender(config) {
2813
2857
  toRemove.forEach((el) => { if (document.contains(el)) el.remove(); });
2814
2858
  });
2815
2859
 
2860
+ // Remove scripts that were injected at runtime (by the loader, plugins,
2861
+ // or third-party libraries) but were NOT in the author's original HTML.
2862
+ // This is done in the browser context (before serialization) so the
2863
+ // removal is permanent in the captured outerHTML. The approach is
2864
+ // future-proof: new plugins and arbitrary third-party scripts are handled
2865
+ // automatically without updating a hardcoded allowlist.
2866
+ await page.evaluate(() => {
2867
+ const originals = window.__manifestOriginalScriptSrcs || new Set();
2868
+ document.querySelectorAll('script[src]').forEach(s => {
2869
+ const src = s.getAttribute('src');
2870
+ if (src && !originals.has(src)) s.remove();
2871
+ });
2872
+ });
2873
+
2816
2874
  let html = await page.evaluate(() => document.documentElement.outerHTML);
2817
2875
  // Inject the hydration contract blob into the raw HTML *before* caching
2818
2876
  // it for locale variant generation, so every locale variant inherits the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.12",
3
+ "version": "0.5.15",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {