阅读视图

发现新文章,点击刷新页面。

CSR秒开有可能么?(附AI驱动学习实践推理过程)

前言

本文我们聊web性能优化,但不聊平时比较常规、基础的点,例如:应用瘦身、拆包、请求加载优化、图片优化、缓存等等。

文章开始前,想问大家一个问题:我们知道SSR配合缓存可以实现H5的秒开?

秒开的原理

为什么SSR可以做到秒开?HTML的渲染会经历哪些过程呢?其实很简单。

  • html文件的响应时间;
  • html文档的DOM+CSS解析时间;

当这两个耗时结束,页面也就出来了。这里用汽车之家的官网来举例:

image.png

image.png

汽车之家的官网打开会发现就是秒开的效果,仔细观察,在上面提到的两次关键耗时中,都处理的很好。

  • html文件的响应时间,走了缓存,没有耗时;
  • html文档直接返回了首页的DOM,直接进行了文档解析;

所以快。

那为啥都说CSR比SSR要慢?不适合做首屏要求很高的toC应用?回到刚才说的话题,CSR返回的html通常是空标签。而最终的DOM需要等待首屏的JS资源请求响应再到解析完成才会渲染,这也是最核心、根本的原因。

CSR应用能秒开么?

那CSR能秒开么?既然它前置依赖这么多资源的加载,在首屏html回来以后就无法让页面元素上屏。

有一种思路是在页面每次渲染以后,将页面的DOM存入storage中,在下一次访问html的时候,优先将storage中的DOM渲染到页面中,等待真实DOM(JS资源加载完毕)后隐藏快照,如此循环。

这也是业界比较主流的 快照方案 对于纯前端的性能指标会有极大的提升。

快照方案

最早了解到这个方案的灵感来源于 淘宝闪购,闪购刚上线的时候能很明显感觉到这是个H5,因为每次进端切到闪购Tab时能明显察觉到白屏刚结束,有的时候甚至切到Tab的时候还在白屏。

但过了两个月,发现首屏的体验一下子上去了,基本就是秒开的效果,因此去看了下闪购的页面加载,看看有没有什么有意思的点。

image.png

抓包看了一下,果然发现了一些看起来类似黑科技的代码,直接把这段JS扔给了AI帮忙解析一下,解析结果也和我所想的比较类似:

这是AI给出的回复:

image.png

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节点。

image.png

同时体感上确实感觉是快了很多,然后把代码发到测试环境,跑个performance看了下效果:

image.png

HTML在180ms左右响应,页面在250ms左右上屏,再把html的缓存处理一下,其实CSR的秒开就实现了。

惊喜么?

面临的问题

理想是美好的,初步方案实现以后,发现了有以下的问题需要解决:

  1. 首屏CSS样式需前置注入,否则快照渲染 -> 首屏CSS解析之间会有无样式窗口期;
  2. 版本号问题,发布后会出现老快照过渡到新页面的问题,需处理版本问题,最简单的方式就是在storage存一份与发布版本关联的versionId,如果刷新页面走到快照阶段的时候版本号一致才渲染快照;

以下两个问题后续我都通过AI快速地解决了。大家也可以思考一下应该如何去处理。

结尾

本文重点侧重于介绍快照方案对于CSR的体验增强,也侧重地体现了笔者在研究 -> 落地阶段,AI的分析、设计、实现表现非常好,在整个过程其实就只用了一晚,对于平时的拓展、研究、实践,AI在现阶段一定是好搭子。

希望可以提供一些工作中的灵感。

❌