對於一個網站來講打開速度是一個很重要的指標,只是大部分時間內咱們的精力可能都用來對付需求了,特別是當咱們作的是一些內部的項目時,咱們經常的會忽略了這一方面的優化。其實要對一個頁面的打開速度作出一些比較常見的優化並無想象中的困難,本文將帶你作一些既不費力也不費時間的優化操做,這些操做中涉及到壓縮,緩存,preload加載關鍵資源,prefetch緩存懶加載資源與一些引用組件的建議及常見的工具庫處理。javascript
當咱們使用webpack打包並壓縮js代碼後,每每某些js(好比vendor)依然會很大,可能會達到1mb左右的大小,雖然以如今的帶寬來講若是咱們是pc端項目這也不是什麼大問題,可是這裏面明顯是存在着至關大的優化空間的,gzip就是一種形式。 在瀏覽器的請求頭裏包含着這樣一句話Accept-Encoding:gzip, deflate
,這告訴咱們瀏覽器是能夠識別gzip壓縮的,使用gzip壓縮後的文件將大大減少,不少狀況下甚至能壓縮70%。如今的服務基本上都是使用nginx作轉發的,對於咱們來講開啓gzip其實至關容易,只要配置以下的代碼就能夠了。css
server {
gzip on;
gzip_types text/xml text/css text/plain text/javascript application/javascript application/x-javascript;
}
複製代碼
上圖能看到沒開啓gzip時vendor近800k,而開啓gzip後大概只有250k。html
能夠說若是咱們連gzip都沒有開啓的話,其餘任何優化都顯得有點多餘,由於大概沒有另一種優化方式能壓縮如此高的比例。vue
除了常見的gzip壓縮外,另外一個能夠利用的優化點就是瀏覽器的緩存。我會順帶着介紹一下瀏覽器的緩存方式,可是不會過於詳細,有興趣的同窗能夠額外去找一些資料學習。java
瀏覽器分爲兩種緩存,強緩存與協商緩存(也被稱爲弱緩存),其中協商緩存不用咱們本身配置,下面咱們經過連續兩次刷新頁面來觀察一下協商緩存。 webpack
如上圖,在第一次的請求中nginx的http響應頭中攜帶了一個Last-Modified: sometime
那麼第二次的請求中瀏覽器的請求頭裏就會攜帶這個時間去對比,當nginx的時間在這個時間以前那麼就說明當前資源並無產生變化,返回的狀態碼也會對應的變成304, 當瀏覽器收到304後說明緩存的資源並無過時,瀏覽器就會去讀取已經緩存好的文件。須要注意的是雖然瀏覽器最終是調用的緩存,可是仍然存在http請求來確認該緩存是否失效,因此很明顯還有另外的一種方式讓瀏覽器能夠直接調用緩存,不須要經過http請求,這就是強緩存。強緩存在nginx中能夠經過以下代碼配置
location ~* \.(css|js)$ {
proxy_set_header Host $host;
proxy_pass http://tomcat_xxx;
expires 7d;
}
location ~* \.(jpg|jpeg|png|gif|webp)$ {
proxy_set_header Host $host;
proxy_pass http://tomcat_xxx;
expires 30d;
}
複製代碼
注意咱們這裏使用的是tomcat,你可能須要的配置與我這個並不同,可是這並不關鍵,咱們主要須要的是expires這項配置,他表示了咱們但願緩存的時間,咱們配置的js與css緩存時間爲10天,而圖片則緩存30天。一塊兒打開瀏覽器看一下效果。nginx
這裏瀏覽器響應頭中會附帶一個max-age=604800,這裏的單位是秒,換算整天就是咱們剛纔設置的7天,再次刷新瀏覽器後狀態碼依然是200可是後面多了一個from memory cache
代表此時是從內存中直接取出緩存,並無發送http請求,這對一些圖片與咱們的依賴包vendor至關有用,咱們徹底能夠給這兩個資源設定一個較大的緩存時間,這樣當用戶訪問第一次後,這些資源始終會保持在用戶的緩存中,就算咱們以後更改了不少咱們的業務代碼,只要依賴沒有更改,用戶只用加載一些小的業務代碼文件就能夠了,對於較大的vendor則依然能夠從緩存中獲取。
咱們能夠簡要的總結一下瀏覽器的緩存方式並增長一些注意的點。瀏覽器會首先檢測強緩存,若是命中則直接返回緩存文件,不會發送http請求,若是沒有命中則去檢查弱緩存,當弱緩存命中時返回304狀態碼,瀏覽器依然從緩存中獲取資源,若是弱緩存也沒有命中則返回200狀態碼從新加載服務器上的資源。git
注意點:github
max-age=0或是no-cache
,注意我這裏說的當前請求資源指的通常是你頁面的html文檔,可是對於文檔中外鏈的js與img等,不會由於刷新致使強緩存失效。不過若是你直接請求的是一個js文件,那麼刷新後這個js文件強緩存也會失效。因爲咱們的技術棧是vue,因此如下示例咱們用vue來進行演示,可是本質上不管是什麼技術棧都是同樣的。 假設咱們的項目是單頁面應用那麼首先應該優化的點就是路由的懶加載,也就是說不要一次性的將全部代碼一塊兒返回,只有切換到當前路由時咱們纔去請求當前路由對應的代碼。對於vue-cli初始化的項目來講配置十分的簡單,在router中更改一下import的方式就能夠。web
const router = new Router({
routes: [
{
path: '/',
redirect: '/a',
},
{
path: '/a',
component: () => import('../components/a/index.vue'),
},
{
path: '/b',
component: () => import('../components/b/index.vue'),
},
]
複製代碼
如今咱們就能夠根據咱們訪問的router動態的加載js文件了。可是這樣其實還有優化的空間,假設咱們如今請求路由a,加載了vendor等公共js與a自己的js,那麼在訪問a頁面的空餘時間裏爲何咱們不將b路由的js也對應的加載到瀏覽器的緩存中那,這樣當用戶切換到b路由時就能夠不用在發送http請求而是直接使用緩存中的文件就能夠了。
在這裏咱們要用到一個webpack插件,PreloadWebpackPlugin
,這個插件的做用是幫助咱們對應的生成<link rel="preload" href="xxxx">
與<link rel="prefetch" href="xxxx">
標籤,其中preload中href的資源瀏覽器會優先的進行加載,關於preload的做用mdn文檔是如此說的。
在瀏覽器的主渲染機制介入前就進行預加載。這一機制使得資源能夠更早的獲得加載並可用,且更不易阻塞頁面的初步渲染,進而提高性能。
具體相關其實就是瀏覽器的關鍵路徑的知識,這裏不詳述,能夠另找資料。
而對於prefetch的href瀏覽器會進行預加載,一樣這裏引用mdn文檔中的話對其描述
其利用瀏覽器空閒時間來下載或預取用戶在不久的未來可能訪問的文檔。網頁向瀏覽器提供一組預取提示,並在瀏覽器完成當前頁面的加載後開始靜默地拉取指定的文檔並將其存儲在緩存中。當用戶訪問其中一個預取文檔時,即可以快速的從瀏覽器緩存中獲得。
因此對於vue-cli生成的項目要作的是用preload加載vendor、manifest與app三個js而用prefetch去加載全部路由對應的文件。這樣當咱們訪問路由a時會首先下載須要的js與css,而後瀏覽器會自動的加載其餘的路由文件。此時當用戶去訪問其餘路由時就不會點擊時纔去發送請求。在webpack.prod.conf.js中加入以下代碼,注意放在new HtmlWebpackPlugin()
的下面,因爲咱們的項目中只是用js與css組成的,你能夠本身配置img與font這類資源。
new PreloadWebpackPlugin({
rel: 'prefetch',
}),
new PreloadWebpackPlugin({
rel: 'preload',
as(entry) {
if (/\.css$/.test(entry)) return 'style'
return 'script';
},
include: ['app', 'vendor', 'manifest']
})
複製代碼
不少第三方組件中讓咱們使用的方式是在main.js中引入組件,而後經過Vue.component()
來註冊全局組件,其實不少狀況下咱們不該該採用這種方案,由於這會增大vendor的體積,特別是當此組件只是在其中的一個路由中用到,放入vendor中就更是不合理的,由於緩存這個組件的代碼對咱們並無不少好處,假設頁面有5個路由,至關於進其餘4個路由時這些代碼都是沒有意義的,因此不少狀況下局部註冊組件將其打包進到路由代碼中是更好的選擇。額外提一句,若是是echarts這種巨型庫,仍是建議打包進vendor中的,由於因爲業務代碼老是在變動的,因此路由代碼的hash值老是在變化,echarts這種重量的代碼就不要每次上線都讓用戶從新加載一遍了...
關於這一點我再思考以後以爲我多是錯的,由於像引入的第三方組件咱們是做爲外部依賴來使用的,就算是體積較小,每次打包進路由對應的js裏也會由於業務代碼的變更致使用戶從新加載這部分代碼,而體積大的時候就像我上文說的更不能打包進業務代碼中。我能想到的場景只有一個狀況可能有些不同,就是當代碼上線後咱們開發新的需求,須要引入新的依賴,但咱們不但願用戶緩存的vendor失效,因此咱們能夠打包一個新的依賴包出來,但這樣就提升了http的請求數量,咱們知道在http1中咱們優化的方向是域名分散與下降請求數,這樣到底好很差見仁見智吧,反正我應該是不會這麼作的。
咱們常見的工具庫好比lodash與moment其實都至關至關的大,而每每咱們只是用幾個小功能而已,爲了用這些小功能引入這麼巨大的庫真的有些浪費,這裏提一些基礎的解決方案。
lodash:安裝lodash-webpack-plugin
babel-plugin-lodash
在.babelrc中配置"plugins": ["lodash"]
,去按需引入lodash,可是說實話,按需引入有時候也不小,若是真的用到幾個很簡單的功能本身寫未必不是一個更好的選擇。
moment: 用day.js去替換。僅2kb大小的庫,與moment同樣的api,簡直不能再贊,我沒看源碼,不知道2kb是怎麼作到與moment同樣的功能的,難道是moment實現的太笨了嗎...有點費解
還有相似echarts這種巨型的庫也有按需引入的方式,你們能夠本身試着優化本身vendor的大小