來自公衆號: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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。