From ec56a2211f03283cba3b48c479779b69628d5653 Mon Sep 17 00:00:00 2001 From: zhangxiang Date: Sat, 5 Sep 2026 08:50:39 +0800 Subject: [PATCH] =?UTF-8?q?chore(audit):=20=E5=90=8C=E6=BA=90=20script=20?= =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=8C=89=E5=90=8E=E6=9E=9C=E5=AF=BC=E5=90=91?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=EF=BC=88=E6=97=A0=E6=9C=AA=E6=8D=95=E8=8E=B7?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E4=B8=8D=E7=95=99=E7=97=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App Router 静态 chunk 预取不带 Sec-Purpose 头无法按头识别; 必需 chunk 被取消必然伴随 hydration 失败(pageErrors 非空,仍以 P1 记录), 故同源 script ERR_ABORTED + 路由无未捕获异常 = 运行时自行取代,不留痕。 终验:33 路由 × 双主题 P1/P2/P3/INFO 全零。 --- scripts/audit/ui-audit.mjs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/audit/ui-audit.mjs b/scripts/audit/ui-audit.mjs index 32f434c..9b55a00 100644 --- a/scripts/audit/ui-audit.mjs +++ b/scripts/audit/ui-audit.mjs @@ -541,15 +541,20 @@ function scoreRoute(r) { if (perr.length) issues.push({ sev: 'P1', cat: 'UE/稳定性', msg: `未捕获异常 ${perr.length} 条`, detail: perr.slice(0, 3) }); const rfailAll = [...(d.failedRequests || []), ...(m.failedRequests || [])]; const rfailNoise = rfailAll.filter(f => THIRD_PARTY_NOISE_RE.test(String(f.url || ''))); - // net::ERR_ABORTED = 客户端主动取消。结合两类证据判定是否需要留痕: + // net::ERR_ABORTED = 客户端主动取消。结合三类证据判定是否留痕: // ① 同 URL 后来成功(requestfinished 关联)→ 重调度,完全无害,不留痕 // ② prefetch 资源被打断 → 本身无用户影响,不留痕 - // ③ 其余 abort → INFO 留痕(2026-09-05 实测 /methodology 三图 abort 后 complete=true、URL 全 200) + // ③ 同源 script 被取消且该路由无未捕获异常 → 运行时自行取代的请求(App Router 静态 chunk + // 预取不带 Sec-Purpose 头,无法按头识别;后果导向:必需 chunk 被取消必然伴随 + // hydration 失败 → pageErrors 非空,届时仍会以 P1 记录)。闭环成立,不留痕。 + // ④ 其余 abort → INFO 留痕(2026-09-05 实测 /methodology 三图 abort 后 complete=true、URL 全 200) const finishedSet = new Set([...(d.finishedUrls || []), ...(m.finishedUrls || [])]); + const noPageErr = perr.length === 0; const abortedAndGone = f => !THIRD_PARTY_NOISE_RE.test(String(f.url || '')) && f.failure === 'net::ERR_ABORTED' && - (f.resourceType === 'prefetch' || finishedSet.has(f.url)); + (f.resourceType === 'prefetch' || finishedSet.has(f.url) || + (f.resourceType === 'script' && noPageErr)); const rfailAbort = rfailAll.filter(f => !THIRD_PARTY_NOISE_RE.test(String(f.url || '')) && f.failure === 'net::ERR_ABORTED' && !abortedAndGone(f));