/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */
#content_views .toc,
/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */
#content_views.markdown_views > p:empty:has(+ .toc),
#content_views.markdown_views > .toc + p:empty,
/* 富文本旧版目录标记 */
#content_views.htmledit_views #main-toc,
#content_views.htmledit_views #hr-toc,
#content_views.htmledit_views p[id*=\”-toc\”] {
display: none !important;
}
/* 目录去掉后,紧跟的首个标题不再多出一块上边距 */
#content_views.markdown_views > .toc + h1,
#content_views.markdown_views > .toc + h2,
#content_views.markdown_views > .toc + h3,
#content_views.markdown_views > .toc + h4,
#content_views.markdown_views > .toc + p:empty + h1,
#content_views.markdown_views > .toc + p:empty + h2,
#content_views.markdown_views > .toc + p:empty + h3,
#content_views.markdown_views > .toc + p:empty + h4 {
margin-top: 0 !important;
}

在大型前端团队备战双 11 大促的过程中,架构师最头疼的挑战往往不是“如何做单点性能优化”,而是如何守住已经优化好的性能基线(Performance Baseline Guard)。
很多时候,大促主会场的性能崩塌并不是因为某个人写了一个惊天大 Bug,而是“温水煮青蛙”式的渐进式性能衰退(Performance Regression):
- 同学 A 在主包里随手引入了一个未做按需加载的完整 lodash,JS 体积增加了 75KB;
- 同学 B 给导航栏加了一个没有写死宽高属性的动效徽章,导致首屏渲染时产生剧烈的累积布局偏移(CLS 从 0.02 恶化到 0.18);
- 同学 C 在图片下方加了一个没有设置异步渲染的骨架屏,把最大内容绘制时间(LCP)硬生生推迟了 400ms。
每个 PR 单独看似乎“无伤大雅”,但几十个 PR 累加起来,在临近双 11 封网的前三天,大家突然发现原本 95 分的高性能主会场,跑分已经惨烈跌落至 65 分。此时再去从几百个 Commit 里抽丝剥茧排查,团队不仅身心俱疲,更极易引发线上事故。
为了把所有的性能衰退阻断在合入主干之前,我们在持续集成(CI)流程中落地了基于 Lighthouse CI 的自动化刚性基线门禁。任何导致性能指标恶化超过 5% 的 PR,都将被 CI 机制冷酷一票否决。
为什么本地跑 Lighthouse 不能作为卡点依据?
很多团队在本地开发时也会按 F12 打开 Chrome DevTools 跑 Lighthouse,但这绝对不能当作 CI 阻断标准:
Lighthouse CI 架构设计:刚性预算与自动化 Diff
我们基于官方 @lhci/cli 搭建了专用的容器化性能检测卡点,其核心工作流如下:
[开发者发起 GitHub PR]
│
▼
[GitHub Actions CI 启动无头容器]
│
├── 1. 自动执行生产打包 (pnpm run build)
├── 2. 启动轻量静态预览服务 (Static Web Server)
├── 3. 运行 Lighthouse CI:针对目标会场连续跑 3 轮完整测试
├── 4. 自动拉取 main 分支的最新基准数据,计算指标 Diff
│
▼
[性能预算断言判定 (Assertions)]
– LCP 阈值 <= 2000ms (误差超出 5% ➔ 立即 Fail)
– CLS 阈值 <= 0.05
– JS Bundle 主包体积 <= 350KB
│
┌────┴───────────────────────────┐
▼ (通过) ▼ (超标阻断)
[CI 绿灯,允许 Merge] [CI 红灯退出,PR 评论区输出详细性能退化报告]
生产级实战:配置 lighthouserc.js 刚性预算
在代码仓库根目录创建 lighthouserc.js,定义极其严苛的大促性能基线断言:
// lighthouserc.js
module.exports = {
ci: {
collect: {
// 针对每个页面连续测试 3 轮,取指标中位数,彻底抹平环境偶发抖动
numberOfRuns: 3,
// 模拟标准的移动端 4G 慢网与 4x CPU 降速设备 (中端大促真实机型)
settings: {
preset: 'desktop',
throttlingMethod: 'simulate',
throttling: {
rttMs: 150,
throughputKbps: 1638.4,
cpuSlowdownMultiplier: 4,
},
},
// 启动本地构建产物的临时预览服务器
startServerCommand: 'pnpm run preview –port 4173',
startServerReadyPattern: 'Local:',
url: [
'http://localhost:4173/promo/d11-main',
'http://localhost:4173/promo/seckill-center',
],
},
assert: {
// 刚性断言规则配置:error 级别直接导致 CI 挂起
assertions: {
// 核心 Web Vitals 基准保护
'categories:performance': ['error', { minScore: 0.90 }], // 性能分必须 >= 90
'first-contentful-paint': ['error', { maxNumericValue: 1200 }], // FCP <= 1.2s
'largest-contentful-paint': ['error', { maxNumericValue: 2000 }], // LCP <= 2.0s
'cumulative-layout-shift': ['error', { maxNumericValue: 0.05 }], // CLS <= 0.05
'total-blocking-time': ['error', { maxNumericValue: 150 }], // TBT <= 150ms
// 静态资源与体积红线
'resource-summary:script:size': ['error', { maxNumericValue: 400 * 1024 }], // 主 JS 严格限制 400KB
'resource-summary:image:size': ['error', { maxNumericValue: 600 * 1024 }], // 首屏图片严格限制 600KB
'dom-size': ['error', { maxNumericValue: 1200 }], // 限制总 DOM 节点数不超过 1200
},
},
upload: {
// 上传至团队自建的 LHCI 报告服务器,用于历史趋势追踪与分支 Diff
target: 'temporary-public-storage',
},
},
};
生产级 GitHub Actions 工作流集成
在流水线 .github/workflows/perf-baseline-gate.yml 中集成 LHCI 检查:
name: Performance Baseline Gate (LHCI)
on:
pull_request:
branches: [main]
jobs:
lighthouse-ci:
runs-on: ubuntu-latest
steps:
– name: Checkout Code
uses: actions/checkout@v4
– name: Setup Node.js & pnpm
uses: pnpm/action-setup@v3
with:
version: 9.12.0
run_install: false
– name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'pnpm'
– name: Install & Build
run: |
pnpm install –frozen-lockfile
pnpm run build
– name: Run Lighthouse CI
run: |
pnpm dlx @lhci/cli@0.14.x autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
如果某个 PR 引入了超大依赖导致 JS 体积超标,或者引入了未优化的图片导致 LCP 变慢,CI 会直接中断,并在 PR 页面清晰展示:
❌ Lighthouse CI Assertions Failed:
– largest-contentful-paint: Expected <= 2000ms, but got 2410ms (+410ms regression)
– resource-summary:script:size: Expected <= 409600 bytes, but got 482100 bytes (+72KB)
Please fix the regressions before merging.
落地成效与战果统计
我们在大促核心营销仓库中正式推行该门禁机制的一个月内,取得了极具说服力的防御成效:
| 大促主会场平均 Lighthouse 得分 | 72 分 (经常发生莫名下滑) | 94 分 (稳如磐石) | 提升 22 分 |
| 单月拦截隐蔽性能退化 PR 次数 | 0 次 (全靠上线后出问题找人) | 26 次在合入前被就地拦截 | 彻底消灭性能隐患 |
| 大促主包体积膨胀率 | 过去每年递增 35% | 0% (被 400KB 军规锁死) | 包体积实现零增长 |
| 首屏 LCP 生产达标率 (<=2.0s) | 62.4% | 98.8% | 几乎全量达标 |
| 上线前夕“紧急性能优化”加班时长 | 人均 40+ 小时 | 0 小时 (平时早已全部把死) | 极大释放研发精力 |
架构师手记
很多管理者总以为做性能保障靠的是“在封网前搞一次性能优化突击”。
作为一个经历过多届大促的工程手艺人,我想严肃地说:突击出来的性能是极其脆弱的,流水线守出来的性能才是真正的护城河。
用机器代替人工,把性能指标变成不可讨价还价的 CI 阻断规则。谁破坏了性能基线,谁就必须在 PR 阶段重构达标,让性能成为团队每个人的日常习惯,这才是企业级工程化演进的核心价值所在。
