適合所有網(wǎng)站的前端優(yōu)化技巧,值得你收藏!

導(dǎo)讀:本文雖然明指了某個(gè)主頁(yè),但是其實(shí)是適合所有網(wǎng)站系統(tǒng)前端的優(yōu)化技巧的。
最近,我們將 Universe.com 主頁(yè)的性能提升了十倍以上。在本文中,我們將解析實(shí)現(xiàn)這一重大改進(jìn)的具體技術(shù)手段。
但在開(kāi)始之前,讓我們先對(duì)網(wǎng)絡(luò)性能的重要意義進(jìn)行一番論證(博文末尾提供相關(guān)案例研究鏈接):
用戶體驗(yàn): 糟糕的性能可能導(dǎo)致響應(yīng)失敗,從 UI 與 UX 的角度來(lái)看,這可能會(huì)引發(fā)用戶的沮喪情緒。
客戶轉(zhuǎn)化與收入: 網(wǎng)站速度緩慢通常會(huì)導(dǎo)致客戶流失,并對(duì)轉(zhuǎn)化率與收入產(chǎn)生負(fù)面影響。
SEO: 從 2019 年 7 月 1 日開(kāi)始,谷歌公司開(kāi)始在全部新網(wǎng)站上默認(rèn)啟用移動(dòng)優(yōu)先索引。如果網(wǎng)站在移動(dòng)設(shè)備上運(yùn)行緩慢,且沒(méi)有針對(duì)移動(dòng)設(shè)備進(jìn)行內(nèi)容格式調(diào)整,那么網(wǎng)站的搜索排名將會(huì)降低。
在本篇文章中,我們將簡(jiǎn)要介紹以下幾大有助于我們提高頁(yè)面性能的主要領(lǐng)域:
性能測(cè)量: 實(shí)驗(yàn)室與現(xiàn)場(chǎng)工具測(cè)量。
渲染: 客戶端與服務(wù)器端渲染、預(yù)渲染以及混合渲染方法。
網(wǎng)絡(luò): CDN、緩存、GraphQL 緩存、編碼、HTTP/2 以及 Server Push。
瀏覽器中的 JavaScript: 數(shù)據(jù)包大小預(yù)算、代碼拆分、async 與 defer 腳本、圖像優(yōu)化(WebP、延遲加載、漸進(jìn)式設(shè)計(jì))以及資源提示(preload、prefetch 與 preconnect)。
這里再介紹一點(diǎn)我們的情況:我們的主頁(yè)由 React(TypeScript)、Phoenix(Elixir)、Puppeteer(headless Chrome)以及 GraphQL API(Ruby on Rails)構(gòu)建而成。以下為主頁(yè)在移動(dòng)設(shè)備上顯示的效果:

Universe 主頁(yè)與瀏覽效果
沒(méi)有數(shù)據(jù)作為支持,一切意見(jiàn)都將毫無(wú)意義。
—— W. Edwards Deming
實(shí)驗(yàn)室工具能夠立足受控環(huán)境從預(yù)定義的設(shè)備及網(wǎng)絡(luò)設(shè)置中收集數(shù)據(jù)。利用這些工具,我們能夠輕松調(diào)試任何性能問(wèn)題并實(shí)現(xiàn)良好的可重復(fù)測(cè)試。
Lighthouse 就是一款立足本地計(jì)算機(jī)對(duì) Chrome 內(nèi)網(wǎng)頁(yè)進(jìn)行審計(jì)的出色工具。其能夠提供一系列關(guān)于如何提高性能、可訪問(wèn)性以及搜索引擎優(yōu)化的實(shí)用性提示。下面,我們來(lái)看模擬高速 3G 加 4x CPU 場(chǎng)景下的 Lighthouse 性能審計(jì)報(bào)告:

之前與之后:首屏內(nèi)容填充(簡(jiǎn)稱(chēng) FCP)性能實(shí)現(xiàn) 10 倍提升
然而,單純使用實(shí)驗(yàn)室工具也會(huì)帶來(lái)不少弊端:這類(lèi)工具不一定能準(zhǔn)確反映出最終用戶所面臨的設(shè)備、網(wǎng)絡(luò)、位置以及多種其它現(xiàn)實(shí)因素造成的性能瓶頸。正因?yàn)槿绱耍覀儾判枰浜犀F(xiàn)場(chǎng)工具進(jìn)行補(bǔ)充。
現(xiàn)場(chǎng)工具允許我們模擬并測(cè)量用戶的真實(shí)頁(yè)面負(fù)載。目前有多種服務(wù)可幫助大家從實(shí)際設(shè)備當(dāng)中獲取真實(shí)性能數(shù)據(jù):
WebPageTest?—?允許用戶立足不同位置上的實(shí)際設(shè)備對(duì)不同瀏覽器進(jìn)行性能測(cè)試。
Test My Site?—?使用 Chrome 用戶體驗(yàn)報(bào)告 (CrUX) 功能,并以 Chrome 使用情況統(tǒng)計(jì)為基礎(chǔ);這款工具公開(kāi)可用且每月更新一次。
PageSpeed Insights?—?將實(shí)驗(yàn)室(Lighthouse)與現(xiàn)場(chǎng)(CrUX)數(shù)據(jù)加以結(jié)合。

WebPageTest 報(bào)告
內(nèi)容的渲染可通過(guò)多種方法實(shí)現(xiàn),其中每一種都擁有獨(dú)特的優(yōu)勢(shì)與缺點(diǎn):
服務(wù)器端渲染 (SSR) 是指在服務(wù)器端為瀏覽器提供最終 HTML 文檔的過(guò)程。優(yōu)勢(shì):搜索引擎可以直接抓取網(wǎng)站而無(wú)需執(zhí)行 JavaScript(SEO)、快速初始頁(yè)面加載、代碼僅存在于服務(wù)器端。短板:非富網(wǎng)站交互、整頁(yè)重新加載、瀏覽器功能受限。
客戶端渲染是指利用 JavaScript 在瀏覽器當(dāng)中進(jìn)行內(nèi)容渲染的過(guò)程。優(yōu)勢(shì):富網(wǎng)站交互、在初始加載后可快速呈現(xiàn)路由變更內(nèi)容、支持現(xiàn)代瀏覽器功能(例如配合 Service Workers 實(shí)現(xiàn)離線支持)。短板:SEO 友好性差、初始頁(yè)面加載緩慢、通常需要在服務(wù)器端實(shí)現(xiàn)單頁(yè)面應(yīng)用程序(SPA)與 API。
預(yù)渲染類(lèi)似于服務(wù)器端渲染方法,但渲染會(huì)提前發(fā)生在構(gòu)建時(shí)而非運(yùn)行時(shí)。優(yōu)勢(shì):built 靜態(tài)支持文件通常比服務(wù)器運(yùn)行方法更簡(jiǎn)單、SEO 友好性高、快速初始頁(yè)面加載。短板:需要在執(zhí)行任何代碼變更時(shí)提前進(jìn)行完整頁(yè)面重新加載、非富網(wǎng)站交互、瀏覽器功能訪問(wèn)限制。
以前,我們將自己的主頁(yè)與 Ember.js 框架一同實(shí)現(xiàn)為采用客戶端渲染方法的單頁(yè)面應(yīng)用。但這種做法的一大問(wèn)題在于,我們的 Ember.js 應(yīng)用程序包過(guò)大。這意味著在瀏覽器下載 JavaScript 文件并對(duì)其進(jìn)行解析、編譯與執(zhí)行的過(guò)程中,用戶只能對(duì)著空白屏幕發(fā)呆:

最要命的空白屏幕
因此,我們決定利用 React 重構(gòu)應(yīng)用當(dāng)中的某些部分。
我們的開(kāi)發(fā)人員已經(jīng)非常熟悉 React 應(yīng)用程序的構(gòu)建方法(例如嵌入式功能部件)。
我們已經(jīng)擁有多個(gè) React 組件庫(kù)可在多個(gè)項(xiàng)目間隨意共享。
新的頁(yè)面中將可包含一些交互式 UI 元素。
龐大的 React 生態(tài)系統(tǒng)能夠提供多種工具方案。
利用瀏覽器中的 JavaScript,我們可以通過(guò)多項(xiàng)強(qiáng)大功能構(gòu)建起漸進(jìn)式 Web 應(yīng)用。
客戶端渲染應(yīng)用程序的具體構(gòu)建——例如采用 React Router DOM,仍然會(huì)帶來(lái)與 Ember.js 相同的問(wèn)題。JavaScript 需要占用大量資源,而且訪問(wèn)者需要經(jīng)歷一段首屏內(nèi)容填充周期才能看到實(shí)際內(nèi)容。
因此在決定使用 React 之后,我們開(kāi)始嘗試其它潛在的渲染選項(xiàng),以確保瀏覽器能夠更快地完成內(nèi)容渲染。

使用 React 時(shí)的常規(guī)渲染選項(xiàng)
Gatsby.js 允許我們利用 React 與 GraphQL 構(gòu)建預(yù)渲染頁(yè)面。Gatsby.js 是一款強(qiáng)大的工具,能夠直接提供多種性能優(yōu)化方案。然而,預(yù)渲染方法并不適合我們的需求,因?yàn)槲覀兊木W(wǎng)站中可能存在無(wú)數(shù)包含用戶生成內(nèi)容的頁(yè)面。
Next.js 是一套高人氣 Node.js 框架,允許用戶通過(guò) React 實(shí)現(xiàn)服務(wù)器端渲染。然而,Next.js 設(shè)定了太多條條框框,要求用戶使用它提供的路由機(jī)制以及 CSS 解決方案等等。另外,我們的現(xiàn)有組件庫(kù)是專(zhuān)為瀏覽器構(gòu)建的,與 Node.js 并不兼容。
因此,我們打算嘗試一下混合方法,即發(fā)揮每一種渲染選項(xiàng)中的獨(dú)特優(yōu)勢(shì)。
Puppeteer 是一套 Node.js 庫(kù),允許用戶使用 headless Chrome。我們希望嘗試?yán)?Puppeteer 在運(yùn)行時(shí)當(dāng)中實(shí)現(xiàn)預(yù)渲染。這代表著一種有趣的混合方法:利用 Puppeteer 進(jìn)行服務(wù)器端渲染,同時(shí)利用 hydration 進(jìn)行客戶端渲染。感興趣的朋友可以點(diǎn)擊此處查看谷歌提供的關(guān)于如何利用 headless 瀏覽器進(jìn)行服務(wù)器端渲染的相關(guān)提示。

利用 Puppeteer 對(duì) React 應(yīng)用程序進(jìn)行運(yùn)行時(shí)預(yù)渲染
這種方法具備以下優(yōu)勢(shì):
允許 SSR,因此有利于 SEO 優(yōu)化。抓取程序不需要執(zhí)行 JavaScript 即可看到網(wǎng)頁(yè)內(nèi)容。
允許一次性構(gòu)建起簡(jiǎn)單的瀏覽器 React 應(yīng)用程序,而后將其同時(shí)用于服務(wù)器端與瀏覽器內(nèi)。這將同時(shí)提高瀏覽器應(yīng)用與 SSR 的速度表現(xiàn),一舉兩得。
利用 Puppeteer 在服務(wù)器端渲染頁(yè)面,在速度上一般快于在最終用戶的移動(dòng)設(shè)備上進(jìn)行渲染(前者網(wǎng)絡(luò)連接更強(qiáng)、硬件配置也更高)。
Hydration 允許我們構(gòu)建起富 SPA,并可訪問(wèn) JavaScript 的瀏覽器功能。
我們不再需要預(yù)先了解所有可能被調(diào)用的頁(yè)面,也不需要預(yù)先進(jìn)行渲染。
但在采用這種方法的過(guò)程中,我們也遇到了一些挑戰(zhàn):
吞吐量是最主要的問(wèn)題。每項(xiàng)請(qǐng)求都會(huì)在單獨(dú)的 headless 瀏覽器進(jìn)程當(dāng)中占用大量資源。雖然我們可以使用單一 headless 瀏覽器進(jìn)程并在其中的各個(gè)選項(xiàng)卡內(nèi)運(yùn)行多項(xiàng)請(qǐng)求,但使用多個(gè)選項(xiàng)卡仍會(huì)降低整個(gè)進(jìn)程的性能水平。

利用 Puppeteer 的服務(wù)器端渲染架構(gòu)
? 穩(wěn)定性。對(duì)眾多 headless 瀏覽器進(jìn)行規(guī)模伸縮,同時(shí)保持進(jìn)程不致過(guò)熱并實(shí)現(xiàn)負(fù)載均衡絕對(duì)是一項(xiàng)高難挑戰(zhàn)。我們嘗試了不同的托管方法,包括在 Kubernetes 集群內(nèi)進(jìn)行自托管,以及利用 AWS Lambda 與 Google Cloud Functions 實(shí)現(xiàn)無(wú)服務(wù)器計(jì)算。我們注意到,后一種方法在配合 Puppeteer 時(shí)存在一些性能問(wèn)題:

AWS Lambdas和GCP函數(shù)的Puppeteer響應(yīng)時(shí)間
在配合 AWS Lambdas 與 GCP Functions 時(shí),Puppeteer 的響應(yīng)時(shí)間結(jié)果隨著我們對(duì) Puppeteer 熟悉程度的逐步提升,我們開(kāi)始對(duì)初始方法進(jìn)行迭代(后文將具體說(shuō)明)。我們還進(jìn)行了其它一系列有趣的實(shí)驗(yàn),希望通過(guò) headless 瀏覽器渲染 PDF。再有,即使不編寫(xiě)任何代碼,我們也能夠利用 Puppeteer 自動(dòng)進(jìn)行端到端測(cè)試。而且除了 Chrome 之外,Puppeteer 現(xiàn)在還支持 Firefox 瀏覽器。
在運(yùn)行時(shí)中使用 Puppeteer 并非易事。正因?yàn)槿绱耍覀儾艣Q定在構(gòu)建時(shí)中加以使用,同時(shí)配合一款工具用于在運(yùn)行時(shí)內(nèi)從服務(wù)器端獲取用戶生成的實(shí)際內(nèi)容。很明顯,這款工具必須擁有比 Puppeteer 更強(qiáng)大的穩(wěn)定性與吞吐能力。
我們決定使用 Elixir 編程語(yǔ)言。Elixir 看起來(lái)與 Ruby 非常相似,但運(yùn)行在 BEAM(Erlang VM)之上。順帶一提,BEAM 專(zhuān)門(mén)為構(gòu)建高容錯(cuò)、高穩(wěn)定性系統(tǒng)而生。
Elixir 采用 Actor 并發(fā)模型。每個(gè)“Actor”(即 Elixir 進(jìn)程)的內(nèi)存占用量都非常有限,僅為 1 到 2 KB。這意味著系統(tǒng)將能夠同時(shí)運(yùn)行成千上萬(wàn)個(gè)獨(dú)立的進(jìn)程。Phoenix 則是一套 Elixir Web 框架,能夠支持高吞吐量,并允許開(kāi)發(fā)者在各個(gè)獨(dú)立的 Exlixir 進(jìn)程當(dāng)中處理各項(xiàng) HTTP 請(qǐng)求。
我們將上述方法結(jié)合起來(lái),充分利用其各自?xún)?yōu)勢(shì),希望能夠切實(shí)滿足自身需求:

Puppeteer 用于實(shí)現(xiàn)預(yù)渲染,Phoenix 則用于實(shí)現(xiàn)服務(wù)器端渲染
Puppeteer 在構(gòu)建時(shí)中按照我們預(yù)期的方式對(duì) React 頁(yè)面進(jìn)行預(yù)渲染,并將結(jié)果保存為 HTML 文件(來(lái)自 PRPL 模式的 app shell)。
我們可以繼續(xù)構(gòu)建一款簡(jiǎn)單的瀏覽器 React 應(yīng)用程序,并在無(wú)需等待最終用戶設(shè)備 JavaScript 處理過(guò)程的同時(shí)獲得快速初始頁(yè)面加載效果。
我們的 Phoenix 應(yīng)用負(fù)責(zé)實(shí)現(xiàn)頁(yè)面預(yù)渲染,并以動(dòng)態(tài)方式將實(shí)際內(nèi)容注入至 HTML。這就使得內(nèi)容的 SEO 友好性大幅提升,讓按需處理大量多種頁(yè)面成為可能,并顯著降低了擴(kuò)展難度。
客戶端接收并立即開(kāi)始顯示 HTML,而后由 Hydration 將 React DOM 狀態(tài)持續(xù)作為常規(guī) SPA。如此一來(lái),我們就構(gòu)建起了高度交互的應(yīng)用程序,并可訪問(wèn)各項(xiàng) JavaScript 瀏覽器功能。

利用 Puppeteer 建立預(yù)渲染架構(gòu),利用 Phoenix 進(jìn)行服務(wù)器端渲染,React 則在客戶端上實(shí)現(xiàn) hydration
利用 CDN 可幫助我們實(shí)現(xiàn)內(nèi)容緩存,并加速其在全球范圍內(nèi)的交付速度。我們選擇了 Fastly.com,其目前處理著全球超過(guò) 10% 的請(qǐng)求總量,并得到 GitHub、Stripe、Airbnb 以及 Twitter 等諸多廠商的青睞。
Fastly 允許我們編寫(xiě)定制化緩存,并可利用 VCL 配置語(yǔ)言建立路由邏輯。下面,我們將具體聊聊基礎(chǔ)請(qǐng)求流如何根據(jù)路由、請(qǐng)求頭等因素分步起效:
VCL 請(qǐng)求流
提高性能的另一個(gè)選項(xiàng)是配合 Fastly 在邊緣位置使用 WebAssembly(WASM)。大家可以將其視為一種無(wú)服務(wù)器模式,只是處于邊緣位置;所使用的語(yǔ)言則包括 C、Rust、Go 以及 TypeScript 等等。Cloudflare 就擁有一個(gè)類(lèi)似的項(xiàng)目,用于在 Workers 上支持 WASM。
盡可能多地利用緩存處理請(qǐng)求是改善性能水平的關(guān)鍵所在。立足 CDN 層級(jí)進(jìn)行緩存,將能夠更快地為新用戶提供響應(yīng)。而通過(guò)發(fā)送 Cache-Control 頭進(jìn)行緩存,則可加快瀏覽器中重復(fù)請(qǐng)求的響應(yīng)速度。
大多數(shù)構(gòu)建工具(例如 Webpack)允許用戶向文件名當(dāng)中添加哈希值。由于指向這些文件的任何變更都會(huì)產(chǎn)生新的輸出文件名,因此大家可以安心將文件添加至緩存當(dāng)中。

通過(guò) HTTP/2 進(jìn)行文件緩存與編碼
發(fā)送 GraphQL 請(qǐng)求的一種常見(jiàn)方法,就是利用 POST HTTP 方法。而我們選擇了立足 Fastly 層級(jí)對(duì)部分 GraphQL 請(qǐng)求進(jìn)行緩存:
我們的 React 應(yīng)用會(huì)標(biāo)注出那些可進(jìn)行緩存的 GraphQL 查詢(xún)。
在發(fā)送 HTTP 請(qǐng)求之前,我們以請(qǐng)求本體為基礎(chǔ)構(gòu)建一條附加 URL 參數(shù),其中包含 GraphQL 查詢(xún)與變量(我們配合 Apollo Client 使用自定義 fetch)。
在默認(rèn)情況下 ,Varnish(與 Fastly)會(huì)使用完整的 URL 作為緩存密鑰的一部分。
這意味著我們可以通過(guò)請(qǐng)求本體當(dāng)中的 GraphQL 查詢(xún)不斷發(fā)送 POST 請(qǐng)求,并在無(wú)需接觸服務(wù)器的前提下立足邊緣位置完成緩存。

利用一條 SHA256 URL 參數(shù)發(fā)送 POST GraphQL 請(qǐng)求
以下是其它一些值得參考的潛在 GraphQL 緩存策略:
服務(wù)器端緩存:立足解析器層級(jí)或者通過(guò)模式標(biāo)注對(duì)全部 GraphQL 請(qǐng)求進(jìn)行緩存。
利用持久化 GraphQL 查詢(xún)并發(fā)送 GET /graphql/:queryId 以使用 HTTP 緩存機(jī)制。
利用自動(dòng)化工具(例如 Apollo Server 2.0)或者 GraphQL 專(zhuān)用型 CDN(例如 FastQL)實(shí)現(xiàn)不同 CDN 的整合。
目前,所有主流瀏覽器都支持利用 gzip 加 Content-Encoding 標(biāo)頭進(jìn)行數(shù)據(jù)壓縮。這意味著面向?yàn)g覽器的發(fā)送數(shù)據(jù)量更低,從而帶來(lái)更快的內(nèi)容傳遞速度。此外,如果瀏覽器支持,大家也可以嘗試使用效率更高的 brotli 壓縮算法。
HTTP/2 是 HTTP 網(wǎng)絡(luò)協(xié)議的新版本(DevConsole 中簡(jiǎn)稱(chēng)為 h2)。由于存在著以下幾項(xiàng)與 HTTP/1.x 版本間的顯著差別,切換至 HTTP/2 能夠帶來(lái)性能提升:
HTTP/2 為二進(jìn)制,而非文本式。因此其解析效率更高,也更加緊湊。
HTTP/2 具有多路復(fù)用屬性,這意味著 HTTP/2 可以通過(guò)單一 TCP 連接發(fā)送多項(xiàng)請(qǐng)求。如此一來(lái),我們就不必?fù)?dān)心每主機(jī)瀏覽器連接限制以及域名分片等問(wèn)題。
其利用標(biāo)頭壓縮機(jī)制減少請(qǐng)求 / 響應(yīng)的實(shí)際體積。
允許服務(wù)器主動(dòng)推送響應(yīng)。這項(xiàng)功能擁有諸多有趣的實(shí)際應(yīng)用方式。
由于給現(xiàn)有工具及生態(tài)系統(tǒng)(例如 rack)引入了一系列顛覆性的變更,很多編程語(yǔ)言與庫(kù)并不能完全支持 HTTP/2 的全部功能。但即便如此,我們?nèi)匀豢梢栽诓糠趾线m的場(chǎng)景中使用 HTTP/2。舉例來(lái)說(shuō):
利用 HTTP/2 在常規(guī) HTTP/1.x 服務(wù)器之前設(shè)置一套 h2o 或者 nginx 代理服務(wù)器。Puma 與 Ruby on Rails 能夠發(fā)送 Early Hints,從而在一定的限制條件下啟用 HTTP/2 Server Push。
利用支持 HTTP/2 的 CDN 交付靜態(tài)資產(chǎn)。例如,我們可以使用這種方法將字體以及一部分 JavaScript 文件推送至客戶端。

HTTP/2 推送字體
對(duì) JavaScript 以及 CSS 的推送功能同樣非常實(shí)用。但請(qǐng)注意不要過(guò)度推送,您可點(diǎn)擊此處了解一些相關(guān)問(wèn)題:https://jakearchibald.com/2017/h2-push-tougher-than-i-thought/
JavaScript 性能優(yōu)化中的頭號(hào)規(guī)則就是,不要使用 JavaScript。
—— 我自己
如果您已經(jīng)擁有現(xiàn)成的 JavaScript 應(yīng)用程序,那么設(shè)置預(yù)算規(guī)則能夠提高包大小的可見(jiàn)性,同時(shí)確保全部?jī)?nèi)容都可容納于同一頁(yè)面當(dāng)中。超出預(yù)算后,開(kāi)發(fā)人員則需要謹(jǐn)慎考慮并盡量防止規(guī)模進(jìn)一步增長(zhǎng)。以下是預(yù)算設(shè)置方面的相關(guān)示例:
根據(jù)您的實(shí)際需求或推薦值設(shè)定數(shù)值。例如,不得大于 170 KB 否則壓縮 JavaScript。
利用現(xiàn)有包大小作為基準(zhǔn),或者嘗試對(duì)其進(jìn)行削減——例如下調(diào) 10%。
嘗試讓網(wǎng)站擁有高于競(jìng)爭(zhēng)對(duì)手的速度,并以此為依據(jù)設(shè)定預(yù)算。
您可以使用 bundlesize 工具包或者 Webpack 性能提示與限制進(jìn)行預(yù)算跟蹤:

Webpack 性能提示與限制
Sidekiq 曾在一篇博文中提到:“代碼越少,運(yùn)行速度越快。代碼越少,bug 就越少。代碼越少,占用的內(nèi)存量就越低。代碼越少,理解起來(lái)就越輕松。”
遺憾的是,實(shí)際 JavaScript 場(chǎng)景中往往存在著不計(jì)其數(shù)的依賴(lài)關(guān)系。您可以試試:ls node_modules | wc -l。
在某些情況下,添加依賴(lài)性是種必然的選擇。在這種情況下,依賴(lài)性的包大小應(yīng)該被視為決定您實(shí)際工具包選擇的重要依據(jù)。我強(qiáng)烈建議大家使用 BundlePhobia:

BundlePhobia 能夠提示將 npm 工具包添加至您數(shù)據(jù)包中帶來(lái)的實(shí)際成本
使用代碼拆分是另一種能夠顯著提高 JavaScript 性能的好辦法。其本質(zhì)在于分解代碼片段并僅向用戶交付當(dāng)前所需要的部分。以下是關(guān)于代碼拆分的相關(guān)示例:
在不同的 JavaScript 代碼塊間分別加載路由機(jī)制。
拆分那些在頁(yè)面中無(wú)法立即顯示的部分,例如彈出框以及頁(yè)面下方的頁(yè)腳。
Polyfills 與 ponyfills 可支持全部主流瀏覽器當(dāng)中的各最新瀏覽器功能。
利用 Webpack 的 SplitChunksPlugin 防止代碼重復(fù)。
按需定位文件,以避免一次性發(fā)送所有受支持的語(yǔ)言。
您可以利用 Webpack 動(dòng)態(tài)導(dǎo)入以及 React.lazy 配合 Suspense 實(shí)現(xiàn)代碼拆分。

利用動(dòng)態(tài)導(dǎo)入以及 React.lazy 配合 Suspense 實(shí)現(xiàn)代碼拆分。
相較于默認(rèn)導(dǎo)出,我們構(gòu)建的函數(shù)可取代 React.lazy 以支持點(diǎn)名導(dǎo)出。
目前,全部主流瀏覽器皆在 script 標(biāo)簽上支持 async 與 defer 屬性:

加載JavaScript的不同方式
幾種不同的 JavaScript 加載方式:
內(nèi)聯(lián)腳本適用于加載小體積、高關(guān)鍵度 JavaScript 代碼。
當(dāng)您的用戶或者任何其它腳本(例如分析腳本)不再需要某些特定腳本時(shí),大家可以將 async 與這些腳本配合使用以避免 HTML 解析阻塞。
從性能角度來(lái)看,將 defer 與腳本配合使用能夠有效提升非關(guān)鍵 JavaScript 代碼的抓取與執(zhí)行效率,且避免發(fā)生 HTML 解析阻塞。此外,這種做法還能夠在調(diào)用腳本時(shí)保證執(zhí)行順序,從而確保不同腳本間存在依賴(lài)性時(shí)實(shí)時(shí)與預(yù)期相符的執(zhí)行效果。
下面來(lái)看 head 標(biāo)簽下不同腳本間的可視化差異:

幾種不同的腳本抓取與執(zhí)行方式
雖然與 100 KB 的圖像相比,100 KB 的 JavaScript 代碼明確會(huì)帶來(lái)更高的性能成本,但我們同樣有必要重視對(duì)圖像內(nèi)容的優(yōu)化調(diào)整。
削減圖像大小的有效手段之一,是在適用的瀏覽器當(dāng)中采用更加輕量化的 WebP 圖像。對(duì)于那些無(wú)法支持 WebP 的瀏覽器,大家則可以采取以下幾種策略:
回退至常規(guī)的 JPEG 或者 PNG 格式(某些 CDN 會(huì)根據(jù)瀏覽器的 Accept 請(qǐng)求標(biāo)頭自動(dòng)執(zhí)行)。
在檢測(cè)瀏覽器的支持情況后,加載并使用 WebP polyfill。
利用 Service Workers 監(jiān)聽(tīng) fetch 請(qǐng)求,并在支持時(shí)利用 WebP 變更實(shí)際 URL。

WebP 圖像
僅當(dāng)圖像位于視圖當(dāng)中或者附近時(shí)才進(jìn)行內(nèi)容加載,堪稱(chēng)多圖像初始頁(yè)面加載過(guò)程中效果最顯著的提速手段之一。您可以在受支持的瀏覽器當(dāng)中使用 IntersectionObserver 功能,也可以利用其它一些替代性工具實(shí)現(xiàn)相同的結(jié)果——例如 react-lazyload。

在滾動(dòng)過(guò)程中進(jìn)行圖像的延遲加載
其它一些圖像優(yōu)化策略還包括:
降低圖像質(zhì)量以減小體積。
調(diào)整大小并加載最小圖像。
利用 Srcset 圖像屬性自動(dòng)在高分辨率顯示器上加載高質(zhì)量圖像。
利用漸進(jìn)式圖像快速顯示圖像的模糊版本。

常規(guī)圖像與漸進(jìn)圖像之間的加載效果差異
大家也可以考慮使用通用型 CDN 或者圖像專(zhuān)用 CDN,其通常會(huì)直接提供與圖像相關(guān)的優(yōu)化功能。
資源提示(Resource hints) 允許我們優(yōu)化資源交付、降低往返次數(shù),同時(shí)獲取資源以實(shí)現(xiàn)頁(yè)面瀏覽過(guò)程中的內(nèi)容交付提速。

帶有 link 標(biāo)簽的資源提示
Preload 會(huì)在當(dāng)前頁(yè)面實(shí)際使用之前,通過(guò)后臺(tái)預(yù)先下載高優(yōu)先級(jí)資源。
Prefetch 的功能與 preload 類(lèi)似,用于抓取資源并進(jìn)行緩存,但僅供用戶后續(xù)導(dǎo)航使用(低優(yōu)先級(jí))。
Preconnect 允許 HTTP 請(qǐng)求被實(shí)際發(fā)送至服務(wù)器之前即設(shè)置預(yù)連接。

提前進(jìn)行預(yù)連接以避免 DNS、TCP 以及 TLS 往返延遲
當(dāng)然,prerender 以及 dns-prefetch 等其它一些資源提示同樣非常重要。其中一部分資源提示可在響應(yīng)標(biāo)頭中進(jìn)行指定。需要提醒大家的是,請(qǐng)務(wù)必小心使用資源提示。一旦開(kāi)始濫用,您的頁(yè)面中可能包含大量不必要的請(qǐng)求并快速下載過(guò)量數(shù)據(jù),這種情況顯然不利于使用蜂窩數(shù)據(jù)的移動(dòng)用戶。
應(yīng)用程序的性能改善之路代表著一個(gè)永遠(yuǎn)盡頭的過(guò)程,且通常要求我們?cè)谡麄€(gè)堆棧當(dāng)中持續(xù)作出更改。
每次看到下面這段視頻,我總會(huì)想起你們努力減少應(yīng)用包大小的樣子。
——我的同事

馬上把一切不需要的東西從飛機(jī)上扔下去!——電影《珍珠港》
以下列出了我們已經(jīng)使用或者計(jì)劃嘗試的其它一些潛在性能改進(jìn)思路:
使用 Service Workers 進(jìn)行緩存、離線支持以及主線程分?jǐn)偂?/p>
通過(guò)關(guān)鍵 CSS 內(nèi)聯(lián)或者函數(shù)式 CSS 實(shí)現(xiàn)數(shù)據(jù)包的長(zhǎng)效“瘦身”。
使用 WOFF2 字體替代 WOFF 字體(僅舉一例,字體變更最高可帶來(lái) 50% 壓縮效果)。
確保 browserslist 的定期更新。
利用 webpack-bundle-analyzer 直觀分析構(gòu)建塊。
優(yōu)選較小的工具包(例如 date-fns)及插件(例如 lodash-webpack-plugin),從而縮小頁(yè)面體積。
嘗試使用 preact、lit-html 或者 svelte。
在 CI 中運(yùn)行 Lighthouse。
漸進(jìn)式 hydration 與 React 流式設(shè)計(jì)。
另外還有更多令人興奮的想法可供嘗試。希望本文提出的信息及以下案例研究能夠激發(fā)出大家改善應(yīng)用程序性能的更多靈感:
根據(jù)亞馬遜方面的計(jì)算,單一頁(yè)面 1 秒的響應(yīng)延時(shí)每年可能造成 16 億美元損失。鏈接地址:https://www.fastcompany.com/1825005/how-one-second-could-cost-amazon-16-billion-sales
沃爾瑪每縮短 1 秒加載時(shí)長(zhǎng),即可提升 2% 的客戶轉(zhuǎn)換率。每 100 毫秒的提升則可帶來(lái) 1% 的收入增長(zhǎng)。鏈接地址:https://wpostats.com/2015/11/04/walmart-revenue.html
谷歌公司計(jì)算出,如果搜索結(jié)果顯示速度減緩 0.4 秒,則每天搜索量將減少 800 萬(wàn)次。鏈接地址:https://www.fastcompany.com/1825005/how-one-second-could-cost-amazon-16-billion-sales
品趣志的頁(yè)面重構(gòu)將等待時(shí)長(zhǎng)縮短了 40%,SEO 流量增加了 15%,注冊(cè)轉(zhuǎn)換率亦提升 15%。鏈接地址:https://medium.com/@Pinterest_Engineering/driving-user-growth-with-performance-improvements-cfc50dafadd7
BBC 通過(guò)觀察發(fā)現(xiàn),網(wǎng)站加載時(shí)長(zhǎng)每增加 1 秒鐘,就會(huì)失去 10% 的用戶。鏈接地址:https://www.creativebloq.com/features/how-the-bbc-builds-websites-that-scale
FT.com 通過(guò)測(cè)試證明,更快的響應(yīng)速度令用戶的參與度提高了 30%——這意味著更多的訪問(wèn)次數(shù)與更大的內(nèi)容消費(fèi)總量。鏈接地址:https://www.wsj.com/articles/financial-times-hopes-speedy-new-website-will-boost-subscribers-1475553602
Instagram 通過(guò)降低顯示評(píng)論內(nèi)容所需的 JSON 響應(yīng)包的大小,成功將展示次數(shù)與用戶個(gè)人資料滾動(dòng)操作量增加了 33%。鏈接地址:https://instagram-engineering.com/performance-usage-at-instagram-d2ba0347e442
來(lái)源:前端之巔 作者:exAspArk 譯者:核子可樂(lè) 原文:https://engineering.universe.com/improving-browser-performance-10x-f9551927dcff?gi=ef65642ac481
