工做中一直在作一款公司內部的BI工具,將數據可視化的報表賦能給業務人員,報表配置者經過簡單的拖拽操做便可生成報表。隨着系統不斷的完善,加上運維推廣,咱們積累了愈來愈多的用戶。這時候用戶體驗的方方面面都體現出來了。咱們也停下產品的功能迭代,將整個系統進行優化,旨在提高用戶體驗。如下是我對前端項目的優化總結。javascript
項目中在使用的 Webpack
版本是3.x,本次優化的方案仍然是基於Webpack3.x版本的 Vue
腳手架進行優化。升級4.x在計劃中。。。css
以前也總結過一次 Webpack 2.x 在Vue2.x項目中的應用,提到過 Webpack
工程的一些優化方案,如下算是一個補充。html
嘗試了下開啓gzip,直接受益仍是比較大的。下面是實際項目中打包結果。前端
Parsed
的js,1.38MGizpped
的js - 421.46K經過數據分析,減小了**70.28%**的打包體積。vue
開啓方式,在腳手架中修改配置文件:/config/index.js
java
// 生產模式 build: { productionGzip: true // 開啓Gzip壓縮 } 複製代碼
同時服務端 nginx
加入配置項webpack
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 6;
gzip_types application/javascript text/plain application/x-javascript text/css application/xml text/javascript application/json;
gzip_vary on;
複製代碼
重啓 nginx
後刷新頁面,在Chrome develop tools
中 Network
查看網絡連接nginx
Request Headers
中出現 Accept-Encoding: gzip
表明客戶端可以理解 gzip
壓縮編碼方式web
Response Headers
中出現 Content-Encoding
表明服務端指明以 gzip
編碼方式對數據進行壓縮算法
這一對請求頭部關鍵字搭配出現,說明配置成功。
preload
與 prefetch
<link rel="preload">
是一種 resource hint,用來指定頁面加載後很快會被用到的資源,因此在頁面加載的過程當中,咱們但願在瀏覽器開始主體渲染以前儘早 preload。
<link rel="prefetch">
是一種 resource hint,用來告訴瀏覽器在頁面加載完成後,利用空閒時間提早獲取用戶將來可能會訪問的內容。
preload-webpack-plugin
是 html-webpack-plugin
插件的一個擴展,因此須要搭配使用。
例如配置 preload
:
plugins: [ new HtmlWebpackPlugin(), new PreloadWebpackPlugin({ rel: 'preload', as(entry) { if (/\.css$/.test(entry)) return 'style'; if (/\.woff$/.test(entry)) return 'font'; if (/\.png$/.test(entry)) return 'image'; return 'script'; }, include: ['app'] }) ] 複製代碼
最終在html注入爲:
<link rel="preload" as="script" href="app.31132ae6680e598f8879.js"> 複製代碼
prefetch
配合 Vue 中的路由懶加載代碼分割更好用
由於本項目可視化工具中沒有使用路由,沒有配置prefetch
。
目前項目中比較經常使用的工具類庫有 lodash、moment、element-ui,對於這些常用的類庫能夠經過 Dllplugin 分離依賴成一個靜態資源庫。通常不會去改動這個依賴包版本。
不過像lodash、moment是有其餘方法來減小打包體積的。
按需加載 element-ui
,見官方文檔
按需加載 lodash
通常咱們使用 lodash 時,不會用到其中全部的函數。有可能用到了幾個,這時候能夠選擇按需引入 lodash,不要引入全量。下面經過安裝兩個插件:
npm i babel-plugin-lodash lodash-webpack-plugin -D
複製代碼
配置 .babelrc
文件
"plugins": [ "lodash" ] 複製代碼
dayjs
代替 moment
,API基本同樣,使用後會發現大部分場景都能使用,並且打包只有 7KB 。可視化工具中組件變得愈來愈豐富,隨之帶來的頁面請求數據接口也逐漸變多,開銷在逐漸增大。單個頁面數據接口請求幾十上百不等。
若是繼續使用HTTP1.x,你們都懂的,HTTP1.x協議的侷限性,大多數現代瀏覽器都支持同時一個主機最大請求數量爲6個,也就是說,若是這6個接口請求沒有返回結果處於pending
狀態的話,頁面就一直刷不出數據,這樣給用戶的體驗是不好的。HTTP2的多路複用解決了這個問題,咱們經過將服務器升級爲 HTTP2
增大了瀏覽器請求鏈接吞吐量,大大提高了應用的性能。
HTTP2.0 可讓咱們的應用更快、更簡單、更健壯 --- 《Web性能權威指南》
HTTP 2.0 的目的就是經過支持請求與響應的多路複用來減小延遲,經過壓縮 HTTP 首部字段將協議開銷降至最低,同時增長對請求優先級和服務器端推送的支持。
HTTP 2.0 性能加強的核心,全在於新增的二進制分幀層
,它定義瞭如何封裝 HTTP 消息並在客戶端與服務器之間傳輸。
HTTP 2.0 把 HTTP 協議通訊的基本單位縮小爲一個一個的幀,這些幀對應着邏輯流中的消息。相應地,不少流能夠並行地在同一個 TCP 鏈接上交換消息。
HTTP 2.0 的二進制分幀
機制解決了 HTTP 1.x 中存在的隊首阻塞問題, 也消除了並行處理和發送請求及響應時對多個鏈接的依賴。結果,就是應用速度更快、開發更簡單、部署成本更低。
域名分區
在 HTTP 2.0 之下屬於反模式,由於多個鏈接會抵消新協議中首部壓縮和請求優先級的效用緩存應用資源,避免每次請求都發送相同的內容。瀏覽器在下載靜態資源後,使用緩存將下載過的資源維護好,這樣下次加載網頁時直接使用本地的副本。減小了資源請求以及等待時間。
通用的HTTP請求頭首部字段,只需指定一個明確的緩存時間便可。能夠配置在 nginx
配置文件裏。
location ~ .*\.(js|css|ttf|svg|ico){ add_header Cache-Control max-age=86400; } 複製代碼
頁面第一次加載
再次加載
緩存驗證
能夠看到加入緩存後,Status Code
爲 200 OK (from memory cache),緩存時間爲:max-age=86400
業務場景中,隨着應用變得愈來愈複雜,加載一個頁面可能須要渲染過多的組件,渲染多個組件有兩種策略:
經過實踐發現,後者渲染更快,後者消除了每次請求接口以後渲染組件的時間,由於屢次渲染組件會帶來額外的Scripting
開銷,好比Vue中的 computed
或 watch
;同時結合 HTTP2 的多路複用,請求多個接口也會很快的響應。
示例代碼:
// 批量更新組件方法 batchUpdateComponent({ dispatch }, promises) { // 請求全部接口 return Promise.all(promises.map(p => p.catch(() => undefined))) .catch(err => { console.log(err) }) .then(res => { // 一次性渲染組件 res && dispatch('updateComponent', res) }) } 複製代碼
💡 若是 Promise 的 catch 回調返回了 undefined,那麼 Promise 的失敗就會被當作成功來處理。 使用
ES2018
的提案Promise.finally
項目中應用業務代碼量在不斷攀升,寫了不少業務組件,其實在必定場景下,並不是全部組件都須要渲染,好比,可視化工具備編輯模式和預覽模式。編輯模式須要使用 Code Mirror
用來編寫一些 SQL
語句,預覽模式時候就不須要使用。
組件正常引入:
import CustomSql from '@/components/CustomSql' export default { components: { CustomSql } } 複製代碼
組件異步引入:
// ES6 結合 Webpack export default { components: { CustomSql: () => import('./CustomSql') } } 複製代碼
Vue中路由懶加載就是使用異步組件和
Webpack
的代碼分割功能實現的。
隨着項目中組件的增多,組件的icon隨之也變的多了。大部分icon是svg格式,咱們可使用 SVG Sprite
技術管理SVG圖標。
所謂 SVG Sprite
相似於CSS中的Sprite
技術。將圖標整合在一塊兒,實際呈現的時候準確顯示特定圖標。
SVG Sprite
技術最佳實踐是:
symbol
元素整合圖標use
元素來使用圖標使用例子:
<svg> <!-- symbol definition NEVER draw --> <symbol id="sym01" viewBox="0 0 150 110"> <circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red"/> <circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white"/> </symbol> <!-- actual drawing by "use" element --> <use xlink:href="#sym01" x="0" y="0" width="100" height="50"/> <use xlink:href="#sym01" x="0" y="50" width="75" height="38"/> <use xlink:href="#sym01" x="0" y="100" width="50" height="25"/> </svg> 複製代碼
基於Vue
封裝的 SVG ICON 組件
// @/components/SvgIcon.vue <template> <svg :class="svgClass" aria-hidden="true" v-on="$listeners"> <use :xlink:href="iconName" /> </svg> </template> <script> export default { name: 'SvgIcon', props: { iconClass: { type: String, required: true }, className: { type: String, default: '' } }, computed: { iconName() { return `#icon-${this.iconClass}` }, svgClass() { return 'svg-icon ' + this.className } } } </script> <style scoped> .svg-icon { width: 1em; height: 1em; vertical-align: -0.15em; fill: currentColor; overflow: hidden; } </style> 複製代碼
將 src/assets/icons 下全部icon動態引入
// @/plugins/svgicon.js import Vue from 'vue' import SvgIcon from '@/components/SvgIcon' Vue.component('svg-icon', SvgIcon) const requireAll = requireContext => requireContext.keys().map(requireContext) const svgIcons = require.context('./components', false, /\.svg$/) requireAll(svgIcons) 複製代碼
咱們能夠用 svg-sprite-loader
這個插件來生成 SVG Sprite
,經過組件的方式引入 svg icon。
基於 Webpack 3.x
的配置方法以下:
// 經過 exclude/include 來區分哪些屬於svg icon,哪些屬於image { test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader', exclude: [resolve('src/assets/icons')], options: { limit: 10000, name: utils.assetsPath('img/[name].[hash:7].[ext]') } }, { test: /\.svg$/, loader: 'svg-sprite-loader', include: [resolve('src/assets/icons')], options: { symbolId: 'icon-[name]' } } 複製代碼
本次性能優化關鍵點:
Webpack方面:
網絡方面:
Vue實踐方面:
資源方面:
項目初始,因爲工期緊張,咱們急着迭代功能,目標是交付功能完備的應用,用戶量增加的時候就該停下來好好考慮考慮如何提高應用的性能了。縱使應用的功能再完備,若是用戶體驗很是差,那是否是值得反思,性能優化是一件須要持續作的事情。
我想借用一下《Web性能權威指南》裏,Ilya Grigorik 提到的:「💡咱們關心的不止是交付能用的應用,咱們目標是交付最佳性能!」 來總結性能優化的實踐,同時提醒本身,在作項目的時候儘量的提早想到性能優化的點。
《Web性能權威指南》