mnfst-render 0.5.6 → 0.5.9

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 +64 -23
  2. package/package.json +1 -1
@@ -150,11 +150,11 @@ async function waitForManifestRenderReady(page, { allLocales, currentLocale, tim
150
150
  )
151
151
  .catch((e) => ({ ok: false, reason: 'evaluate', message: String(e) }));
152
152
 
153
- if (!result?.ok) {
154
- const parts = [`prerender: render-ready wait incomplete (${result?.reason ?? 'unknown'})`];
155
- if (result?.message) parts.push(result.message);
156
- process.stdout.write(`${parts.join('; ')}\n`);
157
- }
153
+ // Note: render-ready wait timeouts are silently tolerated. Earlier versions
154
+ // logged a warning per path, but it fires on essentially every route in
155
+ // projects whose data plugins don't dispatch `manifest:render-ready` (i.e.
156
+ // most of them), drowning the terminal in noise. The fallback timeout is
157
+ // intentional and benign — the DOM is still captured.
158
158
  }
159
159
 
160
160
  // --- Config ------------------------------------------------------------------
@@ -246,7 +246,11 @@ function resolveConfig() {
246
246
  : [],
247
247
  dryRun: !!cli.dryRun,
248
248
  debugPrerender: !!cli.debugPrerender,
249
- pipelineTimeout: 25000,
249
+ // Cap on the manifest:render-ready wait. When the data plugin dispatches
250
+ // the event, we resolve immediately; when it doesn't (most projects), we
251
+ // fall back to the timeout. 10s gives slow data plugin pipelines a
252
+ // chance while bounding worst-case per-path overhead.
253
+ pipelineTimeout: 10000,
250
254
  };
251
255
  }
252
256
 
@@ -976,8 +980,13 @@ function markPrerenderedManifestComponents(html) {
976
980
  // the runtime restoration reinstate the <x-*> tag and the components
977
981
  // plugin processes it normally on load.
978
982
  if (/\bdata-hydrate\b/i.test(a)) return full;
979
- const spacer = /\S/.test(a) ? ' ' : '';
980
- return `<${tag}${a}${spacer}data-pre-rendered="1">`;
983
+ // CRITICAL: always insert a leading space before the injected attribute.
984
+ // For tags with no existing attributes (e.g. `<x-sidebar>`), `a` is empty
985
+ // and concatenating directly produces `<x-sidebardata-pre-rendered=...>`,
986
+ // which mangles the tag name and prevents the components plugin from
987
+ // recognising it. The trailing-space normalisation on `a` keeps the
988
+ // output tidy when there ARE existing attributes.
989
+ return `<${tag}${a.replace(/\s+$/, '')} data-pre-rendered="1">`;
981
990
  });
982
991
  }
983
992
 
@@ -2024,16 +2033,27 @@ async function runPrerender(config) {
2024
2033
  timeout: Math.min(timeout, 30000),
2025
2034
  });
2026
2035
 
2036
+ // Settle waits. These give Manifest plugins (especially the components
2037
+ // plugin, which lazy-fetches each component HTML over the network) time
2038
+ // to finish loading and expanding everything before we snapshot. Each
2039
+ // wait is bounded; large projects with many components need the full
2040
+ // budget on cold runs, but small projects settle long before the cap.
2041
+ //
2042
+ // Lowered from the original "any wait could hold the prerender for ~50s
2043
+ // per path" defaults, but kept generous enough that Playcom-scale sites
2044
+ // (~10 preloaded components, dozens of lazy components) actually finish
2045
+ // expanding before snapshot. Earlier reductions were too aggressive and
2046
+ // left unexpanded `<x-*>` placeholders in the output.
2027
2047
  await Promise.race([
2028
2048
  page.evaluate(() => {
2029
2049
  return new Promise((resolve) => {
2030
2050
  const done = () => resolve();
2031
- const t = setTimeout(done, 6000);
2051
+ const t = setTimeout(done, 3000);
2032
2052
  window.addEventListener(
2033
2053
  'manifest:routing-ready',
2034
2054
  () => {
2035
2055
  clearTimeout(t);
2036
- setTimeout(done, 2000);
2056
+ setTimeout(done, 1000);
2037
2057
  },
2038
2058
  { once: true }
2039
2059
  );
@@ -2042,13 +2062,14 @@ async function runPrerender(config) {
2042
2062
  new Promise((_, rej) => setTimeout(() => rej(new Error('ready timeout')), timeout)),
2043
2063
  ]).catch(() => { });
2044
2064
 
2045
- // Ensure manifest.min.js (dynamic loader) has run and injected plugin scripts before snapshot.
2046
- // Static output still runs the loader and Alpine; we just capture the DOM after they've set up.
2065
+ // Ensure the dynamic loader has injected at least one plugin script.
2066
+ // In practice this happens within ~100ms but allow up to 3s for cold
2067
+ // CDN cache or slow disk.
2047
2068
  await page.evaluate(() => {
2048
2069
  return new Promise((resolve) => {
2049
2070
  const check = () => document.querySelectorAll('script[src*="manifest"]').length >= 2;
2050
2071
  if (check()) return resolve();
2051
- const deadline = Date.now() + 5000;
2072
+ const deadline = Date.now() + 3000;
2052
2073
  const t = setInterval(() => {
2053
2074
  if (check() || Date.now() >= deadline) {
2054
2075
  clearInterval(t);
@@ -2058,19 +2079,21 @@ async function runPrerender(config) {
2058
2079
  });
2059
2080
  }).catch(() => { });
2060
2081
 
2061
- await page.waitForNetworkIdle({ idleTime: 1500, timeout: 10000 }).catch(() => { });
2082
+ // Network idle: drain pending fetches (component templates, data sources,
2083
+ // markdown files, icon SVGs). Larger projects need the full window;
2084
+ // small ones settle in well under 1 second.
2085
+ await page.waitForNetworkIdle({ idleTime: 1000, timeout: 8000 }).catch(() => { });
2062
2086
 
2087
+ // DOM stability after network idle.
2063
2088
  await page.evaluate(() => {
2064
2089
  return new Promise((resolve) => {
2065
2090
  const observer = new MutationObserver(() => {
2066
2091
  clearTimeout(stable);
2067
- stable = setTimeout(resolve, 800);
2092
+ stable = setTimeout(finish, 500);
2068
2093
  });
2069
2094
  observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
2070
- let stable = setTimeout(() => {
2071
- observer.disconnect();
2072
- resolve();
2073
- }, 800);
2095
+ const finish = () => { observer.disconnect(); resolve(); };
2096
+ let stable = setTimeout(finish, 500);
2074
2097
  });
2075
2098
  }).catch(() => { });
2076
2099
 
@@ -3019,7 +3042,25 @@ async function runPrerender(config) {
3019
3042
 
3020
3043
  }
3021
3044
 
3022
- main().catch((err) => {
3023
- console.error('prerender:', err);
3024
- process.exit(1);
3025
- });
3045
+ // Only run main() when invoked as the CLI entry point — this lets tests
3046
+ // import this module to access internal helpers (e.g.
3047
+ // markPrerenderedManifestComponents) without triggering a full prerender.
3048
+ const isCliEntry = (() => {
3049
+ try {
3050
+ const invoked = process.argv[1] && new URL('file://' + process.argv[1]).href;
3051
+ return invoked === import.meta.url;
3052
+ } catch {
3053
+ return false;
3054
+ }
3055
+ })();
3056
+
3057
+ if (isCliEntry) {
3058
+ main().catch((err) => {
3059
+ console.error('prerender:', err);
3060
+ process.exit(1);
3061
+ });
3062
+ }
3063
+
3064
+ // Exports for unit testing. These are intentionally not part of any public
3065
+ // API — they exist so the e2e harness can directly exercise pure helpers.
3066
+ export { markPrerenderedManifestComponents };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.6",
3
+ "version": "0.5.9",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {