鐵人賽之後,我實在對於沒辦法用 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 設定全部都印出來給你,大概會有多長,嗯,非常的長。

所以說,我們在針對載入方式的部分,其實需要認知的部分有:

  1. 你必須要把 runtimeChunk 拿出來,不然 Vue 會 不一樣
  2. 當我們在打包時, entry 需要指定你想要一起包進來的應用程式。
  3. 最終在做 Chunks 的時候,必須要以 手動排序 的方式設定。

所以,我們主要需要更動的地方有兩個,

  1. configureWebpack 需要針對 runtimeChunk 做設定。
  2. 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 的資料夾,你可以改成你自己的。


前端動態載入先告一段落

然後我們找時間來聊後端的(欸)。