From 0b6524f53c0da0323afdd5e1ff158424f3a98130 Mon Sep 17 00:00:00 2001 From: wait4xx Date: Sun, 25 Oct 2026 21:37:38 +0800 Subject: [PATCH] =?UTF-8?q?perf(home):=20=E9=A6=96=E5=B1=8F=20eager+?= =?UTF-8?q?=E5=85=B3=E9=94=AE=20CSS=20=E5=86=85=E8=81=94=EF=BC=8CLighthous?= =?UTF-8?q?e=20=E4=BC=AA=E5=BD=B1=E6=A0=B9=E5=9B=A0=E4=BF=AE=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 车道 D 三件收编(op-gitea):①index.astro 首两图 eager+首图 fetchpriority=high(Image 默认 lazy 曾压 LCP 至 6.4s,好模式现 1.3s)②inline-home-css.mjs:BaseLayout 11KB 内联首页 head(省 发现+下载跳)③build.sh 守卫挂钩 - 诊断结论入档:home 中位漂移根因=测试床伪影(python server 不压 缩,裸传 67KB vs 生产 gzip 9.2KB;lantern 对未压缩 HTML 流式归因 双峰 99↔165ms 翻转出两个世界)——gzip 等价口径中位 100 (FCP 0.9-1.1s/LCP 1.3-1.4s/TBT 0/CLS 0) - 测试床转正:gz-server.py 泛化入仓(ThreadingHTTPServer+gzip [端口 目录] 参数化)——兼治 SW 预缓存风暴竞速(单线程 server 曾把 E2E /gallery/ networkidle 饿死,实锤于收编验收) - E2E serviceWorkers:'block':SW 有专属测试,站点 E2E 求确定性 - 三连否定留档:SW✗/css链✗/子集内联 v3✗(6×63 反例钉死,已 回滚)——残尾 20% 离群属 lantern trace 抖动,采中位+多轮 车道:op-gitea(过程三连否定+测试床移交);收编验收:内联 10KB 实出+首图 eager 实证+E2E 31/31(SW 屏蔽后确定性复现) 验证: gz 测试床 18743 gzip 10KB(生产等价口径);node tests/e2e.mjs 31/31 PASS;构建+守卫链复跑 Spec: none (性能三件+测试床转正,车道级交付) Co-authored-by: GLM-5.3 --- infra/deploy/build.sh | 2 ++ scripts/build-perf/gz-server.py | 29 ++++++++++++++++++++++++++ scripts/build-perf/inline-home-css.mjs | 26 +++++++++++++++++++++++ src/pages/index.astro | 5 ++++- tests/e2e.mjs | 2 +- 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 scripts/build-perf/gz-server.py create mode 100644 scripts/build-perf/inline-home-css.mjs diff --git a/infra/deploy/build.sh b/infra/deploy/build.sh index d24a640..ec364f8 100755 --- a/infra/deploy/build.sh +++ b/infra/deploy/build.sh @@ -27,6 +27,8 @@ pnpm astro build # 按页字体 CSS 子集化(车道 A 任务 1):裁 @font-face 声明数,构建仓与本地同链; # 存在性守卫:新文件未推送到 main 前构建仓无此脚本,绝不让发布链因车道在途文件断掉 [ -f scripts/build-perf/subset-font-css.mjs ] && node scripts/build-perf/subset-font-css.mjs dist || echo "[build] subset 脚本未就绪,跳过" +# 首页阻塞 CSS 内联(车道 D):守卫先行同上——脚本未到 main 前绝不炸链 +[ -f scripts/build-perf/inline-home-css.mjs ] && node scripts/build-perf/inline-home-css.mjs || echo "[build] inline-home 脚本未就绪,跳过" echo "[build] rsync -> $STAGING" mkdir -p "$STAGING" diff --git a/scripts/build-perf/gz-server.py b/scripts/build-perf/gz-server.py new file mode 100644 index 0000000..9b2f9cc --- /dev/null +++ b/scripts/build-perf/gz-server.py @@ -0,0 +1,29 @@ +import gzip, os, functools +from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler + +class GZ(SimpleHTTPRequestHandler): + def do_GET(self): + path = self.translate_path(self.path) + if os.path.isdir(path): + path = os.path.join(path, 'index.html') + compressible = os.path.isfile(path) and not path.endswith(('.woff2', '.png', '.jpg', '.webp', '.ico', '.svg')) + if compressible: + with open(path, 'rb') as f: body = f.read() + gz = gzip.compress(body, 6) + self.send_response(200) + self.send_header('Content-Type', self.guess_type(path)) + self.send_header('Content-Encoding', 'gzip') + self.send_header('Content-Length', str(len(gz))) + self.end_headers() + self.wfile.write(gz) + else: + super().do_GET() + +# 用法:gz-server.py [端口=18743] [目录=www-staging] +# 团队标准测试床(车道 D 移交):ThreadingHTTPServer+gzip=生产 nginx/ESA 等价口径; +# 单线程 SimpleHTTPServer 双宗罪:不压缩致 LH 双峰伪影 + 与 SW 预缓存风暴竞速 +import sys +port = int(sys.argv[1]) if len(sys.argv) > 1 else 18743 +root = sys.argv[2] if len(sys.argv) > 2 else os.path.expanduser('~/docker_apps/blog/www-staging') +print(f'gz server on {port} -> {root}') +ThreadingHTTPServer(('127.0.0.1', port), functools.partial(GZ, directory=root)).serve_forever() diff --git a/scripts/build-perf/inline-home-css.mjs b/scripts/build-perf/inline-home-css.mjs new file mode 100644 index 0000000..efb8b51 --- /dev/null +++ b/scripts/build-perf/inline-home-css.mjs @@ -0,0 +1,26 @@ +#!/usr/bin/env node +/** + * 首页 render-blocking CSS 内联(车道 D):把 dist/index.html 唯一的阻塞样式表 + * (BaseLayout 共享块,~11KB)内联进 。 + * 慢模式双峰根因不在 css(实测:字体子集也内联后 6.0s 模式反成主流), + * 本脚本只保留 BaseLayout 内联这一确定性收益(省一次发现+下载跳)。 + */ +import { readFileSync, writeFileSync } from 'node:fs'; + +const htmlPath = 'dist/index.html'; +let html; +try { + html = readFileSync(htmlPath, 'utf8'); +} catch { + console.error('[inline-home-css] dist/index.html 不存在,跳过'); + process.exit(0); +} + +const m = html.replace(/