用 AI 优化关键渲染路径自动识别与消除阻塞资源一、Lighthouse 报告里的Eliminate render-blocking resources和你的无力感Lighthouse 报告说消除阻塞渲染的资源可节省 2.3 秒。点开详情——3 个 CSS 文件Google Fonts 1 个、Ant Design CSS 1 个、自定义样式 1 个和 2 个 JS 文件在阻塞 FCP。理想方案是内联首屏关键 CSS、异步加载非关键 CSS、对 JS 添加async/defer。但首屏关键 CSS包含哪几条规则Ant Design 的全局 CSS 中有 87% 的规则在首屏根本不使用。谁来判断该内联哪些、该延迟哪些这就是 Critical CSS 提取的问题。手动提取首屏需要的 CSS 规则需要对每个页面做在 375×812 视口下哪些 CSS 规则被应用了的分析。AI 的自动化方案是通过 Puppeteer Coverage API 收集首屏实际使用的 CSS 规则然后自动分割为 Critical首屏内联和 Non-Critical异步加载。二、渲染阻塞的资源分类与处理策略flowchart TD A[HTML 解析] -- B{遇到 link relstylesheet} B -- C[暂停解析 → 下载 CSS → 构建 CSSOM → 恢复解析] A -- D{遇到 script src (无 async/defer)} D -- E[暂停解析 → 下载 JS → 执行 JS → 恢复解析] C -- F[内联 Critical CSS 消除此阻塞] E -- G[async/defer 消除此阻塞] subgraph 阻塞CSS处理 F1[Puppeteer 收集首屏 Coverage] F1 -- F2[提取被应用的 CSS 规则] F2 -- F3[内联到 head 的 style 中] F1 -- F4[未应用的规则 → 异步加载] end subgraph 阻塞JS处理 G1[识别首屏非必需的 JS] G1 -- G2[添加 async / defer 属性] G2 -- G3[确保无 DOM 依赖的脚本用 asyncbr/需要 DOM 的用 defer] end三、自动提取 Critical CSS 的实现/** * Critical CSS 自动提取器 * * 工作流 * 1. Puppeteer 打开目标页面 * 2. 使用 CSS Coverage API 收集所有样式表的使用情况 * 3. 筛选出首屏实际应用的 CSS 规则覆盖率 0% * 4. 输出为单独的内联样式字符串 * * 依赖puppeteer */ import puppeteer, { Browser, Page } from puppeteer; interface CriticalCSSResult { /** 内联到 head 的首屏关键 CSS */ critical: string; /** 未使用规则百分比 */ unusedPercent: string; /** 节省的阻塞 CSS 体积 */ savedBytes: number; } async function extractCriticalCSS( url: string, viewport: { width: number; height: number } { width: 375, height: 812 } ): PromiseCriticalCSSResult { const browser await puppeteer.launch(); const page await browser.newPage(); // 设置移动端视口匹配大多数用户的首屏尺寸 await page.setViewport(viewport); // 关键启用 CSS Coverage await Promise.all([ page.coverage.startCSSCoverage(), page.goto(url, { waitUntil: networkidle0 }), ]); // 获取页面中所有样式表的使用情况 // 返回数组[{ url, text, ranges }] // ranges: [{ start, end }] → 被实际使用的字符范围 const coverage await page.coverage.stopCSSCoverage(); const criticalParts: string[] []; let totalBytes 0; let usedBytes 0; for (const entry of coverage) { totalBytes entry.text.length; // 跳过 data: 和 inline 样式已经内联无需处理 if (entry.url.startsWith(data:)) continue; // ranges 为使用了哪些字符区间 // { start: 0, end: 100 } → text[0:100] 这段 CSS 被使用了 const usedRanges entry.ranges; for (const range of usedRanges) { const usedCode entry.text.slice(range.start, range.end); usedBytes usedCode.length; criticalParts.push(usedCode); } } const unusedPercent (((totalBytes - usedBytes) / totalBytes) * 100).toFixed(1); await browser.close(); return { // 拼接所有被使用的 CSS 代码段 // 生产环境需要 postcss 处理合并后的代码去重、压缩 critical: criticalParts.join(\n), unusedPercent: ${unusedPercent}%, savedBytes: totalBytes - usedBytes, }; } /** * 将 Critical CSS 嵌入 HTML * * 策略 * 1. 将 Critical CSS 内联到 head 的 style 标签中 * 2. 将原始外部 CSS 链接改为 preload异步加载不阻塞渲染 * 3. 在 onload 事件中切换为 stylesheet保证后续页面有完整样式 */ function embedCriticalCSS(html: string, criticalCSS: string): string { // 提取原始的外部 CSS 链接 const linkRegex /link\s[^]*rel[]stylesheet[][^]*/gi; const links html.match(linkRegex) || []; // 构建内联 Critical CSS 标签 const criticalStyleTag style>