簡介: Web 應用在實際體驗上和 Native 應用仍然存在很是明顯的差距,那麼如何低成本地把一個現有的網站改形成類 Native 的體驗呢?本文分享一種讓網站低成本漸進式實現 Native 化體驗的方式——同屏渲染。(文末推薦:2020 阿里雲線上峯會)前端
在有了 PWA(Progressive web apps) 以後,Web Application 也具有了添加到桌面和離線訪問等能力,可是實際體驗上卻老是和 Native 應用存在很是明顯的差距。git
咱們能夠看一下 Alibaba 的 M 站和 iOS 應用的錄屏(左邊爲 WEB,右邊爲 iOS APP):github
.gif")web
.gif")json
咱們能夠看到,對於 Web Applicaiton 來講,在頁面中來回跳轉時訪問的老是割裂的,從上一個頁面到下一個頁面須要等 loading ,返回時不少內存狀態又都不在了,致使沒法正肯定位回以前的列表位置(這一點其實和不一樣的瀏覽器以及列表自己的實現方式有關,也有一些方案能夠規避這個問題,在這裏只是其中一個 case)。跨域
這樣對於用戶的體驗傷害很是明顯,他能明確感受到本身在用的並不是一個 Application 而是一個 Website,並且在進行復雜的操做時整個鏈路也很是容易被中斷。瀏覽器
而其實這種體驗差別的根源,在於 B/S(Browser/Server)和 C/S(Client/Server)的差別。ServiceWorker 雖然提供了一些方案(例如 App Shell)讓咱們較低成本的加強原有的體驗,但仍然難以解決頁面之間的割裂問題,不少相同的代碼在不一樣頁面間重複執行,每一次訪問內存狀態就會丟失。安全
當咱們在說體驗的時候會顯得有點主觀,性能相比之下就容易衡量的多,而頁面割裂帶來的最爲直觀的體驗差距其實就來自於渲染性能的差別。閉包
在 Web 端一個典型的 CSR(Client Side Rendering)要通過的流程大體以下:app
這其中有不少不符合咱們預期的地方:
因此理想中的渲染流程應該是下圖這樣:
其實對於 Native 應用也是如此,用戶點擊時基本就會開始加載 API 而且執行下個頁面的邏輯。其實一個優化的比較好(作了 preload 等)的 SPA 也是相似的效果,咱們提早加載好下個頁面的 vendor ,點擊時直接只執行下個頁面的邏輯便可。
然而實際上對於一個較大的現存站點來講(例如 m.alibaba.com ),把整個網站做爲一個 SPA 來維護是不太現實的,一方面不能適應當前多人協做的現狀,另一方面穩定性上也不能接受修改一個頁面整個網站都要發佈的方案。
那麼,如何低成本的把一個現有的網站改形成類 Native 的體驗呢?
在有了上面的思考後,咱們就在想,有沒有一個方案在不作改造的前提下,在用戶點擊後,當即開始數據的並行加載,同時把下個頁面動態的加載進來,選擇性的保留上個頁面的一些內容(例如正在加載中的數據, jsonp , framework 層的對象等)而隔絕其餘部分的干擾。
因而針對咱們的場景產出了一個同屏渲染的方案:LightHub,所謂同屏渲染,即渲染過程當中頁面不須要被卸載,全部的渲染行爲都在一個上下文中發生。
.gif")
這裏咱們須要幾個東西:
沙箱
咱們須要一個低成本把頁面還原會初始狀態、而且容許保留部分對象的沙箱機制,並且最好這個機制是能夠直接低成本部署到現有頁面上的。其實這裏的訴求和微前端碰到的問題相似,咱們受 qiankun 的沙箱機制啓發,只須要在頁面的
中插入一小段內聯 JS 記錄:
在咱們須要時咱們只須要清空頁面的 DOM,還原變化的全局變量(這裏和 qiankun 同樣採用的淺拷貝),eventListener,定時器和 MutationObserver,就能把頁面還原到初始狀態。
同時,記錄的狀態也能封存到一個對象中,當用戶從下個頁面 back 到上個頁面時,咱們能夠直接把狀態還原到頁面上。
這裏就須要在清空頁面狀態時選擇性的保留一些須要保留的對象:例如公共的 Framework,JSONP 請求的標籤等。
這一點其實就沒有多複雜了,在頁面不須要被卸載和從新加載後,咱們能夠在用戶點擊後當即展現一個動畫。目前採用的只是一個簡單的從右側 slide-in 的動畫。
須要注意的是,因爲在繪製動畫的過程當中咱們每每正在執行下個頁面的邏輯,咱們須要注意使用 GPU 來繪製動畫,從而確保動畫不會被 JS 執行阻塞。這一點對於低端機尤其關鍵。
其實在有了上面的沙箱機制後,API 的並行加載就不是難事了,須要注意的是咱們須要保護 API 並行加載自己的過程當中產生的狀態(例如 setTimeout ),咱們須要實現一個 runInSharedContext 確保這其中的定時器不會在頁面切換時被卸載。
runInSharedContext(() => { // 這裏的 setTimeout 不能是被記錄 & 清除的 setTimeout setTimeout(() => window.sharedfetchDataPromise = fetch(res)); });
而在下個頁面消費的只須要 window.sharedfetchDataPromise || fetch(url) 就能直接複用並行加載的 API 請求。
在咱們的場景下爲了讓這個問題更加開發者無感,封裝了一個叫作 redfox 的工具庫,在同一個頁面環境執行屢次相同配置的請求會自動複用,不須要開發者手動判斷。
按照瀏覽器行爲渲染 HTML
這多是其中最複雜的部分了,在咱們抓到下個頁面的 HTML 後,不能只是簡單的 document.innerHTML = nextHTML ,這樣會致使和普通的瀏覽器行爲徹底不一致,樣式加載會致使閃屏,腳本的執行順序不符合預期等等。
因此咱們須要本身實現一個 renderHTML ,將抓到的 HTML 解析後模擬瀏覽器的行爲進行渲染。
這個部分的行爲比較複雜,須要在較多的場景進行測試,以及有相應的單元測試保障邏輯的正確性。
按瀏覽器行爲觸發事件
其實和上面渲染 HTML 類似,在渲染的過程當中須要按照瀏覽器的行爲觸發相應的事件。
例如上個頁面卸載時依次觸發 beforeunload => pagehide => unload ,在下個頁面加載時先把 readyState 重置,而後按照次序觸發 domInteractive defer 的執行和 DOMContentLoaded 。
一樣的,單元測試在這個環節是必須的。
Timeline 分析
從 Chrome 最後的 Timeline 看執行邏輯基本是符合咱們預期的,點擊後的瞬間 API 開始加載而且基本上就開始全力執行下個頁面的渲染邏輯。
Framework 層的代碼基本也不須要再重複執行。
內存壓力
對於這種不卸載頁面的方案來講最容易引發擔心的可能就是內存泄露問題,其實按照上面的沙箱機制,只要咱們確保 DOM、全局變量、定時器、時間監聽等可以被正確清除,與之相關的閉包等就不會賴在內存中不走。
從咱們本地屢次頻繁點擊切換頁面的反應看,內存隨着頁面的切換返回也會一次次回到初始狀態,從理論上不存在直接致使內存泄露的缺陷。
然而,因爲咱們容許在頁面間保留一部分的公共區域(上面稱之爲 Service Layer),另外沙箱自己是一個約定沙箱而非安全沙箱(例如往 Element.prototype.xxx 屬性寫東西就沒法被攔截),對於一些不規範的寫法仍然存在內存泄露的風險。
這一點可能須要和 Native 端相似的內存壓力監控等方式來長期觀察。
分階段打點
因爲整個 HTML 渲染過程都是咱們本身實現的,因此整個渲染的各個階段能夠本身打點記錄一些時間,下面就是一個例子:API 從 JS 請求到拿到耗時 124ms ,而實際上整個取數據(提早並行取的)花了 350ms 。每個 script 開始執行和執行耗時也能夠經過這種方式打上來。
這也能夠爲咱們的頁面優化提供一些指導,例如 JS 的執行時間是否是過晚,某段 JS 的執行時間是否是過長。
最終的對比效果以下,左爲同屏渲染,右爲正常跳轉,從線上的數據看性能提高大約從 2.8s => 1.8s 。
.gif")
除了異步渲染的頁面外,咱們針對一些原先是 SSR 的頁面也作了很是低成本的接入(不須要改造頁面,可是享受到的受益相對也更有限)。
.gif")
但僅僅是上面這種跳轉體驗和返回體驗的改善,就讓咱們的 Just For U 模塊的曝光屏數有穩定 3% 的增加。
總結一下:
上面的方案仍然存在一些侷限性,例如前面提到的須要開發者防範內存泄露的問題,同時由於 History API 的限制,頁面必須是同域的,不然跳轉的 URL 沒法知足預期。
關注 Chrome 動態的同窗也會了解到 Chrome 最近也退出了一個新的提案:Portal API,就是旨在解決咱們上面提到的 Web 體驗割裂的問題。
可以提供一個相似 iframe 的沙箱,以較低的成本實現頁面間的跳轉過渡等。在將來 Protal 普及後(至少 Chrome 發佈, Safari 跟進後),咱們就能夠在新版本的瀏覽器中拋棄如今使用 JS 實現的沙箱機制,使用更加安全(且炫酷)的 Portal API 來實現同屏渲染。
在 Protal API 的支持下,咱們也能夠克服沒法跨域的問題,按照目前的草案,Portal 是支持跨域跳轉的。
拓展閱讀
[1]qiankun ( https://github.com/umijs/qiankun))
[2]Hands-on with Portals: seamless navigation on the Web
(https://web.dev/hands-on-portals/))
重磅推薦 | 2020 阿里雲線上峯會
就在今天!1 場主旨論壇,27 個產品技術及戰略發佈,30 場行業及產品技術專題演講以及技術大咖脫口秀,思想的盛宴,有料,自助,可續杯。點擊」閱讀原文「當即參會!