[譯] 咱們是怎樣把 Carousell 的移動端 Web 體驗搞快了 3 倍的?

咱們是怎樣把 Carousell 的移動端 Web 體驗搞快了 3 倍的?

回顧一下咱們構建 Progressive Web App 的 6 個月

Carousell 是一個在新加坡開發的移動分類廣告市場,並在包括印度尼西亞、馬來西亞和菲律賓在內的許多東南亞國家開展業務。咱們在今年年初爲一批用戶推出了咱們移動 Web 端的漸進式網頁應用(PWA)] 版本。javascript

在本文中,咱們將分享 (1) 咱們想要創建更快的 Web 端體驗的動機,(2) 咱們怎麼完成它,(3) 它對咱們用戶的影響,以及 (4) 是什麼幫助了咱們快速完成。php

🖼 這個 PWA 在 mobile.carousell.com 🔎css

爲何必定要有更快的 Web 體驗?

咱們的應用是爲新加坡市場開發的,咱們已經習慣於用戶擁有高於平均水平的手機和高速的互聯網。然而,隨着咱們擴展到整個東南亞地區的更多國家,如印度尼西亞和菲律賓,咱們面臨着提供一樣使人愉快和快速的網絡體驗的挑戰。緣由是,在這些地方,較通常的終端設備互聯網速度 與咱們的應用設計標準相比,每每速度慢而且不太可靠。前端

咱們開始閱讀更多有關性能的內容,並開始使用 Lighthouse 從新審視咱們的應用,咱們意識到 若是咱們想要在這些新的市場中成長咱們須要更快的 Web 體驗若是咱們想要獲取或是留住咱們的用戶,那麼一個網頁在 3G 網絡下(跟咱們同樣)須要加載超過 15 秒就是不能接受的了。java

🌩 Lighthouse 的性能表現得分會是一個很好的叫醒服務~ 🏠react

Web 端一般是咱們的新用戶發現和了解 Carousell 的入口。咱們想從一開始就給他們一個愉快的體驗,由於 性能就是用戶體驗android

爲此,咱們設計完成了一種全新的,性能優先的 Web 端體驗。當咱們決定首先使用哪些頁面作嘗試時,咱們選擇了產品列表頁面和主頁,由於 Google Analytics 的統計代表這些頁面的天然流量最大。webpack


咱們怎麼作到的

從現實世界中的性能預算開始

咱們作的第一件事就是起草性能預算,以免犯下未經檢查的臃腫問題(咱們以前的 Web 應用中的一個問題)。ios

性能預算讓每一個人都在同一個「頁面」上。它們有助於創造一種共享熱情的文化,以改善用戶體驗。具備預算的團隊還能夠更輕鬆地跟蹤和繪製進度。這有助於支持那些擁有有意義的指標的執行發起人,指明正在進行的投入的合理性。git

你能負擔得起嗎?:現實世界中的網絡性能預算.

因爲 在加載過程當中存在多個時刻,都會影響到用戶對這個頁面是否「足夠快」的感知,咱們將預算基於一套組合的指標。

加載網頁就像一個有三個關鍵時刻的電影膠片。三個時刻分別是:它發生了嗎?它有用嗎?而後,它能用起來嗎?

2018 年裏 JavaScript 的花費

咱們決定爲關鍵路徑的資源設置 120 KB 的上限,在全部頁面上還有一個 2 秒的 首屏內容渲染 和 5 秒的 可交互時間 限制。這些數字和指標都是基於 Alex Russell 的一篇發人深省的文章 真實世界的 Web 性能預算 以及 Google [以用戶爲中心的性能指標]。

關鍵路徑資源          120KB
首屏內容渲染          2s
可交互時間            5s
Lighthouse 性能得分  > 85
複製代碼

🔼 咱們的性能預算 🌟

爲了能把性能預算堅持下去,咱們在一開始選擇庫時就十分慎重,包括 react、react-router、redux、redux-saga 和 unfetch

咱們還整合了 bundlesize 到咱們的 PR 流程當中,用來執行咱們在關鍵路徑資源上的性能預算方案。

⚠️ bundlesize 阻止了一個超出預算的 PR 🚫

理想狀況下,咱們也會自動檢查 首屏渲染時間可交互時間 指標。可是,咱們目前尚未這樣作,由於咱們想先發布初始頁面。咱們認爲咱們能夠經過咱們的小團隊規模來避免這種狀況,每週經過咱們的 Lighthouse 審覈咱們的發佈,以確保咱們的變動在預算範圍內。

在咱們積壓的工做中,下一步就是自建性能監控框架。

咱們如何讓它(看起來)變快了

  1. 咱們採用了一部分 PRPL 模式 咱們爲每一個頁面請求發送最少許的資源(使用 基於路由的代碼拆分),並 使用 Workbox 預先緩存應用程序包的其他部分。咱們還拆分了沒必要要的組件。例如,若是用戶已登陸,則應用程序將不會加載登陸和註冊組件。目前,咱們仍然在幾個方面偏離了 PRPL 模式。首先,因爲咱們沒有時間從新設計的舊頁面,該應用程序有多個應用程序外殼。其次,咱們尚未探索爲不一樣的瀏覽器生成單獨的構建打包。

  2. 內聯的 關鍵的 CSS 咱們使用 webpack 的 mini-css-extract-plugin 來提取並內聯的方式引入對應頁面的關鍵 CSS,以優化首屏渲染時間。這樣就給用戶提供了 一些事情 正在發生 的感受。

  3. 懶加載視口外的圖像。 而且逐步加載它們。咱們建立了一個滾動觀察組件,其基於 react-lazyload,它會監聽 滾動事件,一旦計算出圖像在視口內,就開始加載圖像。

  4. 壓縮全部的圖像來減小在網絡中傳輸的數據量。 這將在咱們的 CDN 提供商的 自動化圖像壓縮 服務中進行。若是你不使用 CDN,或者只是對圖像的性能問題感到好奇,Addy Osmani 有一個 關於如何自動進行圖像優化的指南

  5. 使用 Service Worker 來緩存網絡請求。 這減小了數據不會常常變化的 API 的數據使用量,並改善了應用程序後續的訪問加載時間。咱們找到了 The Offline Cookbook 來幫助咱們決定採用哪一種緩存策略。直到咱們有了多了應用外殼,Workbox 默認的 registerNavigationRoute 並不適用於咱們的實際場景,因此咱們不得補自行完成一個 handler 來匹配當前應用外殼的導航請求。

workbox.navigationPreload.enable();

// From https://hacks.mozilla.org/2016/10/offline-strategies-come-to-the-service-worker-cookbook/.
function fetchWithTimeout(request, timeoutSeconds) {
  return new Promise((resolve, reject) => {
    const timeoutID = setTimeout(reject, timeoutSeconds * 1000);
    fetch(request).then(response => {
      clearTimeout(timeoutID);
      resolve(response);
    }, reject);
  });
}

const networkTimeoutSeconds = 3;
const routes = [
  { name: "collection", path: "/categories/.*/?$" },
  { name: "home", path: "/$" },
  { name: "listing", path: "/p/.*\\d+/?$" },
  { name: "listingComments", path: "/p/.*\\d+/comments/?$" },
  { name: "listingPhotos", path: "/p/.*\\d+/photos/?$" },
];

for (const route of routes) {
  workbox.routing.registerRoute(
    new workbox.routing.NavigationRoute(
      ({ event }) => {
        return caches.open("app-shells").then(cache => {
          return cache.match(route.name).then(response => {
            return (response
              ? fetchWithTimeout(event.request, networkTimeoutSeconds)
              : fetch(event.request)
            )
              .then(networkResponse => {
                cache.put(route.name, networkResponse.clone());
                return networkResponse;
              })
              .catch(error => {
                return response;
              });
          });
        });
      },
      {
        whitelist: [new RegExp(route.path)],
      },
    ),
  );
}
複製代碼

⚙️ 咱們對全部的應用外殼採用了一個超時時間爲 3 秒的網絡優先策略 🐚

在這些變化中,咱們嚴重依賴 Chrome 的「中端移動設備」模擬功能(即網絡限制爲 3G 速度),並建立了多個 Lighthouse 審計來評估咱們工做的影響。

結果:咱們怎麼作到的

🎉 比較以前和以後的移動 Web 指標 🎉

咱們新的 PWA 列表頁面的加載速度比咱們舊的列表頁面 快 3 倍。在發佈這一新頁面以後,咱們的印度尼西亞的天然流量與咱們全部長時間的周相比,增加了 63%。在 3 周的時間內,咱們還看到,廣告點擊率 增長了 3 倍,在列表頁面上發起聊天的匿名用戶 增長了 46%

在較快的 3G 網絡下的 Nexus 5 上,咱們列表頁面的先後對比。更新:WebPageTest 對這個頁面的簡單報告。 ⏭


快速,自信地迭代

一致的 Carousell 設計系統

在咱們開展這項工做的同時,咱們的設計團隊也在同時建立標準化設計系統。因爲咱們的 PWA 是一個新項目,咱們有機會根據設計系統建立一組標準化的 UI 組件和 CSS 常量。

擁有一致的設計使咱們可以快速迭代。每一個 UI 組件咱們只構建一次,而後在多個地方複用它。例如,咱們有一個 ListingCardList 組件,它顯示列表卡片的提要並觸發回調,以便在滾動到結尾時提示其父組件加載更多列表。咱們在主頁,列表頁面,搜索頁面和我的信息頁面中使用了它。

咱們還與設計師合做,一塊兒肯定應用程序設計中的適當性能權衡。這使咱們可以維持咱們的性能預算,改變一些舊設計以符合新設計,而且,若是它們太昂貴了的話,就放棄花哨的動畫。

與 Flow 同行

咱們選擇將 Flow 類型定義做爲咱們全部文件的必選項,由於咱們想減小煩人的空值或類型問題(我也是漸進類型的忠實粉絲,但爲何咱們選擇了 Flow 而不是 TypeScript 就是下一次的一個話題了)。

在咱們開發和建立了更多代碼時,採用了 Flow 的選擇被證實很是有用。它讓咱們有信心添加或更改代碼,將核心代碼重構得更加簡單和安全。這使咱們可以快速迭代而不會破壞事物。

此外,Flow 類型也對咱們的 API 約定和共享庫組件的文檔很是有用。

對於強制將 Redux 操做和 React 組件的類型寫出來這件事情,還有一個額外的好處,就是它會幫助咱們仔細思考如何設計咱們的 API。它也提供了與團隊開始早期的 PR 討論的簡單途徑。


小結

咱們建立了一個輕量級的 PWA 來爲咱們具備不可靠網速的用戶提供服務,一個頁面接一個頁面地發佈,提升了咱們的商業指標用戶體驗。

是什麼幫助咱們保持足夠快的速度

  • 擁有並堅持一份性能預算
  • 下降關鍵渲染路徑到最小
  • 常用 Lighthouse 進行審計

是什麼幫助咱們快速迭代

  • 擁有標準化的設計系統及其相應的 UI 組件庫
  • 擁有徹底類型化的代碼庫

結束思考

回顧過去兩個季度咱們所作的事情,咱們爲咱們新的移動 Web 業務體驗感到無比自豪,咱們正在努力使其變得更好。這是咱們第一個專一於速度的平臺,也更多的思考了一個頁面的加載過程。咱們的 PWA 對業務和用戶指標的改進有助於說服公司內部更多人去了解應用程序性能和加載時間的重要性。

咱們但願本文可以啓發您在設計和構建 Web 體驗時考慮性能。

在此爲參與這個項目的人歡呼:Trong Nhan Bui、Hui Yi Chia、Diona Lin、Yi Jun Tao 和 Marvin Chin。固然也要感謝 Google,特別是要感謝 Swetha and Minh 對這個項目的建議。

感謝 Bui、Danielle JoyHui YiJingwen ChenSee YishuYao Hui Chua 的寫做和校對。

最後,多虧了 Hui YiYao Hui ChuaDanielle JoyJingwen ChenSee Yishu

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索