CSR秒开有可能么?(附AI驱动学习实践推理过程)
前言
本文我们聊web性能优化,但不聊平时比较常规、基础的点,例如:应用瘦身、拆包、请求加载优化、图片优化、缓存等等。
文章开始前,想问大家一个问题:我们知道SSR配合缓存可以实现H5的秒开?
秒开的原理
为什么SSR可以做到秒开?HTML的渲染会经历哪些过程呢?其实很简单。
- html文件的响应时间;
- html文档的DOM+CSS解析时间;
当这两个耗时结束,页面也就出来了。这里用汽车之家的官网来举例:
汽车之家的官网打开会发现就是秒开的效果,仔细观察,在上面提到的两次关键耗时中,都处理的很好。
- html文件的响应时间,走了缓存,没有耗时;
- html文档直接返回了首页的DOM,直接进行了文档解析;
所以快。
那为啥都说CSR比SSR要慢?不适合做首屏要求很高的toC应用?回到刚才说的话题,CSR返回的html通常是空标签。而最终的DOM需要等待首屏的JS资源请求响应再到解析完成才会渲染,这也是最核心、根本的原因。
CSR应用能秒开么?
那CSR能秒开么?既然它前置依赖这么多资源的加载,在首屏html回来以后就无法让页面元素上屏。
有一种思路是在页面每次渲染以后,将页面的DOM存入storage中,在下一次访问html的时候,优先将storage中的DOM渲染到页面中,等待真实DOM(JS资源加载完毕)后隐藏快照,如此循环。
这也是业界比较主流的 快照方案 对于纯前端的性能指标会有极大的提升。
快照方案
最早了解到这个方案的灵感来源于 淘宝闪购,闪购刚上线的时候能很明显感觉到这是个H5,因为每次进端切到闪购Tab时能明显察觉到白屏刚结束,有的时候甚至切到Tab的时候还在白屏。
但过了两个月,发现首屏的体验一下子上去了,基本就是秒开的效果,因此去看了下闪购的页面加载,看看有没有什么有意思的点。
抓包看了一下,果然发现了一些看起来类似黑科技的代码,直接把这段JS扔给了AI帮忙解析一下,解析结果也和我所想的比较类似:
这是AI给出的回复:
AI不仅回答了我的猜想,也把整个技术方案梳理清楚了,那下一步我想亲自实践一下,我设计了提示词:
我希望自己的前端应用也可以实现这样的方案,让应用的整体渲染速度大幅度提升,提供代码。
AI返回了我这些代码:
(function () {
const PLACEHOLDER_ID = 'fcc-snapshot-placeholder';
const STORAGE_KEY = 'fcc-snapshot-key';
const VERSION_KEY = 'fcc-snapshot-version-key';
const ROOT_DOM_ID = 'demo';
function getPageVersion() {
return document.documentElement.getAttribute('version') || null;
}
function getStoredVersion() {
return localStorage.getItem(VERSION_KEY) || null;
}
function hideSnapshot() {
const snapshotPlaceholder = document.getElementById(PLACEHOLDER_ID);
const rootElement = document.getElementById(ROOT_DOM_ID);
if (snapshotPlaceholder) {
console.log(snapshotPlaceholder.innerHTML);
snapshotPlaceholder.style.display = 'none';
window.__SNAPSHOT_IS_HIDE__ = true;
console.log('快照删除,真实 DOM 已加载 ✅');
}
if (rootElement && rootElement.innerHTML) {
localStorage.setItem(STORAGE_KEY, rootElement.innerHTML);
const version = getPageVersion();
if (version) {
localStorage.setItem(VERSION_KEY, version);
console.log(`保存页面版本号到 localStorage: ${version}`);
}
}
}
function paintSnapshot() {
const snapshotCache = localStorage.getItem(STORAGE_KEY);
const storedVersion = getStoredVersion();
const currentVersion = getPageVersion();
const newPlaceholder = document.createElement('div');
newPlaceholder.id = PLACEHOLDER_ID;
newPlaceholder.style.cssText = 'position: absolute; width: 100%; height: 100%; top: 0; left: 0; z-index: 9999;';
newPlaceholder.innerHTML = snapshotCache;
document.body.appendChild(newPlaceholder);
}
function listenForRender() {
window.addEventListener('appRendered', () => {
console.log('组件渲染完成');
hideSnapshot();
});
}
window.__SNAPSHOT_IS_HIDE__ = false;
paintSnapshot(); // 尝试加载快照
listenForRender(); // 监听渲染完成
})();
我将这段代码在构建阶段插入到CSR渲染根节点之前,刷新了两次页面,发现在storage中已经存入了首屏的DOM节点。
同时体感上确实感觉是快了很多,然后把代码发到测试环境,跑个performance看了下效果:
HTML在180ms左右响应,页面在250ms左右上屏,再把html的缓存处理一下,其实CSR的秒开就实现了。
惊喜么?
面临的问题
理想是美好的,初步方案实现以后,发现了有以下的问题需要解决:
- 首屏CSS样式需前置注入,否则快照渲染 -> 首屏CSS解析之间会有无样式窗口期;
- 版本号问题,发布后会出现老快照过渡到新页面的问题,需处理版本问题,最简单的方式就是在storage存一份与发布版本关联的versionId,如果刷新页面走到快照阶段的时候版本号一致才渲染快照;
以下两个问题后续我都通过AI快速地解决了。大家也可以思考一下应该如何去处理。
结尾
本文重点侧重于介绍快照方案对于CSR的体验增强,也侧重地体现了笔者在研究 -> 落地阶段,AI的分析、设计、实现表现非常好,在整个过程其实就只用了一晚,对于平时的拓展、研究、实践,AI在现阶段一定是好搭子。
希望可以提供一些工作中的灵感。