mnfst-render 0.5.39 → 0.5.40
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.
- package/manifest.render.mjs +82 -35
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -317,12 +317,69 @@ function loadConfig(rootDir) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
function normalizeLocaleRouteExclude(val) {
|
|
320
|
+
const clean = (s) => String(s).trim().replace(/^\/+|\/+$/g, '');
|
|
320
321
|
if (val == null) return [];
|
|
321
|
-
if (Array.isArray(val)) return val.map(
|
|
322
|
-
if (typeof val === 'string') return val.split(',').map(
|
|
322
|
+
if (Array.isArray(val)) return val.map(clean).filter(Boolean);
|
|
323
|
+
if (typeof val === 'string') return val.split(',').map(clean).filter(Boolean);
|
|
323
324
|
return [];
|
|
324
325
|
}
|
|
325
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Shared predicate: true when route segment `seg` (no leading slash) falls
|
|
329
|
+
* under one of the excluded prefixes — prefix-matched on whole segments, so
|
|
330
|
+
* "docs" excludes "docs" itself and "docs/group/article" but not "docsite".
|
|
331
|
+
* Used by locale route expansion, link prefixing, and (mirrored in
|
|
332
|
+
* mnfst-publish) the publisher's expected-file computation.
|
|
333
|
+
*/
|
|
334
|
+
function isLocaleRouteExcluded(seg, localeRouteExclude) {
|
|
335
|
+
if (!seg || !localeRouteExclude || !localeRouteExclude.length) return false;
|
|
336
|
+
const clean = String(seg).replace(/^\/+|\/+$/g, '');
|
|
337
|
+
for (const prefix of localeRouteExclude) {
|
|
338
|
+
if (clean === prefix || clean.startsWith(prefix + '/')) return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Expand discovered route segments into the full path list to render:
|
|
345
|
+
* every locale-neutral route as-is, plus a `<locale>/<route>` variant for
|
|
346
|
+
* every non-default locale (and for the default locale itself, under its
|
|
347
|
+
* own slug, when there's more than one locale) — except routes under an
|
|
348
|
+
* excluded prefix (localeRouteExclude), which are never locale-prefixed.
|
|
349
|
+
* Pure/side-effect-free so it can be unit tested without Puppeteer.
|
|
350
|
+
*/
|
|
351
|
+
function expandLocaleRoutePaths({ routeSegments, locales, defaultLocale, localeRouteExclude }) {
|
|
352
|
+
const localeList = locales || [];
|
|
353
|
+
const localeSet = new Set(localeList.map((l) => String(l).toLowerCase()));
|
|
354
|
+
const localeNeutralSegments = routeSegments.filter((seg) => {
|
|
355
|
+
if (!seg) return true;
|
|
356
|
+
const first = seg.split('/')[0].toLowerCase();
|
|
357
|
+
return !localeSet.has(first);
|
|
358
|
+
});
|
|
359
|
+
const paths = new Set();
|
|
360
|
+
paths.add('');
|
|
361
|
+
for (const seg of routeSegments) paths.add(seg);
|
|
362
|
+
for (const locale of localeList.slice(1)) {
|
|
363
|
+
paths.add(locale);
|
|
364
|
+
for (const seg of localeNeutralSegments) {
|
|
365
|
+
if (!seg) continue;
|
|
366
|
+
if (isLocaleRouteExcluded(seg, localeRouteExclude)) continue;
|
|
367
|
+
paths.add(`${locale}/${seg}`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// Default locale also under its slug (e.g. /en/, /en/page-1) so linking is
|
|
371
|
+
// symmetric with other locales; canonical points to root. Skip this when
|
|
372
|
+
// there's only one locale — the duplicates serve no purpose and bloat the
|
|
373
|
+
// output (every page would be written twice: at root AND under /en/).
|
|
374
|
+
if (defaultLocale && localeList.length > 1) {
|
|
375
|
+
paths.add(defaultLocale);
|
|
376
|
+
for (const seg of localeNeutralSegments) {
|
|
377
|
+
if (seg !== '' && !isLocaleRouteExcluded(seg, localeRouteExclude)) paths.add(`${defaultLocale}/${seg}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return paths;
|
|
381
|
+
}
|
|
382
|
+
|
|
326
383
|
function resolveConfig() {
|
|
327
384
|
const cli = parseArgs();
|
|
328
385
|
const cwd = process.cwd();
|
|
@@ -1567,7 +1624,6 @@ function buildSubstitutionPairs(defaultLocaleData, targetLocaleData) {
|
|
|
1567
1624
|
function prefixLocaleInternalLinks(html, locale, locales, localeRouteExclude) {
|
|
1568
1625
|
if (!locale || !locales || !locales.length) return html;
|
|
1569
1626
|
const localeSet = new Set(locales);
|
|
1570
|
-
const excludeSet = new Set(localeRouteExclude || []);
|
|
1571
1627
|
|
|
1572
1628
|
// Match <a ... href="..." ...> — capture the href value
|
|
1573
1629
|
return html.replace(
|
|
@@ -1588,7 +1644,7 @@ function prefixLocaleInternalLinks(html, locale, locales, localeRouteExclude) {
|
|
|
1588
1644
|
if (/\.(css|js|json|svg|png|jpg|jpeg|gif|ico|woff2?|ttf|eot|pdf|xml|txt)$/i.test(href)) return full;
|
|
1589
1645
|
|
|
1590
1646
|
// Respect localeRouteExclude — these routes stay locale-neutral
|
|
1591
|
-
if (
|
|
1647
|
+
if (isLocaleRouteExcluded(withoutSlash, localeRouteExclude)) return full;
|
|
1592
1648
|
|
|
1593
1649
|
// Prefix with locale
|
|
1594
1650
|
return `${prefix}/${locale}${href}${suffix}`;
|
|
@@ -3214,35 +3270,11 @@ async function runPrerender(config) {
|
|
|
3214
3270
|
if (!segSet.has(p)) { routeSegments.push(p); segSet.add(p); }
|
|
3215
3271
|
}
|
|
3216
3272
|
}
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
});
|
|
3223
|
-
const paths = new Set();
|
|
3224
|
-
paths.add('');
|
|
3225
|
-
|
|
3226
|
-
for (const seg of routeSegments) {
|
|
3227
|
-
paths.add(seg);
|
|
3228
|
-
}
|
|
3229
|
-
for (const locale of locales.slice(1)) {
|
|
3230
|
-
paths.add(locale);
|
|
3231
|
-
for (const seg of localeNeutralSegments) {
|
|
3232
|
-
if (!seg) continue;
|
|
3233
|
-
paths.add(`${locale}/${seg}`);
|
|
3234
|
-
}
|
|
3235
|
-
}
|
|
3236
|
-
// Default locale also under its slug (e.g. /en/, /en/page-1) so linking is
|
|
3237
|
-
// symmetric with other locales; canonical points to root. Skip this when
|
|
3238
|
-
// there's only one locale — the duplicates serve no purpose and bloat the
|
|
3239
|
-
// output (every page would be written twice: at root AND under /en/).
|
|
3240
|
-
if (defaultLocale && locales.length > 1) {
|
|
3241
|
-
paths.add(defaultLocale);
|
|
3242
|
-
for (const seg of localeNeutralSegments) {
|
|
3243
|
-
if (seg !== '') paths.add(`${defaultLocale}/${seg}`);
|
|
3244
|
-
}
|
|
3245
|
-
}
|
|
3273
|
+
// Expand routeSegments into the full path list: locale-neutral routes as-is,
|
|
3274
|
+
// plus a `<locale>/<route>` variant per locale (skipping localeRouteExclude
|
|
3275
|
+
// prefixes). Factored into a pure function so it's unit-testable without
|
|
3276
|
+
// Puppeteer — see expandLocaleRoutePaths.
|
|
3277
|
+
const paths = expandLocaleRoutePaths({ routeSegments, locales, defaultLocale, localeRouteExclude: config.localeRouteExclude });
|
|
3246
3278
|
|
|
3247
3279
|
const NOT_FOUND_PATH = '__prerender_404__'; // URL path that matches no route so router shows x-route="!*" (404)
|
|
3248
3280
|
const pathList = [...paths, NOT_FOUND_PATH];
|
|
@@ -3355,7 +3387,10 @@ async function runPrerender(config) {
|
|
|
3355
3387
|
// Configurable via manifest.prerender.browserRecycleEvery.
|
|
3356
3388
|
const browserRecycleEvery = Math.max(0, pre.browserRecycleEvery ?? 40);
|
|
3357
3389
|
// Bounds on the recycle handshake and on a single page render. A stuck
|
|
3358
|
-
// page must never pin the whole run: each is capped, logged and stepped
|
|
3390
|
+
// page must never pin the whole run: each is capped, logged and stepped
|
|
3391
|
+
// over — and a page that hits the ceiling forces a browser recycle, since
|
|
3392
|
+
// a wedged page.evaluate() may have poisoned the shared browser process.
|
|
3393
|
+
// pageTimeout is configurable via manifest.prerender.pageTimeout; 0 disables.
|
|
3359
3394
|
const drainTimeoutMs = Math.max(0, pre.recycleDrainTimeout ?? 60000);
|
|
3360
3395
|
const recycleTimeoutMs = Math.max(0, pre.recycleTimeout ?? 180000);
|
|
3361
3396
|
const pageTimeoutMs = Math.max(0, pre.pageTimeout ?? Math.max(120000, (config.wait ?? 30000) * 4));
|
|
@@ -4814,6 +4849,11 @@ async function runPrerender(config) {
|
|
|
4814
4849
|
if (!finished) {
|
|
4815
4850
|
attemptToken.abandoned = true;
|
|
4816
4851
|
attemptToken.failure = { path: displayPath, message: `page render exceeded ${pageTimeoutMs}ms` };
|
|
4852
|
+
// A timed-out page is presumed to have wedged the browser (e.g.
|
|
4853
|
+
// a hung page.evaluate on a dead Chrome process) — force a
|
|
4854
|
+
// recycle once this path is done, even if a later retry
|
|
4855
|
+
// "succeeds" by reusing the same possibly-poisoned browser.
|
|
4856
|
+
recycleGate.requestRecycle();
|
|
4817
4857
|
}
|
|
4818
4858
|
} catch (err) {
|
|
4819
4859
|
// Unexpected exception escaped processPath (e.g. browser died mid-call).
|
|
@@ -4988,4 +5028,11 @@ if (_isDirectEntry) {
|
|
|
4988
5028
|
}
|
|
4989
5029
|
|
|
4990
5030
|
// Exports for the CLI bin script and for unit testing.
|
|
4991
|
-
export {
|
|
5031
|
+
export {
|
|
5032
|
+
main,
|
|
5033
|
+
markPrerenderedManifestComponents,
|
|
5034
|
+
normalizeLocaleRouteExclude,
|
|
5035
|
+
isLocaleRouteExcluded,
|
|
5036
|
+
expandLocaleRoutePaths,
|
|
5037
|
+
prefixLocaleInternalLinks,
|
|
5038
|
+
};
|