V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kaiduo
V2EX  ›  Node.js

服务端流式渲染 iOS 中踩坑记

  •  1
     
  •   kaiduo · 2022-06-26 23:57:58 +08:00 · 3287 次点击
    这是一个创建于 876 天前的主题,其中的信息可能已经有所发展或是发生改变。

    原文链接(查看演示视频更友好): https://github.com/xiaoxiaojx/blog/issues/37

    近期 IOS 客户端反映 WebView 中打开 h5 页面存在明显的白屏时间, 于是打算把后端接口延时高(> 150ms )的 h5 项目由现在的 SSR 改成 html 请求达到 Node 时率先返回构建时生成的骨架屏 html 主体, 然后再异步请求后端接口数据, 获取到接口数据后再追加到 html 响应流中。这样 Node 能够 1ms 内响应实际内容让用户先看到页面框架, 通过内网并发聚合的接口数据也能让客户端直接复用这部分数据更快展示出最终屏。

    按理来说 h5 不再受限于后端接口的响应时长, 能够第一时间渲染出骨架屏页面, 但是体验后白屏时间好像没怎么缩短? 最后反复删减代码测试发现了一个残酷的现实 👇

    IOS WKWebView 不支持流式渲染(分块渲染), 安卓 WebView 与 PC Chrome 是支持的。

    即表示 IOS 中会等待 html 请求彻底结束后才开始渲染, 如下是安卓与 IOS 中的效果演示视频,希望其他同学不要再踩坑 🤯

    https://user-images.githubusercontent.com/23253540/174447134-25daa11b-0be8-4330-85b7-e464c14f6047.mp4

    https://user-images.githubusercontent.com/23253540/174447157-8ccc2be4-52fe-4d67-a11d-d4701677aa5d.mp4


    2022-06-20 更新,经过大佬提醒,IOS 中如果返回的 data 是普通文本文字,或返回的数据中包含普通文本文字,那只需要达到非空 200 字节即可以触发渲染,详细见 iOS 之深入解析 WKWebView 加载的生命周期与代理方法

    https://user-images.githubusercontent.com/23253540/174550696-cb3b54df-6db1-4aff-8adb-b60258461b20.mp4

    所以 IOS chrome 与 safari 也是支持流式渲染(分块渲染),App 中没有效果是有效内容没有达到 200 字节 (innerText)

    h5 页面首屏文字等内容达到 200+ 字节还是较少的,设置为 display: none 来凑数的 div 不会被计数进去,相关代码实现见

    // https://github.com/WebKit/webkit/blob/main/Source/WebCore/page/FrameView.h#L975
    
    static const unsigned visualCharacterThreshold = 200;
    
    // https://github.com/WebKit/WebKit/blob/ed7fed17c5ac886890859f1fc8682dba06424616/Source/WebCore/page/FrameView.cpp#L4685
    
    void FrameView::checkAndDispatchDidReachVisuallyNonEmptyState()
    {
    // ...
    // The first few hundred characters rarely contain the interesting content of the page.
            if (m_visuallyNonEmptyCharacterCount > visualCharacterThreshold)
                return true;
    }
    
    void FrameView::incrementVisuallyNonEmptyCharacterCount(const String& inlineText)
    {
        if (m_visuallyNonEmptyCharacterCount > visualCharacterThreshold && m_hasReachedSignificantRenderedTextThreshold)
            return;
    
        auto nonWhitespaceLength = [](auto& inlineText) {
            auto length = inlineText.length();
            for (unsigned i = 0; i < inlineText.length(); ++i) {
                if (isNotHTMLSpace(inlineText[i]))
                    continue;
                --length;
            }
            return length;
        };
        m_visuallyNonEmptyCharacterCount += nonWhitespaceLength(inlineText);
        ++m_textRendererCountForVisuallyNonEmptyCharacters;
    }
    

    2022-06-26 更新,最后给 body 标签插入了一个塞了 200 个空格字符的 div 来强制 WKWebView 进行刷新缓存实时渲染,经过一周多的测试,白屏时间明显减少甚至不见!

    const IOS_200 = `<div style="height:0;width:0;">\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b\u200b</div>`
    

    把服务端渲染 SSR 改为现在的预渲染+接口聚合还有什么其他优势吗?

    新的预渲染+接口聚合架构在公司 CDN 基建支持的情况下,不就是天然的 ⚡️ ESR (边缘流式渲染方案) 么 ~

    3 条回复    2022-06-27 23:50:38 +08:00
    mayliya
        1
    mayliya  
       2022-06-27 11:24:17 +08:00
    强👍
    DingJZ
        2
    DingJZ  
       2022-06-27 18:45:19 +08:00
    牛逼,mark 一下,万一用得上
    kaiduo
        3
    kaiduo  
    OP
       2022-06-27 23:50:38 +08:00
    @DingJZ iOS 哪天明显比安卓慢,就能用上了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1361 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 17:41 · PVG 01:41 · LAX 09:41 · JFK 12:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.