mnfst-render 0.5.27 → 0.5.29

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.
@@ -238,7 +238,10 @@ function resolveConfig() {
238
238
  const cwd = process.cwd();
239
239
  const root = resolve(cwd, cli.root ?? '.');
240
240
  const manifest = loadConfig(root);
241
- const pre = manifest.prerender ?? {};
241
+ // Render config lives under manifest.render (per schema + docs). Older
242
+ // projects used manifest.prerender — keep reading it as a fallback so they
243
+ // don't silently lose their config.
244
+ const pre = manifest.render ?? manifest.prerender ?? {};
242
245
 
243
246
  const localUrl = (cli.localUrl ?? cli.baseUrl ?? process.env.PRERENDER_BASE ?? pre.localUrl ?? pre.baseUrl)?.replace(/\/$/, '');
244
247
  const serve = cli.localUrl ? false : (cli.serve !== undefined ? !!cli.serve : true);
@@ -2977,7 +2980,7 @@ async function runPrerender(config) {
2977
2980
  stripTailwindCssImportsFromOutput(outputResolved);
2978
2981
  }
2979
2982
 
2980
- const pre = manifest.prerender ?? {};
2983
+ const pre = manifest.render ?? manifest.prerender ?? {};
2981
2984
  const bundleUtilities = pre.utilitiesBundle !== false;
2982
2985
  const tailwindBuilt = runTailwindCliForPrerender(rootResolved, outputResolved, pre);
2983
2986
  const utilityBlocks = [];
@@ -4160,6 +4163,52 @@ async function runPrerender(config) {
4160
4163
  toRemove.forEach((el) => { if (document.contains(el)) el.remove(); });
4161
4164
  });
4162
4165
 
4166
+ // Tag baked x-for/x-if clones that Alpine produced during prerender but
4167
+ // whose <template> is still in the output (so Alpine WILL re-render the
4168
+ // list/conditional at runtime). Without this, the runtime shows both the
4169
+ // baked copy AND Alpine's fresh render — a duplicate (the hero file tabs,
4170
+ // the docs eyebrow + article, etc.). We KEEP the baked copies in the
4171
+ // shipped HTML so crawlers see the content, and tag them so the loader can
4172
+ // remove them right before Alpine boots — giving exactly one live render.
4173
+ //
4174
+ // Identify clones via Alpine's own bookkeeping rather than DOM heuristics:
4175
+ // x-for tracks its generated elements in template._x_lookup, and x-if in
4176
+ // template._x_currentIfEl. Templates already removed earlier (static-bake
4177
+ // freeze) or whose clones were already stripped (dynamic collapse) simply
4178
+ // have nothing to tag here. data-hydrate islands are left untouched.
4179
+ await page.evaluate(() => {
4180
+ const tag = (el) => {
4181
+ if (!el || el.nodeType !== 1 || !el.setAttribute) return;
4182
+ if (el.closest('[data-hydrate]')) return;
4183
+ el.setAttribute('data-mnfst-prerender-clone', '1');
4184
+ };
4185
+ document.querySelectorAll('template[x-for]').forEach((tpl) => {
4186
+ if (tpl.hasAttribute('data-hydrate') || tpl.closest('[data-hydrate]')) return;
4187
+ // Prefer Alpine's own lookup when populated; otherwise fall back to
4188
+ // walking the consecutive same-tag siblings Alpine emitted right after
4189
+ // the template (the pattern the collapse passes already use).
4190
+ let tagged = false;
4191
+ const lookup = tpl._x_lookup;
4192
+ if (lookup) {
4193
+ try { Object.values(lookup).forEach((el) => { if (el) { tag(el); tagged = true; } }); } catch { /* not iterable */ }
4194
+ }
4195
+ if (tagged) return;
4196
+ const first = tpl.content && tpl.content.firstElementChild;
4197
+ if (!first) return;
4198
+ const cloneTag = first.tagName;
4199
+ let n = tpl.nextElementSibling;
4200
+ while (n && n.tagName === cloneTag) {
4201
+ const next = n.nextElementSibling;
4202
+ tag(n);
4203
+ n = next;
4204
+ }
4205
+ });
4206
+ document.querySelectorAll('template[x-if]').forEach((tpl) => {
4207
+ if (tpl.hasAttribute('data-hydrate') || tpl.closest('[data-hydrate]')) return;
4208
+ tag(tpl._x_currentIfEl);
4209
+ });
4210
+ });
4211
+
4163
4212
  // SEO / AEO meta injection — see resolveConfig().seo for precedence layers.
4164
4213
  // Runs in the live page so prerender.meta expressions can use Alpine context
4165
4214
  // (real $x.* evaluation, not yaml-only paths). Each pass only fills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.27",
3
+ "version": "0.5.29",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {