mnfst-render 0.5.51 → 0.5.52
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 +218 -69
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -524,6 +524,25 @@ function expandLocaleRoutePaths({ routeSegments, locales, defaultLocale, localeR
|
|
|
524
524
|
return paths;
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
/**
|
|
528
|
+
* Incremental participation policy. Recording is on whenever the project
|
|
529
|
+
* PARTICIPATES: config `render.incremental: true`, the --incremental flag, or
|
|
530
|
+
* a valid ledger already on disk (so one opted-in render upgrades every later
|
|
531
|
+
* plain run). --no-incremental disables skipping only — a participating run
|
|
532
|
+
* still records and refreshes the ledger. Explicit config `incremental:
|
|
533
|
+
* false` is fully off, ledger or not. Never-opted-in stays fully inert.
|
|
534
|
+
*/
|
|
535
|
+
function resolveIncrementalPolicy({ configValue, flagIncremental, flagNoIncremental, ledgerExists }) {
|
|
536
|
+
const participates = flagIncremental === true
|
|
537
|
+
? true
|
|
538
|
+
: configValue === false ? false : (configValue === true || !!ledgerExists);
|
|
539
|
+
return {
|
|
540
|
+
record: participates,
|
|
541
|
+
skip: participates && flagNoIncremental !== true,
|
|
542
|
+
explicit: flagIncremental === true || configValue === true,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
|
|
527
546
|
function resolveConfig() {
|
|
528
547
|
const cli = parseArgs();
|
|
529
548
|
const cwd = process.cwd();
|
|
@@ -580,9 +599,21 @@ function resolveConfig() {
|
|
|
580
599
|
paths: Array.isArray(pre.paths)
|
|
581
600
|
? pre.paths.map((p) => String(p).replace(/^\/+|\/+$/g, '')).filter(Boolean)
|
|
582
601
|
: [],
|
|
583
|
-
// Incremental renders
|
|
584
|
-
//
|
|
585
|
-
|
|
602
|
+
// Incremental renders: see resolveIncrementalPolicy. `incremental` gates
|
|
603
|
+
// skipping; `incrementalRecord` gates capture + ledger writes.
|
|
604
|
+
...(() => {
|
|
605
|
+
const p = resolveIncrementalPolicy({
|
|
606
|
+
configValue: pre.incremental,
|
|
607
|
+
flagIncremental: cli.incremental,
|
|
608
|
+
flagNoIncremental: cli.noIncremental,
|
|
609
|
+
ledgerExists: loadRouteLedger(root) !== null,
|
|
610
|
+
});
|
|
611
|
+
return { incremental: p.skip, incrementalRecord: p.record, incrementalExplicit: p.explicit };
|
|
612
|
+
})(),
|
|
613
|
+
// Requests ABORTED during prerender (substring match, `*` glob within a
|
|
614
|
+
// pattern). blockTelemetry (default on) adds TELEMETRY_BLOCK_PATTERNS.
|
|
615
|
+
blockRequests: Array.isArray(pre.blockRequests) ? pre.blockRequests.map((p) => String(p)).filter(Boolean) : [],
|
|
616
|
+
blockTelemetry: pre.blockTelemetry !== false,
|
|
586
617
|
dryRun: !!cli.dryRun,
|
|
587
618
|
forceLock: !!cli.forceLock,
|
|
588
619
|
debugPrerender: !!cli.debugPrerender,
|
|
@@ -3492,6 +3523,121 @@ const PINNED_VERSION_RE = /@\d+\.\d+\.\d+/;
|
|
|
3492
3523
|
// Bodies above this are never fingerprinted — the route renders instead.
|
|
3493
3524
|
const EXTERNAL_BODY_HASH_LIMIT = 8 * 1024 * 1024;
|
|
3494
3525
|
|
|
3526
|
+
// A prerender is not a visitor (Playcom measured 465 fake GA page_views per
|
|
3527
|
+
// render). Collection endpoints only — never script loads.
|
|
3528
|
+
const TELEMETRY_BLOCK_PATTERNS = [
|
|
3529
|
+
'google-analytics.com/g/collect',
|
|
3530
|
+
'*.google-analytics.com/mp/collect',
|
|
3531
|
+
'stats.g.doubleclick.net',
|
|
3532
|
+
'*.analytics.google.com/g/collect',
|
|
3533
|
+
'plausible.io/api/event',
|
|
3534
|
+
'api.segment.io/v1',
|
|
3535
|
+
'api.mixpanel.com/track',
|
|
3536
|
+
'.facebook.com/tr?',
|
|
3537
|
+
'.facebook.com/tr/',
|
|
3538
|
+
];
|
|
3539
|
+
|
|
3540
|
+
/** URL predicate from block patterns: substring match, `*` globs within a
|
|
3541
|
+
* pattern. null when there is nothing to block. */
|
|
3542
|
+
function compileBlockMatcher(patterns) {
|
|
3543
|
+
const regs = [];
|
|
3544
|
+
for (const p of patterns || []) {
|
|
3545
|
+
const src = String(p).split('*').map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*');
|
|
3546
|
+
if (src) regs.push(new RegExp(src));
|
|
3547
|
+
}
|
|
3548
|
+
if (!regs.length) return null;
|
|
3549
|
+
return (url) => regs.some((r) => r.test(String(url)));
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
/** Cross-origin response → { validator } | { flag } | {} (2xx without
|
|
3553
|
+
* validator: content-address the body). A 304 is the page's OWN conditional
|
|
3554
|
+
* revalidation answered "unchanged" — a hit, not a failure: the validator
|
|
3555
|
+
* comes from the 304's headers, else the request's conditional values.
|
|
3556
|
+
* A 304 has no body, so it never falls through to buffer(). */
|
|
3557
|
+
function classifyExternalResponse(status, responseHeaders = {}, requestHeaders = {}) {
|
|
3558
|
+
if (status === 304) {
|
|
3559
|
+
const etag = responseHeaders['etag'] || requestHeaders['if-none-match'];
|
|
3560
|
+
if (etag) return { validator: { etag } };
|
|
3561
|
+
const lastModified = responseHeaders['last-modified'] || requestHeaders['if-modified-since'];
|
|
3562
|
+
if (lastModified) return { validator: { lastModified } };
|
|
3563
|
+
return { flag: 'external 304 without validator' };
|
|
3564
|
+
}
|
|
3565
|
+
if (status < 200 || status >= 300) return { flag: `external ${status}` };
|
|
3566
|
+
const etag = responseHeaders['etag'];
|
|
3567
|
+
const lastModified = responseHeaders['last-modified'];
|
|
3568
|
+
if (etag || lastModified) return { validator: etag ? { etag } : { lastModified } };
|
|
3569
|
+
return {};
|
|
3570
|
+
}
|
|
3571
|
+
|
|
3572
|
+
/**
|
|
3573
|
+
* Incremental input-capture hooks over plain request/response shapes
|
|
3574
|
+
* (Puppeteer-independent for unit tests). Same-origin GETs map to disk files
|
|
3575
|
+
* via the static server's own resolution; anything un-fingerprintable marks
|
|
3576
|
+
* the route non-cacheable. A blocked request never happened: not an input,
|
|
3577
|
+
* not a failure, never awaited.
|
|
3578
|
+
*/
|
|
3579
|
+
function createCaptureHooks({ capture, localOrigin, resolveRequest, isBlocked }) {
|
|
3580
|
+
const flag = (reason) => {
|
|
3581
|
+
if (!capture.nonCacheable) { capture.nonCacheable = true; capture.reason = reason; }
|
|
3582
|
+
};
|
|
3583
|
+
return {
|
|
3584
|
+
request({ url, method }) {
|
|
3585
|
+
try {
|
|
3586
|
+
const u = new URL(url);
|
|
3587
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') return;
|
|
3588
|
+
if (isBlocked && isBlocked(u.href)) return;
|
|
3589
|
+
if (method !== 'GET') { flag(`non-GET ${method} ${u.href}`); return; }
|
|
3590
|
+
if (u.origin !== localOrigin) {
|
|
3591
|
+
// Pinned-version URL: the URL is its own identity. Anything else
|
|
3592
|
+
// must get a PROCESSED response (validator or content hash) by the
|
|
3593
|
+
// end of the route or it stays in `awaiting` → route renders.
|
|
3594
|
+
if (PINNED_VERSION_RE.test(u.pathname)) capture.externalPinned.add(u.href);
|
|
3595
|
+
else capture.awaiting.add(u.href);
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
const rel = resolveRequest(u.pathname);
|
|
3599
|
+
if (rel) { capture.requested.set(u.pathname, rel); capture.files.add(rel); }
|
|
3600
|
+
else flag(`unresolvable request ${u.pathname}`);
|
|
3601
|
+
} catch { flag('unparseable request URL'); }
|
|
3602
|
+
},
|
|
3603
|
+
requestFailed({ url }) {
|
|
3604
|
+
if (isBlocked && isBlocked(url)) return;
|
|
3605
|
+
try { capture.awaiting.delete(new URL(url).href); } catch { /* flagged below */ }
|
|
3606
|
+
flag(`request failed: ${url}`);
|
|
3607
|
+
},
|
|
3608
|
+
response({ url, status, headers, requestHeaders, buffer }) {
|
|
3609
|
+
try {
|
|
3610
|
+
const u = new URL(url);
|
|
3611
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:') return;
|
|
3612
|
+
if (isBlocked && isBlocked(u.href)) return;
|
|
3613
|
+
if (u.origin === localOrigin || PINNED_VERSION_RE.test(u.pathname)) {
|
|
3614
|
+
if (status >= 400) flag(`${status} ${u.href}`);
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
const verdict = classifyExternalResponse(status, headers || {}, requestHeaders || {});
|
|
3618
|
+
if (verdict.flag) { capture.awaiting.delete(u.href); flag(`${verdict.flag} ${u.href}`); return; }
|
|
3619
|
+
if (verdict.validator) {
|
|
3620
|
+
capture.externalValidated.set(u.href, verdict.validator);
|
|
3621
|
+
capture.awaiting.delete(u.href);
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
// No validator (e.g. cdn.manifestx.dev): content-address the bytes;
|
|
3625
|
+
// revalidation re-downloads and compares. Unreadable or oversized
|
|
3626
|
+
// body → render. `awaiting` clears only once the hash is recorded.
|
|
3627
|
+
capture.pending.push(
|
|
3628
|
+
Promise.resolve().then(buffer)
|
|
3629
|
+
.then((body) => {
|
|
3630
|
+
if (body.length > EXTERNAL_BODY_HASH_LIMIT) { flag(`external response too large to fingerprint: ${u.href}`); return; }
|
|
3631
|
+
capture.externalValidated.set(u.href, { contentHash: sha256Full(body) });
|
|
3632
|
+
capture.awaiting.delete(u.href);
|
|
3633
|
+
})
|
|
3634
|
+
.catch(() => flag(`external response unreadable: ${u.href}`))
|
|
3635
|
+
);
|
|
3636
|
+
} catch { flag('unparseable response URL'); }
|
|
3637
|
+
},
|
|
3638
|
+
};
|
|
3639
|
+
}
|
|
3640
|
+
|
|
3495
3641
|
const externalValidatorToken = (url, v) =>
|
|
3496
3642
|
`${url}#etag:${v?.etag || ''}|lm:${v?.lastModified || ''}|ch:${v?.contentHash || ''}`;
|
|
3497
3643
|
|
|
@@ -3656,7 +3802,7 @@ async function evaluateRouteSkip({ entry, hashInput, resolveRequest, revalidateE
|
|
|
3656
3802
|
* Run-scoped incremental state: ledger, memoized hashing/resolution, the skip
|
|
3657
3803
|
* pass, per-route capture records, and the end-of-run ledger write.
|
|
3658
3804
|
*/
|
|
3659
|
-
function createIncrementalRun({ rootResolved, previousOutput, stagingOutput, globalKey, deps }) {
|
|
3805
|
+
function createIncrementalRun({ rootResolved, previousOutput, stagingOutput, globalKey, deps, skipEnabled = true }) {
|
|
3660
3806
|
const ledger = loadRouteLedger(rootResolved);
|
|
3661
3807
|
const revalidate = createExternalRevalidator();
|
|
3662
3808
|
const hashFileAt = (base, relPath) => {
|
|
@@ -3671,7 +3817,7 @@ function createIncrementalRun({ rootResolved, previousOutput, stagingOutput, glo
|
|
|
3671
3817
|
};
|
|
3672
3818
|
const captures = new Map(); // routeKey → capture record of a route rendered this run
|
|
3673
3819
|
const carried = new Map(); // routeKey → previous entry revalidated and reused this run
|
|
3674
|
-
const state = { disabled: false, skipped: 0, reasons: {} };
|
|
3820
|
+
const state = { disabled: false, skipped: 0, reasons: {}, skipEnabled };
|
|
3675
3821
|
const disable = (reason) => {
|
|
3676
3822
|
if (state.disabled) return;
|
|
3677
3823
|
state.disabled = true;
|
|
@@ -3702,6 +3848,7 @@ function createIncrementalRun({ rootResolved, previousOutput, stagingOutput, glo
|
|
|
3702
3848
|
/** Skip pass: returns the subset of `paths` that must render; copies the
|
|
3703
3849
|
* previous outputs of the rest into staging and carries their entries. */
|
|
3704
3850
|
async partition(paths) {
|
|
3851
|
+
if (!skipEnabled) return paths; // recording-only run: render all, refresh the ledger
|
|
3705
3852
|
if (!ledger) {
|
|
3706
3853
|
for (let i = 0; i < paths.length; i++) noteRender('no ledger entry');
|
|
3707
3854
|
return paths;
|
|
@@ -4437,15 +4584,22 @@ async function runPrerender(config) {
|
|
|
4437
4584
|
// baseHtmlCache: base path segment → raw DOM HTML captured before any Node.js transforms
|
|
4438
4585
|
const baseHtmlCache = new Map();
|
|
4439
4586
|
|
|
4440
|
-
// --- Incremental skip pass (
|
|
4587
|
+
// --- Incremental skip pass (see the incremental-rendering section) ----------
|
|
4441
4588
|
// Skips reuse files from the PREVIOUS final output, which still exists while
|
|
4442
|
-
// this run builds into staging.
|
|
4589
|
+
// this run builds into staging. Recording runs whenever the project
|
|
4590
|
+
// participates (incrementalRecord); skipping only when `incremental` is on.
|
|
4591
|
+
// Never-opted-in → no capture, no ledger, no new work.
|
|
4443
4592
|
let inc = null;
|
|
4444
|
-
if (config.
|
|
4593
|
+
if (config.incrementalRecord && !config.serve) {
|
|
4445
4594
|
// Input capture maps request pathnames to disk via the built-in server's
|
|
4446
4595
|
// resolution; an external --local server voids that mapping.
|
|
4447
|
-
|
|
4448
|
-
|
|
4596
|
+
if (config.incrementalExplicit) {
|
|
4597
|
+
process.stdout.write('prerender: incremental needs the built-in static server — ignoring it for this run\n');
|
|
4598
|
+
}
|
|
4599
|
+
} else if (config.incrementalRecord) {
|
|
4600
|
+
if (!config.incrementalExplicit) {
|
|
4601
|
+
process.stdout.write('prerender: incremental active from existing ledger (.mnfst-cache/routes.json; --no-incremental to disable)\n');
|
|
4602
|
+
}
|
|
4449
4603
|
let manifestContentHash = 'missing';
|
|
4450
4604
|
try { manifestContentHash = sha256Full(readFileSync(join(config.root, 'manifest.json'))); } catch { /* missing */ }
|
|
4451
4605
|
// Output-relevant resolved options (CLI included). pathList is in here
|
|
@@ -4466,6 +4620,9 @@ async function runPrerender(config) {
|
|
|
4466
4620
|
liveUrl: config.liveUrl,
|
|
4467
4621
|
utilitiesBundle: bundleUtilities,
|
|
4468
4622
|
tailwindBuilt,
|
|
4623
|
+
// Blocking changes what a page loads; entries from another regime must not skip.
|
|
4624
|
+
blockRequests: [...(config.blockRequests || [])].sort(),
|
|
4625
|
+
blockTelemetry: config.blockTelemetry,
|
|
4469
4626
|
pathList: [...pathList].sort(),
|
|
4470
4627
|
}));
|
|
4471
4628
|
inc = createIncrementalRun({
|
|
@@ -4481,8 +4638,23 @@ async function runPrerender(config) {
|
|
|
4481
4638
|
getRawHtml: (seg) => baseHtmlCache.get(seg),
|
|
4482
4639
|
pushUtilityBlock: (b) => utilityBlocks.push(b),
|
|
4483
4640
|
},
|
|
4641
|
+
skipEnabled: config.incremental,
|
|
4484
4642
|
});
|
|
4485
4643
|
}
|
|
4644
|
+
|
|
4645
|
+
// --- Request blocking during prerender ---------------------------------------
|
|
4646
|
+
// Telemetry collection endpoints are blocked by default (blockTelemetry:
|
|
4647
|
+
// false opts out); render.blockRequests adds user patterns. Blocked
|
|
4648
|
+
// requests are aborted via CDP and are invisible to incremental capture.
|
|
4649
|
+
const blockPatterns = [
|
|
4650
|
+
...(config.blockTelemetry ? TELEMETRY_BLOCK_PATTERNS : []),
|
|
4651
|
+
...(config.blockRequests || []),
|
|
4652
|
+
];
|
|
4653
|
+
const blockMatcher = compileBlockMatcher(blockPatterns);
|
|
4654
|
+
const telemetryMatcher = config.blockTelemetry ? compileBlockMatcher(TELEMETRY_BLOCK_PATTERNS) : null;
|
|
4655
|
+
const cdpBlockedUrls = blockPatterns.map((p) => `*${p}*`);
|
|
4656
|
+
let telemetryBlockedCount = 0;
|
|
4657
|
+
|
|
4486
4658
|
const puppeteerRenderPaths = inc ? await inc.partition(puppeteerPaths) : puppeteerPaths;
|
|
4487
4659
|
let incRendered = puppeteerRenderPaths.length;
|
|
4488
4660
|
const puppeteerTotal = puppeteerRenderPaths.length;
|
|
@@ -4537,64 +4709,22 @@ async function runPrerender(config) {
|
|
|
4537
4709
|
externalPinned: new Set(), externalValidated: new Map(),
|
|
4538
4710
|
awaiting: new Set(), pending: [], nonCacheable: false, reason: null,
|
|
4539
4711
|
};
|
|
4540
|
-
const
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
try {
|
|
4546
|
-
const u = new URL(req.url());
|
|
4547
|
-
if (u.protocol !== 'http:' && u.protocol !== 'https:') return;
|
|
4548
|
-
if (req.method() !== 'GET') { flag(`non-GET ${req.method()} ${u.href}`); return; }
|
|
4549
|
-
if (u.origin !== localOrigin) {
|
|
4550
|
-
// Pinned-version URL: the URL is its own identity. Anything else
|
|
4551
|
-
// must get a PROCESSED response (validator or content hash) by the
|
|
4552
|
-
// end of the route or it stays in `awaiting` → route renders.
|
|
4553
|
-
if (PINNED_VERSION_RE.test(u.pathname)) capture.externalPinned.add(u.href);
|
|
4554
|
-
else capture.awaiting.add(u.href);
|
|
4555
|
-
return;
|
|
4556
|
-
}
|
|
4557
|
-
const rel = resolveStaticRequestFile(rootResolved, u.pathname);
|
|
4558
|
-
if (rel) { capture.requested.set(u.pathname, rel); capture.files.add(rel); }
|
|
4559
|
-
else flag(`unresolvable request ${u.pathname}`);
|
|
4560
|
-
} catch { flag('unparseable request URL'); }
|
|
4561
|
-
});
|
|
4562
|
-
page.on('requestfailed', (req) => {
|
|
4563
|
-
try { capture.awaiting.delete(new URL(req.url()).href); } catch { /* flagged below */ }
|
|
4564
|
-
flag(`request failed: ${req.url()}`);
|
|
4565
|
-
});
|
|
4566
|
-
page.on('response', (res) => {
|
|
4567
|
-
try {
|
|
4568
|
-
const u = new URL(res.url());
|
|
4569
|
-
if (u.protocol !== 'http:' && u.protocol !== 'https:') return;
|
|
4570
|
-
const status = res.status();
|
|
4571
|
-
if (u.origin === localOrigin || PINNED_VERSION_RE.test(u.pathname)) {
|
|
4572
|
-
if (status >= 400) flag(`${status} ${u.href}`);
|
|
4573
|
-
return;
|
|
4574
|
-
}
|
|
4575
|
-
if (status < 200 || status >= 300) { flag(`external ${status} ${u.href}`); capture.awaiting.delete(u.href); return; }
|
|
4576
|
-
const headers = res.headers();
|
|
4577
|
-
const etag = headers['etag'];
|
|
4578
|
-
const lastModified = headers['last-modified'];
|
|
4579
|
-
if (etag || lastModified) {
|
|
4580
|
-
capture.externalValidated.set(u.href, etag ? { etag } : { lastModified });
|
|
4581
|
-
capture.awaiting.delete(u.href);
|
|
4582
|
-
return;
|
|
4583
|
-
}
|
|
4584
|
-
// No validator (e.g. cdn.manifestx.dev): content-address the bytes;
|
|
4585
|
-
// revalidation re-downloads and compares. Unreadable or oversized
|
|
4586
|
-
// body → render. `awaiting` clears only once the hash is recorded.
|
|
4587
|
-
capture.pending.push(
|
|
4588
|
-
res.buffer()
|
|
4589
|
-
.then((body) => {
|
|
4590
|
-
if (body.length > EXTERNAL_BODY_HASH_LIMIT) { flag(`external response too large to fingerprint: ${u.href}`); return; }
|
|
4591
|
-
capture.externalValidated.set(u.href, { contentHash: sha256Full(body) });
|
|
4592
|
-
capture.awaiting.delete(u.href);
|
|
4593
|
-
})
|
|
4594
|
-
.catch(() => flag(`external response unreadable: ${u.href}`))
|
|
4595
|
-
);
|
|
4596
|
-
} catch { flag('unparseable response URL'); }
|
|
4712
|
+
const hooks = createCaptureHooks({
|
|
4713
|
+
capture,
|
|
4714
|
+
localOrigin: new URL(config.localUrl).origin,
|
|
4715
|
+
resolveRequest: (pathname) => resolveStaticRequestFile(rootResolved, pathname),
|
|
4716
|
+
isBlocked: blockMatcher,
|
|
4597
4717
|
});
|
|
4718
|
+
page.on('request', (req) => hooks.request({ url: req.url(), method: req.method() }));
|
|
4719
|
+
page.on('requestfailed', (req) => hooks.requestFailed({ url: req.url() }));
|
|
4720
|
+
page.on('response', (res) => hooks.response({
|
|
4721
|
+
url: res.url(), status: res.status(), headers: res.headers(),
|
|
4722
|
+
requestHeaders: res.request().headers(),
|
|
4723
|
+
buffer: () => res.buffer(),
|
|
4724
|
+
}));
|
|
4725
|
+
}
|
|
4726
|
+
if (telemetryMatcher) {
|
|
4727
|
+
page.on('requestfailed', (req) => { if (telemetryMatcher(req.url())) telemetryBlockedCount++; });
|
|
4598
4728
|
}
|
|
4599
4729
|
// Render at a typical desktop viewport so layouts dependent on viewport
|
|
4600
4730
|
// width (responsive flex/grid, container queries, media queries) settle
|
|
@@ -4604,6 +4734,13 @@ async function runPrerender(config) {
|
|
|
4604
4734
|
// viewport-driven flex distribution.
|
|
4605
4735
|
await page.setViewport({ width: 1200, height: 800, deviceScaleFactor: 1 });
|
|
4606
4736
|
try {
|
|
4737
|
+
// CDP URL blocking (telemetry + render.blockRequests): requests abort at
|
|
4738
|
+
// the network layer; coexists with the page.on capture listeners above.
|
|
4739
|
+
if (cdpBlockedUrls.length > 0) {
|
|
4740
|
+
const cdp = await page.createCDPSession();
|
|
4741
|
+
await cdp.send('Network.enable');
|
|
4742
|
+
await cdp.send('Network.setBlockedURLs', { urls: cdpBlockedUrls });
|
|
4743
|
+
}
|
|
4607
4744
|
// Align <html lang> with the URL being prerendered before any app script runs.
|
|
4608
4745
|
// initializeDataSourcesPlugin picks locale from document.documentElement.lang first; a mismatch
|
|
4609
4746
|
// (e.g. headless default vs /en/...) leaves $x.* empty while x-route sections still render.
|
|
@@ -6059,8 +6196,15 @@ async function runPrerender(config) {
|
|
|
6059
6196
|
}
|
|
6060
6197
|
|
|
6061
6198
|
if (inc) {
|
|
6062
|
-
|
|
6063
|
-
|
|
6199
|
+
if (!inc.state.skipEnabled) {
|
|
6200
|
+
process.stdout.write(`prerender: rendered ${incRendered} (ledger refreshed, skipping off)\n`);
|
|
6201
|
+
} else {
|
|
6202
|
+
const why = Object.entries(inc.state.reasons).sort((a, b) => b[1] - a[1]).map(([r, c]) => `${c} ${r}`);
|
|
6203
|
+
process.stdout.write(`prerender: skipped ${inc.state.skipped} unchanged, rendered ${incRendered}${why.length ? ` (${why.join(', ')})` : ''}\n`);
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
if (telemetryBlockedCount > 0) {
|
|
6207
|
+
process.stdout.write(`prerender: blocked ${telemetryBlockedCount} telemetry requests (render.blockTelemetry: false to allow)\n`);
|
|
6064
6208
|
}
|
|
6065
6209
|
|
|
6066
6210
|
if (config.debugPrerender) {
|
|
@@ -6187,4 +6331,9 @@ export {
|
|
|
6187
6331
|
isSafeRelPath,
|
|
6188
6332
|
settleCaptureAwaiting,
|
|
6189
6333
|
EXTERNAL_BODY_HASH_LIMIT,
|
|
6334
|
+
resolveIncrementalPolicy,
|
|
6335
|
+
classifyExternalResponse,
|
|
6336
|
+
createCaptureHooks,
|
|
6337
|
+
compileBlockMatcher,
|
|
6338
|
+
TELEMETRY_BLOCK_PATTERNS,
|
|
6190
6339
|
};
|