考慮到一些第三方依賴庫,在短時間內不會發生頻繁變更,不必每次都打包,只須要經過js引入便可。javascript
eject暴露webpack配置以後,module.exports下原本是沒有externals配置項的,須要手動新增該配置項。php
webpack.config.prod.js
css
externals: {
jquery: 'jQuery',
axios: 'axios',
moment: 'moment'
}複製代碼
該配置是將代碼中的一部分第三方依賴庫,如jquery、axios、moment不進行打包,而是手動在html中引入相應的cdn js,以下:html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Gearbest Influencer Program</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_975949_zge2u562xp.css">
<script src="//at.alicdn.com/t/font_975949_zge2u562xp.js"></script>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="main"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>複製代碼
其中前端
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script>複製代碼
這三行代碼,分別引入了jquery、axios、moment三個cdn js。像這些第三方依賴庫的cdn js的連接地址,能夠從www.bootcdn.cn/上獲取。可是,也不是全部第三方依賴庫能夠採用cdn js的方式引入成功的,須要經過實踐去驗證到底有哪些第三方依賴庫是能夠經過cdn js的方式引入的。java
未採用externals配置的打包圖(原始)node
採用externals配置的打包圖react
從圖中很明顯地發現,引入externals配置打包後,jquery沒有被打包進去,且main包大小從902.68KB減少到822.95KB,瘦身幅度達到8.83%
。jquery
考慮到一些第三方依賴庫,在短時間內不會發生頻繁變更,不必每次都打包,能夠經過dll(相似於C/C++語言中的動態連接庫的概念)的方式單獨引入。該方式爲externals的另外一種實現方式。
webpack
在目錄config下新建webpack.dll.config.js文件,具體內容以下:
// webpack_dll.config.js
const paths = require('./paths');
const DllPlugin = require('webpack/lib/DllPlugin');
module.exports = {
entry: {
// 如下基本是全量dll優化的第三方包
// react: ['react', 'react-dom', 'react-redux', 'redux-saga', 'react-router', 'video-react', 'jquery', 'axios', 'bizcharts'],
react: ['react', 'react-dom', 'redux-saga', 'video-react'],
// polyfill: ['core-js/fn/promise', 'whatwg-fetch']
},
output: {
filename: '[name].dll.js',// 該配置上面entry配置項key爲react,那麼生成filename就是react.dll.js
path: paths.appPublic,// 生成dll文件的目標路徑,本項目保存在public目錄下
library: '_dll_[name]',// dll的全局變量名
},
plugins: [
new DllPlugin({
name: '_dll_[name]', // dll的全局變量名
path: paths.appPublic + '/[name].manifest.json',// 描述生成的manifest文件,一樣保存在public目錄下
})
]
};複製代碼
以上配置能夠將生成的dll文件(react.dll.js)和對應的manifest.json文件(react.manifest.json)保存到public目錄下,這樣在對整個項目build時能夠將該文件直接打包到html文件中。
在package.json中新增dll命令
"scripts": {
"start": "set PORT=3001 && node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom",
"lint": "eslint --ext .jsx --ext .js src --fix",
"dll": "webpack --config config/webpack.dll.config.js"// dll打包命令,--config後的參數爲dll配置文件的路徑
}複製代碼
在控制檯輸入yarn dll,生成dll文件(react.dll.js)
將dll文件(react.dll.js)手動引入到對應html文件body標籤末尾
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Gearbest Influencer Program</title>
<link rel="stylesheet" href="//at.alicdn.com/t/font_975949_zge2u562xp.css">
<script src="//at.alicdn.com/t/font_975949_zge2u562xp.js"></script>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script src="%PUBLIC_URL%/react.dll.js"></script>
</body>
</html>複製代碼
其中,PUBLIC_URL即public目錄。
yarn build以後,打包後的文件目錄結構以下:
build
├─ asset-manifest.json
├─ favicon.ico
├─ index.html
├─ login.html
├─ main.html
├─ manifest.json
├─ react.dll.js
├─ react.manifest.json
├─ register.html
├─ service-worker.js
└─ static
├─ css
│ ├─ index.7dc833cb.css
│ ├─ index.7dc833cb.css.map
│ ├─ login.2ff64c8e.css
│ ├─ login.2ff64c8e.css.map
│ ├─ main.c8e56e47.css
│ ├─ main.c8e56e47.css.map
│ ├─ register.e54b83db.css
│ └─ register.e54b83db.css.map
├─ js
│ ├─ index.e6968086.js
│ ├─ index.e6968086.js.map
│ ├─ login.67bdfb3a.js
│ ├─ login.67bdfb3a.js.map
│ ├─ main.4a5c410c.js
│ ├─ main.4a5c410c.js.map
│ ├─ register.5d9dd95a.js
│ └─ register.5d9dd95a.js.map
└─ media
├─ GB_bg.c4c2913b.png
├─ iconfont.26b4e298.eot
├─ iconfont.3e355800.svg
├─ iconfont.5cd1f541.ttf
├─ img07.c2e13bab.png
├─ img08.22d05748.png
└─ login_bg.165cc596.jpg複製代碼
能夠發現,打包後的build文件夾中已經有了react.dll.js和react.manifest.json文件,這兩個文件是沒有通過打包,直接從public文件夾中copy過來的。
採用dll配置的打包圖
從打包圖中,能夠明顯地看出來原先兩個第三方依賴庫react-dom和video-react,並無打包進來,且index包大小從460.75KB減少到408.67KB,瘦身幅度達到11.3%
。
採用externals和dll進行webpack優化的初衷都是提取一些公共的第三方依賴包,單獨由cdn引入或以動態連接庫的方式引入項目中來,使其不用每次都被打包進主包(build/dist文件夾)。可是,仍然須要手動在html文件中引入,在頁面加載渲染的時候不只要下載主css、主js,還要下載對應的cdn js或對應的dll js,這無疑可能會使得頁面的白屏時間更長。
能夠發現,上述兩種方法均是從客戶端(前端第三方依賴庫的分離)的角度出發對頁面首屏性能優化的嘗試,不一樣的是,gzip(以及接下來要介紹的cdn方法)是從服務端的角度出發對頁面首屏進行優化的一種方案。gzip的重點在於對nginx進行配置優化,如下將以本人的nginx爲例對該方案進行詳細介紹。
nginx.conf
個人前端項目nginx配置文件node_react.conf在/etc/nginx/vhost目錄下,能夠看到,咱們經過include的方式引入個人前端項目nginx配置文件,這種寫法更加便於管理和維護,可以下降配置污染。進入該目錄。
該目錄下,主要關注node_react.conf和gzip.conf,其中前者爲個人前端項目nginx配置文件,後者爲gzip配置文件。
node_react.conf
進一步查看gzip配置文件
gzip.conf
#開啓gzip
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
#針對不一樣格式文件過濾進行gzip緩存
location ~* ^.+\.(ico|gif|jpg|jpeg|png|webp)$ {
access_log off;
expires 30d;
}
location ~* ^.+\.(css|js|txt|xml|swf|wav)$ {
access_log off;
expires 24h;
}
location ~* ^.+\.(html|htm)$ {
expires 1h;
}
#開啓gzip複製代碼
打開customize control DevTools,切換到Network,找到Content-Encoding列。
若是找不到Content-Encoding列,可按照下圖中的方法操做
下圖爲「未開啓gzip」的DevTools截圖
下圖爲「開啓gzip」的DevTools截圖
從圖中能夠看出,在DevTools->Network中Content-Encoding列顯示gzip字樣,便可肯定服務端開啓了gzip。
也許此時讀者會有疑問啦!咱們能夠經過什麼手段準確地獲得頁面首屏時間和頁面白屏時間的數據呢?怎麼判斷何時是頁面首屏加載結束的時間點呢?其實剛開始我也有這些疑問,後來通過查閱資料和對谷歌瀏覽器customize control DevTools的一番研究以後,得出了一種簡單可行的方法推薦給你們。
注意記錄頁面首屏時間和頁面白屏時間前必定要清緩存
),點擊F12,進入customize control DevTools界面,切換Performance面盤,點擊已提早保存的書籤打開網頁或輸入網址,同時點擊DevTools界面左上方的灰色圓形按鈕,隨即灰色原型按鈕變爲紅色閃亮,開始記錄頁面加載時間,此時網頁頁面title處會出現loading一直轉圈。到此,一波頁面首(白)屏時間之旅正式宣告結束,不知道您學會沒呢?固然,咱們也能夠結合特定工具對咱們的網頁進行性能分析,如下推薦給你們一些谷歌插件:
爲了節省篇幅,在此就不對各個插件進行詳細介紹了,你們能夠自行研究。
通過測試,咱們發現開啓gzip後,在客戶端請求服務器前端靜態資源(css、js、圖片)時能夠對其進行高質量壓縮,大大下降帶寬壓力,從而能夠大幅度減少頁面白屏時間。如下將從一組數據對該結論進行驗證。在給出測試數據和圖表以前,須要對下文說起的方案一、方案二、方案三、方案四、方案五、方案六、方案7-一、方案7-二、方案8進行簡要描述。
方案1:原始webpack(eject後,未作任何處理)+未開啓gzip
方案2:原始webpack(eject後,未作任何處理)+開啓gzip
方案3:原始webpack(eject後,未作任何處理)+未開啓gzip+對GB_bg.png/img07.png/img08.png進行cdn地址替換+【對打包後的index.js進行cdn地址替換】
方案4:原始webpack(eject後,未作任何處理)+開啓gzip+對GB_bg.png/img07.png/img08.png進行cdn地址替換+【對打包後的index.js進行cdn地址替換】
方案5:原始webpack(eject後,未作任何處理)+開啓gzip+對GB_bg.png/img07.png/img08.png進行cdn地址替換+【不對打包後的index.js進行cdn地址替換】
方案6:原始webpack(eject後,未作任何處理)+開啓gzip+img07/img08圖片懶加載
方案7-1:原始webpack(eject後,未作任何處理)+開啓gzip+img07/img08圖片懶加載+GB_bg.png->GB_bg.jpg
方案7-2:原始webpack(eject後,未作任何處理)+開啓gzip+img07/img08圖片懶加載+GB_bg.png->GB_bg.webp
方案8:原始webpack(eject後,未作任何處理)+開啓gzip+【不對打包後的index.js進行cdn地址替換】+img07/img08圖片懶加載+GB_bg.png->GB_bg.webp後替換cdn地址
未開啓gzip
開啓gzip
以上這組數據對個人前端項目中的兩張圖片和主css文件進行了gzip性能對比測試,設備爲本人筆記本電腦,網絡爲住所WIFI。
從數據中能夠看出,開啓gzip較之未開啓gzip,優化之處在於大幅度下降了下載靜態資源的時間。其中,index.css的下載時間降低了87.88%
,index.js的下載時間降低了59.41%
,並且index.css的文件大小由175KB減少到33KB,瘦身了81.14%
,index.js的文件大小由1536KB減少到525KB,瘦身了65.82%
。
進一步地,咱們只關注本組數據的頁面首屏時間和頁面白屏時間,能夠看到前者的指標數據從25.208秒降低到了18.253秒,降低幅度達到了27.59%
,後者的指標數據從14.454秒降低到了6.02秒,降低幅度達到了58.35%
,特別是頁面白屏時間的降低幅度已是一個很不錯的成績了。
咱們已經知道,服務端gzip方案確實可以大幅下降頁面白屏時間,大大優化首屏性能,那麼爲何能夠達到這樣的性能優化效果呢?在回答這個問題以前,咱們首先思考一下形成前端頁面首屏加載慢的緣由究竟是什麼呢?仔細琢磨一下,瀏覽器加載首屏以前,首先會去服務器下載主css、主js文件,這二者之中任何一個文件下載速度慢了都會致使頁面阻塞,形成頁面首屏加載慢。而服務端gzip恰好能夠解決這個問題,它採用高效的壓縮算法(應該也有一部分緩存做用,這部分暫不探究),可以大幅度下降服務端靜態資源到達客戶端的速度(或體量),起到加速(或瘦身)器的做用。
CDN的全稱是Content Delivery Network,即 內容分發網絡。CDN是構建在現有網絡基礎之上的智能虛擬網絡,依靠部署在各地的邊緣服務器,經過中心平臺的負載均衡、內容分發、調度等功能模塊,使用戶就近獲取所需內容,下降網絡擁塞,提升用戶訪問響應速度和命中率。CDN的關鍵技術主要有內容存儲和分發技術。---引自百度
CDN應用於前端領域,主要是能夠起到加速下載靜態資源的過程,從而達到首屏優化的效果。通常地,在CDN的加持下,靜態資源的下載過程將被大幅度加速,從而達到優化首屏性能的目的。結合實際狀況,咱們能夠選擇對整個前端項目進行全局CDN加速,也能夠對部分js文件或圖片進行局部CDN加速。
如下將給出一組測試數據,測試設備爲本人筆記本電腦,測試網絡爲本人住所WIFI。
未開啓gzip+cdn(保留異常數據)
未開啓gzip+cdn(去除異常數據)
從以上測試數據能夠看出,cdn方案雖然可以達到較理想的性能優化效果(去除異常數據後,相較於方案1,頁面首屏時間下降了50.02%
,頁面白屏時間下降了61.94%
),可是穩定性不足,12次測試就有兩次數據異常,異常率高達16.67%
,甚至有一次3分鐘還未加載出首屏。
爲了達到最大化的性能優化效果,咱們能夠將gzip與cdn結合運用到咱們的前端項目中,如下給出一組這種結合的測試數據。
開啓gzip+cdn(保留異常數據)
開啓gzip+cdn(去除異常數據)
該方案雖然能夠達到較大的優化結果,可是穩定性仍然有待提升(異常率高達23.08%
),這取決於cdn網絡的穩定性,若是cdn網絡的穩定性達到理想程度,首屏優化效果至少還能夠再提高50%以上。此處爲了折中穩定性和優化性能,給出了方案5,即「原始webpack(eject後,未作任何處理)+開啓gzip+對GB_bg.png/img07.png/img08.png進行cdn地址替換+【不對打包後的index.js進行cdn地址替換】」的局部cdn方案,如下爲該方案的測試數據。
開啓gzip+局部cdn
綜合上述五種方案,給出第一輪性能對比。
從柱狀圖中能夠看出,方案4雖然性能提高最大,可是較不穩定(取決於cdn網絡的穩定性),方案5的性能提高幅度僅次於方案4,且較穩定,很好地折中了穩定性和性能提高兩方面,對於cdn網絡待提高的現實應用場景下,不失爲一個理想的解決方案。
若是首屏有些圖片不須要在可視區當即展現,咱們能夠採用圖片懶加載的方式減小首屏加載的時間。在本項目中,有兩張圖片img07和img08不須要在首屏可視區中當即展現,能夠對其進行懶加載,即鼠標滑動進入圖片所在區域才加載。參考延遲加載圖像和視頻,對本項目兩張圖片img0七、img08進行懶加載,並對首屏性能進行了一組測試,數據以下:
從數據中能夠看出,該方案相較於方案2,頁面首屏時間減小1.033秒,降低幅度達5.66%
,這段減小的時間即爲圖片懶加載省下來的時間。
經過將圖片轉化爲編碼性能更高、體積更小的格式,也可以有效減小首屏加載時間,如PNG格式轉化爲jpeg格式或webP格式後,文件將變得更小。咱們經過一些手段將PNG格式圖片轉化爲jpeg格式或webP格式,這裏我推薦一款谷歌擴展工具Convertio,它能夠幫助咱們完成多種圖片格式的轉換,很是方便。利用該工具,我特意將本項目中首屏頁面中的一張最大的背景圖GB_bg.png轉換爲了jpeg格式和webP格式,以下圖
從圖中能夠看出,相比於png格式的原圖,jpeg格式轉換圖的壓縮率達到了39.22%
,webP格式轉換圖的壓縮率更是達到了驚人的88.12%
。
進一步地,咱們在對img07和img08採起懶加載處理的基礎上,將jpeg轉換圖和webP轉換圖分別應用於項目中,獲得了一組測試數據。
jpeg轉換圖
webP轉換圖
能夠看出,將轉換後的jpeg格式圖片和webP格式圖片應用到項目中,與方案2相比,相同內容的圖片下載時間分別減小了49.12%
和92.65%
,最終表如今頁面首屏時間上的提高則是14.1%
和42.5%
,這也有力地證明了將圖片轉換成編碼性能更高、體積更小的格式,可以有效減小首屏加載時間的結論。
咱們不妨繼續折騰一下,將上述轉換的webP圖片用cdn地址替換,對頁面首屏性能進行了一組測試。
很不幸,咱們看到,將轉換的webP格式圖片用cdn地址替換後,頁面首屏時間並無降低,而是有了小幅上升,可見不是全部的資源都適合cdn。仔細分析一波,轉換的webP格式圖片只有84.8KB,直接從本地服務器下載的速度可能並不比從cdn服務器下載的速度慢,這才致使了頁面首屏時間不降反升的現象。
好了,說了這麼多,給出了這麼多方案,爲了可以更加宏觀地分析方案的總體性能,咱們對以上八種方案進行了終極性能對比。
從終極性能對比圖中,咱們能夠看到方案4的頁面首屏性能提高最爲明顯,平均只須要9.656秒便可完成首屏加載,可是該方案的穩定性不足,可是若是cdn網絡足夠穩定的話,頁面首屏性能仍有較大的提高空間。方案7-2的頁面首屏性能提高僅次於方案4,且具備較強的穩定性,該方案在cdn網絡性能受限的應用場景下具有較強的競爭力。固然,具體選擇什麼方案,須要針對應用場景和現狀具體問題具體分析,仁者見仁智者見智,永遠沒有最好的方案,只有更好的方案。