鐵人賽之後,我實在對於沒辦法用 Vue CLI 3.0 來組合出動態載入,感到有點小小挫折。不過,畢竟所謂的 vue.config.js
其實,背後也都還是 Webpack 的設定,只是被封裝起來大家看不到而已。那麼,總是會有辦法把他做出來。
對,我做出來了,終究他還是 Webpack 所以其實也沒有非常困難。
然後 Vue 出了 4.0
結果 3.0 我都還不熟,然後就出 4.0 了(眼神死)。但是呢,這個設定檔案在 3.0/4.0 的專案當中都可以使用,所以基本上不會有太大的問題。
Vue.config.js 與 Webpack
其實在 Vue CLI 的命令中,你可以在專案資料夾,執行
vue inspect
來印出設定。他會把他所用到的 Webpack 設定全部都印出來給你,大概會有多長,嗯,非常的長。
所以說,我們在針對載入方式的部分,其實需要認知的部分有:
- 你必須要把
runtimeChunk
拿出來,不然 Vue 會 不一樣 。 - 當我們在打包時,
entry
需要指定你想要一起包進來的應用程式。 - 最終在做 Chunks 的時候,必須要以 手動排序 的方式設定。
所以,我們主要需要更動的地方有兩個,
configureWebpack
需要針對runtimeChunk
做設定。chainWebpack
需要針對 html-webpack-plugin 這個外掛套件做 Chunks 手動排序。
不過你如果使用 vue inspect --plugins
的時候會發現,其實他沒有列出 html-webpack-plugin
這個外掛套件。
所以說,主要的設定就是我們剛剛提到的兩個地方。
// 前略...
configureWebpack: config => ({
entry: manualEntries,
target: 'web',
optimization: {
runtimeChunk: {
name: 'manifest'
}
}
}),
// 後略...
上述的 manualEntries
則是你需要載入的應用程式物件,舉例來說,我們原本就有的 main.js
,然後假設我們有個 App2 的話,那他可能會長這樣:
let manualEntries = {
app2: './src/app2.js',
app: './src/main.js'
};
接著是我們的 chainWebpack
的部分:
config.plugin('html').tap(args => {
args[0].chunks = [
'manifest',
'chunk-vendors',
'chunk-common',
...manualChunks
]
args[0].chunksSortMode = 'manual'
return args
})
請留意上述的 manifest
, chunk-vendors
, chunk-common
這三個請不要略過,這個是 Vue CLI 的程序打包會產生的預設檔案,名稱不要亂改,如果壞了我不負責喔(啾咪)。然後後續就是把 manualChunks
接到後面去,然後把他指定給 chunks
的設定。
manualChunks
的內容就是你的應用程式的 順序 ,就是你剛剛在 manualEntries
所定義的物件鍵值,以上面的例子來說,他會長這樣:
let manualChunks = [
'app2',
'app'
];
請注意!你的主要應用程式 app
一定要放在最後面!這是必要的順序!
最後,把 chunksSortMode
設定為 manual
這個設定,那麼,你的 Chunks 在 HTML 當中的順序,就會依照你的 Chunks 的順序而依序被注入到 index.html
裡面。
關於
chunksSortMode
可參考 HTML Webpack Plugin 的說明頁面。
魔術方法 :is
是的,這裡還是需要搭配 :is
這個魔術方法,因為我們在 Dark
這裡的載入方式,是使用全域元件註冊的方式來達成:
import Vue from "vue";
Vue.component("App2Dark", () => import("@/themes/dark/Hello.vue"));
所以說,我們在主要應用程式 App 裡面,會使用魔術方法來將元件放進去。
<template>
<section>
<component :is="dynamicComponent"></component>
</section>
</template>
mounted () {
if (typeof Vue.options.components.App2Dark !== "undefined") {
this.dynamicComponent = Vue.options.components.App2Dark;
}
}
這樣一來,我們就能將物件放進去,所以,我們剛剛所提到的 順序問題 就是在這個地方會有所影響。當你的主要應用程式比你的元件或是第二個應用程序還要 早一步 運行,那麼,畫面上就不會有東西出現了。
當然,這些 順序問題 基本上你還是可以用其他的生命週期的勾子,或是使用其他的事件驅動方式來操作,這邊就不多做說明了。
實際運作
畫面上的 Dark 就是另外一個應用程式所載入的內容,所以,基本上他們可以共用同一個 App 的主要工具與方法,例如 Vue 或 EventBus 的部分。底下是簡單的結構:
這樣一來,我們在 vue.config.js
的地方,就能先把 themes
底下的東西載入進來,然後最後由 index.html
那邊來決定要用 哪一個 應用程式或是物件。
底下是完整的 vue.config.js
給大家參考:
const fs = require('fs')
const path = require('path')
const resolve = file => path.resolve(__dirname, file)
const isProd = process.env.NODE_ENV === 'production'
let manualChunks = []
let manualEntries = {}
fs.readdirSync('./src/themes').forEach(function (theme) {
if (fs.lstatSync('./src/themes/' + theme).isDirectory()) {
let fullpath = './src/themes/' + theme + '/index.js'
if (manualChunks.indexOf(theme) === -1) {
manualChunks.push(theme)
Object.defineProperty(
manualEntries,
theme,
{
value: fullpath,
writable: false,
enumerable: true
}
)
}
}
})
// The last one should be "app" with "main.js"
manualChunks.push('app')
Object.defineProperty(
manualEntries,
'app',
{
value: './src/main.js',
writable: false,
enumerable: true
}
)
module.exports = {
publicPath: '/',
filenameHashing: false,
css: {
extract: true,
sourceMap: !isProd
},
devServer: {
headers: { 'Access-Control-Allow-Origin': '*' },
disableHostCheck: true
},
productionSourceMap: !isProd,
transpileDependencies: [],
configureWebpack: config => ({
entry: manualEntries,
target: 'web',
optimization: {
runtimeChunk: {
name: 'manifest'
}
}
}),
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src'))
.set('@assets', resolve('src/assets'))
config.plugin('html').tap(args => {
args[0].chunks = [
'manifest',
'chunk-vendors',
'chunk-common',
...manualChunks
]
args[0].chunksSortMode = 'manual'
return args
})
// config.plugins.delete('html')
config.plugins.delete('preload')
config.plugins.delete('prefetch')
}
}
以上的設定,可以使用 Vue CLI 新建立好的專案,直接放進去就可以用,不過,我所使用的是 src/themes
的資料夾,你可以改成你自己的。
前端動態載入先告一段落
然後我們找時間來聊後端的(欸)。