在 Laravel 中搭配 Async Vue Component 使用

Laravel 中,如果是基於要和使用者互動操作的一些介面(元件),透過撰寫 Vue Component 可以很方便地在各個 Laravel Blade 中輕鬆地透過 <Component /> 的 tag 方式來重複使用 Vue Component。

目前在 Laravel 的前端打包工具大部分都是透過 laravel-mix 做整合,前陣子也才幫系統的 laravel-mix 升級到 2.0.0 版本,基本上是無痛升級;在去年的「從 laravel-mix 0.8 升級到 1.4 版的記錄與坑」這邊文章提到使用「async/await」的方式現在也不需要安裝額外的 babel-plugin 也都可以使用了,而且現在 Laravel 內你也不需要建立 .babelrc 來設定相關 babel 的設定。

這次主要記錄是怎麼在 Laravel 中設定並使用「非同步載入 的 Vue Component」Vue.js 官方文件在 Component 部分也有對於 Async Component 也有相關解釋,如果不太了解的話,可以參考一下。

簡單說明一下,在大型的 Application 我們可能會有許多的程式碼,但是我們希望不要一次把所有的程式碼都給打包起來,這樣會造成 bundle 的檔案過大,網頁在載入時會比較慢,而且並不是每個程式碼都會被頁面所需要,這時候我們可以拆分(chunk)程式碼成不同的 bundle 檔案(a.k.a Code Splitting),可以在需要的時候「動態」引入(Dynamic import)進來。

修改 Vue Component 的引入方式

在 Laravel 我們通常會在 /resource/assets/js/app.js 來引入我們的 Vue Component:

這裡的檔案路徑根據官方預設的路徑來做說明。

import FooComponent from './components/FooComponent.vue';
Vue.component('foo-component', FooComponent);

Vue.component 的第二個參數接收一個 factory callback function,當你向伺服器發出請求取得 Component 時,會從這裡的 callback function 來執行,factory function 有兩個參數,也就是 Promiseresolvereject,所以當你要載入 Component 時可以透過 resolve 或者你也可以使用 reject function 來說明載入 Component 為什麼失敗:

Vue.component('foo-component', (resolve, reject) => {/* ... */});

或者你也可以使用 ES6 的 import 語法:

Vue.component('foo-component', () => import('./components/FooComponent.vue'));

這裡為了確保可以執行,我們需要安裝額外的 babel plugin - Syntax Dynamic Import

$ yarn add babel-plugin-syntax-dynamic-import --dev

接著在 .babelrc 內設定好 plugins

{
"plugins": ["syntax-dynamic-import"]
}

如果你升級到了 laravel-mix 2.0 版本,你可以直接透過 mix.babelConfig() 來直接設定 babel 的設定,更多請參考 laravel-mix 的 Release

webpack 設定檔案的修改

我們必須要更新 webpack 設定檔的部分,你必須在 output 的地方加上 chunkFilename

mix.webpackConfig({
output: {
chunkFilename: 'js/[name].js',
}
});

執行 build 可以看到 webpack 的 bundle 後的訊息:

Bundle Result

可以看到紅框圈起來的部分,這就是被拆分出來的檔案,大小有 26.4 kB!因為並不是每個地方都會用到它,所以我只在需要的時候在動態引入進來,這樣就可以有效的減少 bundle 後的檔案大小了!

也可以加上 chunkhashjs/[name].[chunkhash].js):

Add chunkhash

你也可以加上 [chunkhash] 的選項,這個 hash 就是根據 module 內容所計算出來的 hash 值,也就是說,如果檔案內的內容沒有改變的話,這個 hash 值就不會改變,這樣在瀏覽器就可以有效的被 Cache

讓我們來測試被 chunk 出來的程式碼會被在特定的頁面會被載入:

head

你可以透過 Devtools 的 Network(網路) Panel 來查看載入網站的資訊,我們可以找到了加上 chunkhash 的 asset:0.9fe85922805f06a8cafc.js,點擊「回應(Response)」的部分:

response

可以看到這是由 webpack 所 chunk 出來的檔案內容。

請注意到黃色圈起來的部分,關於 ETag 這個 header,什麼是 ETag 呢?

ETag Header

根據 Google Web Developer Cache 文件的解釋

  • 伺服器使用 ETag HTTP Header 來傳遞一個驗證的 token
  • 你可以透過驗證 token 來有效的對進行資源更新和檢查;如果資源沒有改變,則不會傳輸任何資料

上圖的意思是:如果 client 端向伺服器發送請求時,提供了 If-None-Match Header 並夾帶 ETag token,伺服器針對目前的資源來檢查 token,如果 token 一致,代表這個資源沒有改變,伺服器將會回傳 304 Not Modified,就可以繼續使用原來的資源。

讓我們回到原來的範例:

ETag cache example

當我第一次載入網頁時,網頁回傳的 HTTP Status 是 200,當我再次載入時,請求的 If-None-Match Header 提供了一個 hash 給伺服器驗證,可以發現這次回傳的 HTTP Status 是 304,代表我的傳送的 ETag hash 和伺服器是一致的,所以可以繼續使用原來 Cache 的檔案。

後記

一開始在升級 laravel-mix 的時候蠻擔心在 build 的時候又會炸掉,這次是從 1.7 版升級到 2.0 版,還好沒有什麼太大的問題,在升級前也先確認 laravel-mix 的 Release 記錄,看看有沒有什麼 Breaking Change 會導致升級炸掉的,後來一切都蠻順利的。

在設定 webpack chunk 中,有遇到一個小問題,在 bundle 的過程中都很順利,檔案也有順利的被拆分出來,但是載入網頁時出現資源找不到的情況(無法載入拆分出來的程式碼),後來追了一下載入的路徑,發現是 webpack 的 publicPath 沒有設定好,導致找不到檔案,如果當你上述步驟都很順利,最後發現載入卻一直失敗,不妨檢查一下!

Reference