摘要:本文详细记录了 CMC Link 供应商门户(Supplier Portal)的性能优化实战过程。我们跳出了常规的“压缩图片、减少请求”的浅层优化,深入浏览器渲染原理,通过关键渲染路径(Critical Rendering Path)优化、现代 CSS 引擎升级、以及极致的构建配置,实现首屏加载速度(FCP/LCP)的显著提升。
一、 破局:为何你的页面加载不够快?
在讨论具体代码之前,我们需要理解浏览器是如何渲染页面的。当用户访问 index.html 时,浏览器需要经历以下步骤:
- 解析 HTML:构建 DOM 树。
- 遇到 CSS/JS:暂停解析(阻塞),发起网络请求。
- 解析 CSS:构建 CSSOM 树。
- 合成 Render Tree:DOM + CSSOM。
- 布局(Layout)与绘制(Paint)。
痛点在于:现代 SPA(单页应用)通常依赖大量的 JavaScript 和 CSS 才能完成首屏渲染。如果 CSS 文件过大、或者 JS 阻塞了主线程,用户就会看到长时间的白屏。
我们的优化目标很明确:缩短关键请求链(Critical Request Chains),让浏览器尽早把核心内容画出来。
二、 第一刀:缩短关键请求链
这是本次优化的核心战场。我们通过减少首屏依赖的资源数量和大小,让页面“瞬间”可用。
1. 内联关键 CSS (Critical CSS Inlining)
原理:将首屏必须的布局样式(如容器、骨架屏背景)直接写在 HTML 的 <head> 中。这样浏览器在解析 HTML 时就能直接构建部分 CSSOM,无需等待外部 .css 文件下载。
实战代码 (index.html):
<head>
<!-- 优化前:完全依赖外部 CSS,下载前页面空白 -->
<!-- <link href="src/assets/styles/tailwind.css" /> -->
<!-- 优化后:内联关键样式,确保布局瞬间就位 -->
<style>
/* 首屏必需的 CSS(如布局、字体、核心组件样式) */
.container { width: 100%; max-width: 1200px; margin: 0 auto; }
#app { width: 100%; height: 100%; }
/* 避免 FOUC (无样式内容闪烁) */
[v-cloak] { display: none; }
</style>
<!-- 非关键 CSS 依然通过链接加载,但此时页面已有基本骨架 -->
<link href="src/assets/styles/tailwind.css" />
</head>
2. 资源合并策略
背景:项目中引入了 Iconfont。原本的做法是在 CSS 中通过 @import 或在 HTML 中通过 <link> 引入 iconfont.css。这会导致浏览器多发一个 HTTP 请求。
优化方案:利用 SCSS 的编译机制,将小体积的 CSS 直接打入主样式包。
代码变更 (src/assets/styles/index.scss):
// ❌ 优化前:独立的 HTTP 请求
// @import url('@/assets/iconfont/iconfont.css');
// ✅ 优化后:SCSS 编译时合并内容,减少请求链长度
@use '@/assets/iconfont/iconfont.css';
3. 非关键脚本的延迟加载
背景:PWA 的 Service Worker 注册脚本 (registerSW.js) 虽然重要,但绝不应该阻塞首屏渲染。
优化方案:配置 VitePWA 插件,使用 script-defer 策略。
代码变更 (build/plugins.ts):
VitePWA({
registerType: "autoUpdate",
// ✅ 关键改动:延迟加载 Service Worker 注册脚本
// 浏览器会在解析完 HTML 后再执行此脚本,不阻塞主线程
injectRegister: "script-defer",
// ...
})
三、 第二刀:极致的资源压缩
1. Gzip + Brotli 双重压缩
我们在构建阶段引入了 vite-plugin-compression,同时生成两种格式的压缩文件:
- .gz (Gzip): 兼容性极好,压缩率高。
- .br (Brotli): Google 推出的算法,比 Gzip 压缩率高 20%~30%,现代浏览器均支持。
Vite 配置 (build/plugins.ts):
// Gzip
viteCompression({
algorithm: "gzip",
threshold: 10240, // >10KB 才压缩,避免小文件越压越大
}),
// Brotli
viteCompression({
algorithm: "brotliCompress",
threshold: 10240,
}),
2. CSS 引擎升级:Tailwind CSS v4
我们采用了最新的 Tailwind CSS v4。相比 v3,v4 是一个巨大的飞跃:
- 零运行时:构建产物更小。
- 按需生成:不再需要 PurgeCSS 扫描文件,它根据你的类名实时生成 CSS,绝无冗余。
- LightningCSS:底层使用 Rust 编写的 LightningCSS 进行压缩,速度极快且体积更小。
配置变更 (vite.config.ts):
export default defineConfig({
build: {
// 使用 lightningcss 替代 esbuild 进行 CSS 压缩
// 效果:体积更小,语法降级处理更智能
cssMinify: 'lightningcss',
}
})
四、 第三刀:网络层的魔法
1. Preconnect (预连接)
如果你的应用需要访问第三方 API 或 CDN,建立连接(DNS 解析 + TCP 握手 + TLS 协商)是耗时的。
优化方案:告诉浏览器“我稍后要从这个域名加载数据,请提前握手”。
实战代码 (index.html):
<head>
<!-- 性能优化:提前建立到 API 服务器的连接 -->
<!-- 节省了后续 fetch 请求时的 100ms+ 握手时间 -->
<link rel="preconnect" href="https://spt-devsinolines.cn:25080">
</head>
2. HTTP/2 多路复用
虽然这属于服务器配置(Nginx),但前端需要配合。我们将大量小资源(如 Iconfont、拆分后的 Vendor Chunks)合并或拆分的策略,正是为了利用 HTTP/2 的**多路复用(Multiplexing)**特性——在同一个 TCP 连接上并发传输多个文件,不再受 HTTP/1.1 的“队头阻塞”限制。
五、 总结与成效
通过以上一套组合拳,我们实现了:
- FCP (First Contentful Paint) 降低:内联 CSS 让页面骨架瞬间展现。
- LCP (Largest Contentful Paint) 优化:资源压缩和预连接加速了核心内容加载。
- TBT (Total Blocking Time) 减少:Defer 脚本和代码拆分释放了主线程。
性能优化不是一蹴而就的,而是一个持续的工程。 本次实践建立了一套基于 Vite 的现代化性能基准,为后续的业务迭代提供了坚实的底座。