首先,我們要知道什麼要用webpack來打包,這樣打包有那些好處。我們可以簡單的列出以下幾點:
- 單檔案元件 (.vue檔案)
- 優化Vue構建過程 (alias等)
- 瀏覽器快取管理
- 程式碼分離 (懶載入等)
這篇文章的重點講的就是webpack打包之優化瀏覽器快取管理,vue-cli生成的腳手架的配置中,已經做了很多對於打包,利用快取的優化處理,本文將來學校其中知識,並且做出改動。
瞭解瀏覽器的快取原理
在此之前呢,我們需要先了解瀏覽器快取是怎麼工作的,先抄了一張圖。
- 瀏覽器: 我需要 test.js
- 伺服器:找到了給你,並且在259200秒(一個月)內別來找我
- 瀏覽器:好的,那我快取到磁碟裡
過了一個星期,再次訪問這個頁面
- 瀏覽器:我需要test.js,快取期限還在,直接從磁碟讀取
- 伺服器:沒我卵事
- 使用者:哇塞開啟頁面好快
應產品經理需求更改了一個圖示
- 瀏覽器:我需要test.js,快取期限還在,直接從磁碟讀取
- 產品經理:釋出了嗎?你確定?怎麼沒效果啊?
- 伺服器:吃瓜
弄清了原理,我們就知道怎麼去破壞快取機制,讓瀏覽器請求到新的檔案。
清楚快取技術
ctrl+F5 強制重新整理頁面
手動強制重新整理頁面,但是使用者不是程式設計師啊,他們怎麼會知道需要強制重新整理呢,所以這個方案給使用者肯定是不可行的。
更改檔案
- 修改檔案的名字:test.js -> test.v2.js
- 修改檔案的路徑:/static/test.js -> /static/v2/test.js
- 加 query string : test.js -> test.js?v=qwer
我們瞭解完了如何清楚快取,再來看看Vue-cli模版中是如何進行配置的
Code Splitting(程式碼分割)
什麼是程式碼分割
我們直接生成一個Vue-cli的新專案,安裝依賴後直接執行 npm run build命令,並開啟/dist/js檔案目錄
發現有3個js檔案,這就是webpack將程式碼進行了分割。為什麼要進行程式碼分割
- 分離業務程式碼和第三方庫( vendor )
- 按需載入(利用 import() 語法)
之所以把業務程式碼和第三方庫程式碼分離出來,是因為產品經理的需求是源源不斷的,因此業務程式碼更新頻率大,相反第三方庫程式碼更新迭代相對較慢且可以鎖版本,所以可以充分利用瀏覽器的快取來載入這些第三方庫。
而按需載入的適用場景,比如說「訪問某個路由的時候再去載入對應的元件」,使用者不一定會訪問所有的路由,所以沒必要把所有路由對應的元件都先在開始的載入完;更典型的例子是「某些使用者他們的許可權只能訪問某些頁面」,所以沒必要把他們沒許可權訪問的頁面的程式碼也載入
剖析 vue-cli 的 webpack Code Splitting
分離業務程式碼和第三方庫( vendor )
vue-cli中使用了 CommonsChunkPlugin這個webpack外掛來提取框架程式碼。 開啟 webpack.prod.conf.js 檔案,找到下面這段程式碼
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
複製程式碼
這段程式碼在打包的時候把 node_modules 下面的 ,並且名字是 .js 結尾的,並且不是重複的模組提取到vender
裡面。
所以打包後應該會生成app.js(業務程式碼)、vender.js(框架程式碼)這個兩個檔案,細心的同學可能會發現還有個 manifest.js,在後面我們講進行解讀。
按需載入(利用 import() 語法)
如果我們修改一下hello元件的載入方式改為路由懶載入(import()語法),在進行打包
// import Hello from '@/components/Hello'
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
// component: Hello,
component: () => import('@/components/Hello')
}
]
})
複製程式碼
很明顯的看到,打包後有4個js檔案,仔細的同學還發現,app.js檔案的大小加上新多出檔案的大小,正好等於沒有分割打包的app的大小。 這樣等於非同步載入的元件,是單獨打包成了一個js,在頁面首次載入的時候不需要載入他,等到請求相應的頁面的時候在去伺服器請求它,減小了頁面首屏載入的時間。
vue-cli /webpack.prod.conf.js
中配置 output.chunkFilename 規定了打包非同步檔案的格式
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
複製程式碼
如何利用利用瀏覽器快取
目標
假設我們現在有很多很多的靜態檔案,然後每次需要更新很多很多的檔案,那是不是要手動地一個一個地修改檔案的名字呢?我們的理想當然是:哪個檔案更新了,就自動地生成一個新的檔名。
另外,如果我們打包出來的靜態檔案只有一個單獨的 JavaScript 檔案 app.js ,那麼每次改動一點程式碼,app.js 的檔名肯定都會變。但實際上,我只改動了某個模組的程式碼(其他模組並沒有修改),就破壞了其他模組的快取,這顯然沒有充分利用到快取啊。我們的目標是:
哪個模組更新了破壞他的快取,沒更新的模組繼續利用快取。
步驟1:增加hash值
上文中我們提到清楚快取的三種方式:修改檔名,修改路徑,給url加引數,webpack的做法是修改檔名。
vue-cli /webpack.prod.conf.js
中 output.chunkFilename 規定了打包非同步檔案的格式
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// 規定檔名為 js資料夾下 Chunk.name . hash值 .js 的檔案
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
// 規定檔名為 js資料夾下 module id. hash值 .js 的檔案
},
複製程式碼
這樣的話給每個檔案加上了hash值,那個檔案發生了變化hash值就會改變
步驟2:提取manifast檔案
為什麼要提取manifast檔案呢?
原因是 vendor chunk 裡面包含了 webpack 的 runtime 程式碼(用來解析和載入模組之類的執行時程式碼)
這樣會導致:即使你沒有更改引入模組(vendor的模組沒有發生變動的情況下,你僅僅修改了其他程式碼) 也會導致 vendor
的chunkhash值發生變化,從而破壞了快取,達不到預期效果
vue-cli /webpack.prod.conf.js
提取 manifast
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
複製程式碼
步驟3:根據模組的相對路徑生成一個四位數的hash作為模組id
webpack 裡每個模組都有一個 module id ,module id 是該模組在模組依賴關係圖裡按順序分配的序號,如果這個 module id 發生了變化,那麼他的 chunkhash 也會發生變化。
這樣會導致:如果你引入一個新的模組,會導致 module id 整體發生改變,可能會導致所有檔案的chunkhash發生變化,這顯然不是我們想要的
這裡需要用 HashedModuleIdsPlugin ,根據模組的相對路徑生成一個四位數的hash作為模組id,這樣就算引入了新的模組,也不會影響 module id 的值,只要模組的路徑不改變的話。
vue-cli /webpack.prod.conf.js
new webpack.HashedModuleIdsPlugin()
複製程式碼
完成目標
至此如果我們改了某個模組的程式碼,是不會破壞其他模組的快取,這就是我們想要實現的永續性快取。
改造vue-cli中的webpack提升首頁載入速度
分析
我們首先來看一個實際專案
執行 npm run build --report 可以檢視打包分佈圖
我們發現最大的檔案還是vendor,大部分框架程式碼都打包在這裡面,而這些框架程式碼是不常變化的,也不需要每次進行打包。所以我們可以想辦法把他們提取出來,掛到cdn上面去。具體步驟
以 vue, vue-router,element-ui為例
步驟1 index.html cdn引入框架
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>demo-vue-project</title>
<link rel="stylesheet" href="https://cdn.bootcss.com/element-ui/2.0.8/theme-chalk/index.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.0.7/index.js"></script>
</body>
</html>
複製程式碼
步驟2 修改 build/webpack.base.conf.js
module.exports = {
...
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT'
},
...
}
複製程式碼
步驟3 修改框架註冊方式
修改 src/router/index.js
// import Vue from 'vue'
import VueRouter from 'vue-router'
// 註釋掉
// Vue.use(VueRouter)
...
複製程式碼
修改 src/main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import ELEMENT from 'element-ui'
// import 'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip = false
Vue.use(ELEMENT)
Vue.prototype.$http = axios
/* eslint-disable no-new */
new Vue({
el: '#app',
store,
template: '<App/>',
components: { App }
})
複製程式碼
打包
打包後我們發現vendor體積大大減小,因為庫程式碼都用cdn載入了。但是這樣會導致請求資源增多也有響應的代價,這僅可算是一個思路。 首屏問題的最終解決方案還是SSR
總結
至此 vue-cli中的打包配置,也有一些瞭解了。個人吐槽下webpack是真的複雜。觀望和期待 parcel能來帶不一樣的體驗。