我是如何將網頁性能提高5倍的 — 構建優化篇

來自公衆號:code祕密花園

最近對公司的一個 PC 站點作了一次總體的性能優化,因爲這個系統業務複雜、依賴很是多,加載速度很是慢,優化後各個性能指標都有了顯著提高,大約加載速度快了 5 倍左右。
html

我在 構建、網絡、資源加載、運行時、服務端、功能組織等多個方面都進行了優化,準備作一個系列,分章節給你們分享下個人優化經驗。react

今天,咱們從優化效果最爲明顯的構建角度開始。webpack

優化前

首先咱們看一下在優化前站點的資源加載狀況:web

可見最大的 vendor 包竟然有 3MB(通過 gzip 壓縮後),沒有作額外配置的話,webpack 將全部的第三方依賴都打入了這個包,若是引入依賴愈來愈多,那麼這個包就會愈來愈大。npm

另外,系統自己的邏輯打的包也達到了 600kb編程

分析依賴關係

咱們能夠藉助 webpack-bundle-analyzer 將打包後的內容展現爲方便交互的樹狀圖,咱們能夠很直觀的看到有哪些比較大的模塊,而後作針對性優化。緩存

npm install --save-dev webpack-bundle-analyzer

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

CDN 引入

CDN 的工做原理是將源站的資源緩存到位於全球各地的 CDN 節點上,用戶請求資源時,就近返回節點上緩存的資源,而不須要每一個用戶的請求都回您的源站獲取,避免網絡擁塞、緩解源站壓力,保證用戶訪問資源的速度和體驗。性能優化

這個估計你們都明白,由於打包後的產物自己也是上傳到 CDN 的。可是咱們要作的是將體積較大的第三方依賴單獨拆出來放到 CDN 上,這樣這個依賴既不會佔用打包資源,也不會影響最終包體積。微信

若是一個依賴有直接打包壓縮好的單文件 CDN 資源,例如上面圖中的 g6,就能夠直接使用。網絡

按照官方文檔的解釋,若是咱們想引用一個庫,可是又不想讓 webpack 打包,而且又不影響咱們在程序中以 import、require 或者 window/global 全局等方式進行使用,那就能夠經過配置 externals

externals 配置選項提供了「從輸出的 bundle 中排除依賴」的方法。相反,所建立的 bundle 依賴於那些存在於用戶環境(consumer's environment)中的依賴。

首先將 CDN 引入的依賴加入到 externals 中。

而後藉助 html-webpack-plugin 將 CDN 文件打入 html:

這裏有一點須要注意,在 html 中配置的 CDN 引入腳本必定要在 body 內的最底部,由於:

  • 若是放在  body 上面或  header 內,則加載會阻塞整個頁面渲染。
  • 若是放在  body 外,則會在業務代碼被加載以後加載,模塊中使用了該模塊將會報錯。

拆 vendor

某些場景下, 一個第三方依賴可能拆成了多個子依賴,例如上面的 monaco,或者沒有提供可直接經過 CDN 引入的文件,咱們就沒法經過配置一個 CDN 文件來引入它了。

這時咱們須要本身去 webpack 設置一些規則,將咱們想拆出來的依賴單獨打包一個 vendor

動態 import

將 vendor 拆分後,依賴仍然會在首屏被加載,若是依賴不在首屏使用,仍然會形成網絡資源的浪費,並阻塞頁面渲染,對於不必在首屏進行加載的依賴,咱們能夠採用動態 import 的方式。

例如上面這個 js-export-excel 這個依賴,本身自己有將近 500 kb,可是其只會在用戶點擊【導出】按鈕的時候使用,咱們首先在 vendor 中將其拆出來。

使用時,將 import 的邏輯由首屏改到運行時異步加載

這樣的話,js-export-excel 這個依賴包只會在用戶點擊【導出】按鈕時引入,首屏再也不引入。

不是全部依賴都適合異步加載,若是你對使用該依賴有很高的性能要求,而後依賴自己也比較大,這種狀況是不適合的,由於你可能會看到明顯的延遲。以上 export 實際上是一個比較合適的場景,下載 excel 自己須要延遲時間,加上動態加載依賴的時間是可接收的。

React 懶加載

相似的,對於某些第三方依賴組件,例如 monaco editor ,咱們只有在不多的業務場景下才會用到,可是其自己一個包占用了 5MB 。。咱們每次在打開頁面時都要加載它,這太耗費性能了。

對於一個依賴包,咱們能夠經過動態 import 的方式進行懶加載,可是對於一個 React 組件,直接使用動態 import 可能就不太合適了,組件渲染的運行時都是可屢次觸發了,不可能在每次組件渲染時都加載一次組件。

React.lazy 函數能讓你像渲染常規組件同樣處理動態引入組件。React.lazy 接受一個函數,這個函數須要動態調用 import()。它必須返回一個 Promise,該 Promise 須要 resolve 一個 default export 的 React 組件。

const MonacoEditor = React.lazy(() => import('react-monaco-editor'));

此代碼將會在組件首次渲染時,自動導入包含 MonacoEditor 組件的包。可是直接使用React.lazy引入的組件是沒法直接使用的,由於 React 沒法預測組件什麼時候被加載,直接渲染會致使頁面崩潰。

在 Suspense 組件中渲染 lazy 組件,可使用在等待加載 lazy 組件時作優雅降級(如 loading )。fallback 屬性接受任何在組件加載過程當中你想展現的 React 元素。你能夠將 Suspense 組件置於懶加載組件之上的任何位置。你甚至能夠用一個 Suspense 組件包裹多個懶加載組件。

將全部 monaco editor 改成懶加載後,首屏已經不會加載 monaco editor

路由懶加載

上面 React 懶加載的方式,一樣適用於路由,對於每一個路由都使用懶加載的方式引入,則每一個模塊都會被單獨打爲一個 js,首屏只會加載當前模塊引入的 js

不過 路由懶加載 也有一個很明顯的弊端,就是每一個模塊的資源是隻有加載這個模塊的時候纔回去下載的,因此在切換模塊的時候可能會有一小段白屏或 loading 效果,這個要結合業務自身的狀況綜合判斷要不要使用。

語言包優化

在某些場景下,語言包會佔用整個包體積的很是大一部分。實際上庫自己的邏輯不會很大,moment 就是一個很好例子。

若是最開始選擇日期庫,那直接推薦使用 dayjs 了,若是你選擇了 moment ,必定要注意把不使用的語言包過濾掉,推薦使用 ContextReplacementPlugin,它會告訴 webpack 咱們會使用到哪一個本地文件:

plugins: [
    new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn/),
  ]

優化效果

最終優化後,會發現模塊已經被咱們拆的很是均勻,而且只會在對應頁面渲染時加載對應模塊,這對首屏渲染速度有顯著提高。


本文分享自微信公衆號 - 編程微刊(wangxiaoting678)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索