跟著Vue-cli來'學'並'改'Webpack之 打包優化

gold_gold發表於2017-12-25

首先,我們要知道什麼要用webpack來打包,這樣打包有那些好處。我們可以簡單的列出以下幾點:

  • 單檔案元件 (.vue檔案)
  • 優化Vue構建過程 (alias等)
  • 瀏覽器快取管理
  • 程式碼分離 (懶載入等)

這篇文章的重點講的就是webpack打包之優化瀏覽器快取管理,vue-cli生成的腳手架的配置中,已經做了很多對於打包,利用快取的優化處理,本文將來學校其中知識,並且做出改動。

瞭解瀏覽器的快取原理

在此之前呢,我們需要先了解瀏覽器快取是怎麼工作的,先抄了一張圖。

跟著Vue-cli來'學'並'改'Webpack之 打包優化

  • 瀏覽器: 我需要 test.js
  • 伺服器:找到了給你,並且在259200秒(一個月)內別來找我
  • 瀏覽器:好的,那我快取到磁碟裡

過了一個星期,再次訪問這個頁面

跟著Vue-cli來'學'並'改'Webpack之 打包優化

  • 瀏覽器:我需要test.js,快取期限還在,直接從磁碟讀取
  • 伺服器:沒我卵事
  • 使用者:哇塞開啟頁面好快

應產品經理需求更改了一個圖示

跟著Vue-cli來'學'並'改'Webpack之 打包優化

  • 瀏覽器:我需要test.js,快取期限還在,直接從磁碟讀取
  • 產品經理:釋出了嗎?你確定?怎麼沒效果啊?
  • 伺服器:吃瓜

弄清了原理,我們就知道怎麼去破壞快取機制,讓瀏覽器請求到新的檔案。

清楚快取技術

ctrl+F5 強制重新整理頁面

手動強制重新整理頁面,但是使用者不是程式設計師啊,他們怎麼會知道需要強制重新整理呢,所以這個方案給使用者肯定是不可行的。

更改檔案

  1. 修改檔案的名字:test.js -> test.v2.js
  2. 修改檔案的路徑:/static/test.js -> /static/v2/test.js
  3. 加 query string : test.js -> test.js?v=qwer

我們瞭解完了如何清楚快取,再來看看Vue-cli模版中是如何進行配置的

Code Splitting(程式碼分割)

什麼是程式碼分割

我們直接生成一個Vue-cli的新專案,安裝依賴後直接執行 npm run build命令,並開啟/dist/js檔案目錄

跟著Vue-cli來'學'並'改'Webpack之 打包優化
發現有3個js檔案,這就是webpack將程式碼進行了分割。

為什麼要進行程式碼分割

  1. 分離業務程式碼和第三方庫( vendor )
  2. 按需載入(利用 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')
    }
  ]
})
複製程式碼

跟著Vue-cli來'學'並'改'Webpack之 打包優化

很明顯的看到,打包後有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 可以檢視打包分佈圖

跟著Vue-cli來'學'並'改'Webpack之 打包優化
我們發現最大的檔案還是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能來帶不一樣的體驗。

相關文章