mnfst-render 0.5.43 → 0.5.44

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 +39 -10
  2. package/package.json +1 -1
@@ -27,20 +27,25 @@ async function importFromProject(moduleName) {
27
27
 
28
28
 
29
29
  async function flushAlpineEffects(page) {
30
+ // Alpine's scheduler can swallow a nextTick callback (a re-triggered
31
+ // already-flushed job is dropped), so this wait must be bounded or a page
32
+ // hangs idle until the per-page timeout kills it.
30
33
  await page
31
34
  .evaluate(() => {
32
35
  return new Promise((resolve) => {
36
+ const cap = setTimeout(resolve, 3000);
37
+ const done = () => { clearTimeout(cap); resolve(); };
33
38
  try {
34
39
  if (typeof Alpine !== 'undefined' && typeof Alpine.nextTick === 'function') {
35
40
  Alpine.nextTick(() => {
36
- if (typeof Alpine.nextTick === 'function') Alpine.nextTick(resolve);
37
- else resolve();
41
+ if (typeof Alpine.nextTick === 'function') Alpine.nextTick(done);
42
+ else done();
38
43
  });
39
44
  } else {
40
- queueMicrotask(resolve);
45
+ queueMicrotask(done);
41
46
  }
42
47
  } catch {
43
- resolve();
48
+ done();
44
49
  }
45
50
  });
46
51
  })
@@ -149,7 +154,10 @@ async function waitForManifestRenderReady(page, { allLocales, currentLocale, tim
149
154
  try {
150
155
  const cfg = window.ManifestDataConfig;
151
156
  const main = window.ManifestDataMain;
152
- const manifest = await cfg?.ensureManifest?.();
157
+ const manifest = await Promise.race([
158
+ Promise.resolve(cfg?.ensureManifest?.()),
159
+ new Promise((resolve) => setTimeout(resolve, 5000)),
160
+ ]);
153
161
  if (manifest?.data && typeof main?.loadDataSource === 'function') {
154
162
  const isAppwrite = cfg.isAppwriteCollection;
155
163
  for (const [name, source] of Object.entries(manifest.data)) {
@@ -164,9 +172,14 @@ async function waitForManifestRenderReady(page, { allLocales, currentLocale, tim
164
172
 
165
173
  // 6. Run component swapping explicitly so components tied to this route render
166
174
  // and trigger any $x accesses that start on-demand data loads.
175
+ // Bounded: a stranded component-load promise must not hang the
176
+ // pipeline past the render-ready timeout (step 7 would never run).
167
177
  if (window.ManifestComponentsSwapping?.processAll) {
168
178
  try {
169
- await window.ManifestComponentsSwapping.processAll(normalizedPath);
179
+ await Promise.race([
180
+ window.ManifestComponentsSwapping.processAll(normalizedPath),
181
+ new Promise((resolve) => setTimeout(resolve, ms)),
182
+ ]);
170
183
  } catch (e) {
171
184
  return { ok: false, reason: 'processAll-error', message: String(e?.message || e) };
172
185
  }
@@ -3608,11 +3621,20 @@ async function runPrerender(config) {
3608
3621
  console.error(' npm i -D puppeteer-core @sparticuz/chromium');
3609
3622
  process.exit(1);
3610
3623
  }
3624
+ // chrome-headless-shell: the new headless mode's captureScreenshot can
3625
+ // hang forever on macOS (frames never produced), which wedges og
3626
+ // snapshots and, through the shared browser, stalls sibling pages.
3611
3627
  return await puppeteer.default.launch({
3612
- headless: true,
3628
+ headless: 'shell',
3613
3629
  args: [
3614
3630
  '--no-sandbox',
3615
3631
  '--disable-setuid-sandbox',
3632
+ // Concurrency keeps sibling tabs backgrounded; without these flags a
3633
+ // backgrounded tab stops producing frames and firing rAF/timers, so
3634
+ // its captureScreenshot (og snapshots) and settle waits hang forever.
3635
+ '--disable-background-timer-throttling',
3636
+ '--disable-backgrounding-occluded-windows',
3637
+ '--disable-renderer-backgrounding',
3616
3638
  ],
3617
3639
  });
3618
3640
  }
@@ -4072,7 +4094,8 @@ async function runPrerender(config) {
4072
4094
  // small ones settle in well under 1 second.
4073
4095
  await page.waitForNetworkIdle({ idleTime: 1000, timeout: 8000 }).catch(() => { });
4074
4096
 
4075
- // DOM stability after network idle.
4097
+ // DOM stability after network idle. Hard-capped: a page that animates
4098
+ // continuously must not reset the quiet window forever.
4076
4099
  await page.evaluate(() => {
4077
4100
  return new Promise((resolve) => {
4078
4101
  const observer = new MutationObserver(() => {
@@ -4080,8 +4103,9 @@ async function runPrerender(config) {
4080
4103
  stable = setTimeout(finish, 500);
4081
4104
  });
4082
4105
  observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
4083
- const finish = () => { observer.disconnect(); resolve(); };
4106
+ const finish = () => { clearTimeout(cap); observer.disconnect(); resolve(); };
4084
4107
  let stable = setTimeout(finish, 500);
4108
+ const cap = setTimeout(finish, 15000);
4085
4109
  });
4086
4110
  }).catch(() => { });
4087
4111
 
@@ -4112,7 +4136,12 @@ async function runPrerender(config) {
4112
4136
  const ogImageHandled = !!config.seo.meta?.image
4113
4137
  || await page.evaluate(() => !!document.head.querySelector('meta[property="og:image"]'));
4114
4138
  if (!ogImageHandled) {
4115
- earlySnapshotUrl = await takeOgSnapshot(page, config.output, is404 ? '__404__' : pathSeg, globalAssetSig, ogCacheDir);
4139
+ // Bounded: a CDP call that never returns must cost this page its og
4140
+ // image, not the whole render (the meta fallback image covers it).
4141
+ earlySnapshotUrl = await Promise.race([
4142
+ takeOgSnapshot(page, config.output, is404 ? '__404__' : pathSeg, globalAssetSig, ogCacheDir),
4143
+ new Promise((resolve) => setTimeout(() => resolve(null), 30000)),
4144
+ ]);
4116
4145
  }
4117
4146
  }
4118
4147
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.43",
3
+ "version": "0.5.44",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {