在 Web 應用開發過程中,我們經常談及到的就是優化,而優化往往又是既簡單而又複雜的過程,優化這個命題很廣,最終體現出來的都是使用者體驗問題,我們一切優化都是為了使用者體驗。
為什麼說簡單?在現代 Web 開發生態中,有非常優秀的工具鏈幫助我們做一些很實際的優化工作,例如 webpack
。這些工具可以很好的幫助我們解決包之間的依賴、減小包大小、提取公共模組等等問題。
為什麼說複雜?優化這個話題我們談了很多年,只要有使用者群,優化問題就會一直存在。而優化工作涉及的領域特別廣,包含的因素又特別多,有時候需要針對特定的場景做特殊的優化工作,所以說又很複雜。
不管是簡單還是複雜,作為程式設計師,我們應當做一些我們力所能及的優化工作,本文屬於探討性話題,希望廣大網友能夠在留言區留下您的一些思考。
Code Splitting
這裡不探討如何書寫高效能的程式碼,而是探討下我們書寫的程式碼該如何被構建。這裡以 webpack 為構建工具(版本為4.19),來闡述下在 webpack 我們該做的優化工作。
webpack 從 v4
版本開始,做了很多的優化工作,詳情請看這裡 。我們就拿 Code Splitting
說起,Code Splitting
是 webpack 一項重要的編譯特性,能夠幫助我們將程式碼進行拆包,抽取出公共程式碼。利用這項特性我們可以做更多的優化工作,減少載入時間,例如可以做按需載入。而在 webpack 中開啟 Code Splitting
也很簡單,它是一項開箱即用的外掛,示例程式碼如下:
module.export = {
// ...
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
複製程式碼
上面的 chunks
配置建議大家配置為 all
,詳細配置請參考:splitChunks.chunks
這裡給出個參考結果:分別為配置前和配置後
這裡明顯多出了幾個包含 vendors~
字樣的檔案,而且你會發現 app
這個檔案被提取出了 vendors~app~react_vendor
和 vendors_app_redux_vendor
這兩個檔案。至於最大的檔案為什麼還有1.02M,我們可以使用 analyze
來分析下包的結構,這裡是由於包含了 antd
的檔案。
在實際開發過程中,路由也是需要進行 Code Splitting
,在過去我們經常使用 bundle-loader ,來幫助我們進行程式碼分割,它是基於 require.ensure 介面進行實現。既然我們可以對路由進行程式碼分割,那麼路由頁面中的元件我們是否可以按需載入,實現程式碼分割呢?答案是顯然的。
這種業務場景也是非常的多,這裡我舉一個例子,就是一個登入頁面,登入有多種方式,其中最常見的就是賬號登入和掃碼登入,預設為掃碼登入。當使用者沒有選擇賬號登入,那麼按道理這部分程式碼我們可以不進行載入,從而減少載入時間,優化使用者體驗。我們建議能進行元件級分割就分割,最大化減小頁面大小。
在 React
中雖然也可以使用 bundle-loader 來實現元件級程式碼分割,但是也會有一些問題。在後來,React Router
官方也推薦使用 react-loadable 來進行程式碼分割。強烈建議 React
使用者使用此庫,該庫的功能很強大,是基於 import() 實現。它可以實現預載入、重新載入等等強大功能。
Tree Shaking
如果你對自己編寫的程式碼很瞭解,你可以通過在 package.json
中新增 sideEffects
來啟用 Tree Shaking
,即搖樹優化,幫助我們刪掉一些不用的程式碼。這裡不再贅述,詳情可以點選Tree Shaking。
Dynamic import
在談到 Code Spliting
時,我們不得不想到 dynamic import
,在之前版本的 webpack 中,我們想實現動態載入使用的是 require.ensure ,而在新版本中,取而代之的 import() ,這是TC39關於使用 import()的提案,而目前 import()相容性如下:
import() 返回一個 Promise
,如果你想使用它請確保支援 Promise
或者使用 Polyfill
,在想使用 import() 前,我們還得使用前處理器,我們可以使用 @babel/plugin-syntax-dynamic-import 外掛來幫助webpack解析。webpack 官方給了我們一個 dynamic import
的示例 ,這裡我就不做舉例。使用 import() 我們可以很方便的實現 preload
預載入、懶載入以及上面談到的 Code Splitting
。
Polyfill
Polyfill
現在對於大家來說應該並不陌生,他可以幫助我們使用一些瀏覽器目前並不支援的特性,例如 Promise
。在Babel中,官方建議使用 babel-preset-env 配合 .browserslistrc ,開發人員可以無需關心目標環境,提升開發體驗。尤其在 Polyfill
方面,只要我們配置好 .browserslistrc ,Babel 就可以智慧的根據我們配置的瀏覽器列表來幫助我們自注入 Polyfill
,比如:
.babelrc
{
"presets": [
[
"@babel/preset-env",
{
"useBuiltIns": "entry"
}
]
]
}
複製程式碼
useBuiltIns 告訴 babel-preset-env 如何配置 Polyfill
,這裡我配置為:entry
,然後在 webpack 入口檔案中引入 import '@babel/polyfill'
即可,這裡注意不能多次引入 import '@babel/polyfill'
,否則會報錯。
.browserslistrc
> 1%
Last 2 versions
複製程式碼
這樣就完成了自動根據 .browserslistrc注入 Polyfill
,但是這樣有一個問題,就是所有的瀏覽器都會有 Polyfill
的並集。每個瀏覽器之間的特性具有很大的差異,為了儘可能的減小包的大小,我們可以為每個主流瀏覽器單獨生成 Polyfill
,不同的瀏覽器載入不同的 Polyfill
。
首屏檔案
SPA 程式打包出來的html檔案一般都是很小的,也就2kb左右,似乎我們還可以利用下這個大小做個優化,有了解初始擁塞視窗 的同學應該知道,通常是14.6KB,也就意味著這我們還能利用剩下的12KB左右的大小去幹點什麼,這了我建議內聯一些首屏關鍵的css檔案(可以使用 criticalCSS ),或者將css初始化檔案內聯進去,當然你也可以放其他東西,這裡只是充分利用下初始擁塞視窗 特性。
這裡順便講下css初始化,css初始化有很多種選擇,其中有三種比較出名的,分別是:normalize.css 、sanitize.css 和 reset.css 。關於這三種的區別我就直接引用了。
normalize.css and sanitize.css correct browser bugs while carefully testing and documenting changes. normalize.css styles adhere to css specifications. sanitize.css styles adhere to common developer expectations and preferences. reset.css unstyles all elements. Both sanitize.css and normalize.css are maintained in sync.
快取
在利用 webpack 打包完之後,我們有些檔案幾乎不會變更,比如我這裡列舉的react
、redux
、polyfill
相關的檔案。
entry: {
react_vendor: ['react', 'react-dom', 'react-router-dom'],
redux_vendor: ['react-redux','redux', 'redux-immutable','redux-saga', 'immutable'],
polyfill: '@babel/polyfill',
app: path.join(process.cwd(),'app/app.js')
}
複製程式碼
這些不變的檔案我們就可以好好的利用下,常見(http 1.1)的就是設定 Etag
,Last-Modified
和 Cache-Control
。前面兩種屬於對比快取,還是需要和伺服器通訊一次,只有當伺服器返回 304
,瀏覽器才會去讀取快取檔案。而 Cache-Control
屬於強制快取,伺服器設定 max-age
當過了設定的時間後才會向伺服器發起請求。這裡打包再配上 chunk-hash
幾乎可以完美的配置快取。
當然還可以利用 localStorage 來做快取,這裡提出一種思路,是我以前在效仿百度首頁快取機制想的。我們可以在把js檔案版本號弄成一個配置,同時儲存在服務端和客戶端,比如:
{
"react_version": 16.4,
"redux_version": 5.0.6,
"web_version": 1.0
}
複製程式碼
客戶端將該版本號儲存在 cookie
或其他儲存引擎中,這裡推薦 localForage 來做儲存。服務端將最新版本號渲染到html檔案中,然後通過js指令碼對比版本號,如若版本號不同,則進行載入對應的js檔案,載入成功後再儲存到本地儲存中。如果相同,則直接取本地儲存檔案。
還有一種快取的場景,就是有一些api服務端更新的進度很慢,比如一天之內訪問的資料都是一樣的,這樣就可以對客戶端進行請求快取並攔截請求,從而優化速度,減小伺服器壓力。
其他
還有其他很多可以優化的地方,比如減少http請求、圖片懶載入等等,就不一一列舉了,大家可以看雅虎34條軍規:
儘量減少 HTTP 請求個數——須權衡
使用 CDN(內容分發網路)
為檔案頭指定 Expires 或 Cache-Control ,使內容具有快取性。
避免空的 src 和 href
使用 gzip 壓縮內容
把 CSS 放到頂部
把 JS 放到底部
避免使用 CSS 表示式
將 CSS 和 JS 放到外部檔案中
減少 DNS 查詢次數
精簡 CSS 和 JS
避免跳轉
剔除重複的 JS 和 CSS
配置 ETags
使 AJAX 可快取
儘早重新整理輸出緩衝
使用 GET 來完成 AJAX 請求
延遲載入
預載入
減少 DOM 元素個數
根據域名劃分頁面內容
儘量減少 iframe 的個數
避免 404
減少 Cookie 的大小
使用無 cookie 的域
減少 DOM 訪問
開發智慧事件處理程式
用 代替 @import
避免使用濾鏡
優化影像
優化 CSS Spirite
不要在 HTML 中縮放影像——須權衡
favicon.ico要小而且可快取
保持單個內容小於25K
打包元件成複合文字
寫在最後
關於優化的文章網上太多太多,這篇文章並不是告訴大家如何優化,而是在平時寫程式碼時能夠培養一種習慣、一種意識,就是做我們力所能及的優化以及要知其所以然。
文 / GoDotDotDot
LESS is MORE
編 / 熒聲
本文已由作者授權釋出,版權屬於創宇前端。歡迎註明出處轉載本文。本文連結:knownsec-fed.com/2018-09-25-…
想要訂閱更多來自知道創宇開發一線的分享,請搜尋關注我們的微信公眾號:創宇前端(KnownsecFED)。歡迎留言討論,我們會盡可能回覆。
感謝您的閱讀。