昨天分享透過 vue3-sfc-loader 載入 .vue 檔案的解決方案,讓輕前端也能輕鬆應用 SFC 元件實現氣刀體一致,但仍有美中不足之處。

首先,vue3-sfc-loader 的原理是把 Vue CLI 開發階段用的 SFC 編譯程式搬到瀏覽器端( vue3-sfc-loader.js = Webpack( @vue/compiler-sfc + @babel ) ),因此 vue3-sfc.loader.js 高達 1.37MB,以當代 JavaScript 力求輕薄的標準有點肥大。

第二,.vue 編譯動作將重複在每個瀏覽器每次載入執行一次(所以 vue3-sfc-loader 設計了簡單的 compiledCache 機制),不利網頁效能。

在我的想法裡,開發測試階段以簡單方便優先,上線時則需追求最佳化,才是良好的系統架構。於是我開始研究,有沒有不需要建立 Vue 專案,將單一 .vue 打包成 .js 的做法。

前後花了二十幾個小時爬文跟反覆實驗,大半個勞動節連假跟它拼到底,從中午搞到凌晨,睡沒幾小時想到點子又跳下床開始測試,用了點 Hakcing 手段,終於試出滿意的解法,興奮到尖叫。(好久沒有這麼熱血了)

身為前端菜鳥對 node.js 所知有限,研究到 Vue CLI 內部運作根本在「越級打怪」。幸好,靠著累積多年的實戰經驗及 Debug 技巧,終究還是讓我破解難題。

經過一番搜索,我先找到一個看似完美的 vue-cli-service build --target lib 指令。

以全域方式裝好 @vue/cli-service,只需要 vue-cli-service build --target lib '.\timer.vue',Vue CLI 會將 .vue 編譯成 .js 及 .css,還有一個 demo.html,展示在網頁載入 vue.js、timer.umd.js 及 timer.css,並建立 Vue Model 使用元件,這是標準的輕前端做法沒錯,好感動呀。(安裝及執行步驟可參考這篇)

dmeo.html 還可直接執行測試,驗證包出來的 js 跟 css 沒問題,很酷吧!

只是,我馬上發現問題,雖然元件可以動,但 demo.html 載入的是 //unpkg.com/vue@2,不是 Vue3。另一個線索是,必須要安裝 Vue2 專用的 vue-template-compiler 才能產生 .js,所以元件是被編譯成 Vue2 版本。

vue-cli-service 沒提供參數指定 Vue2 或 Vue3,我只能追進原始碼。結果讓我憂喜參半,好消息是 vue-cli-service 可同時支援 Vue2 跟 Vue3,壞消息是採用 Vue2 或 Vue3 是依專案環境自動判斷,單靠一個 .vue 檔無法判別。

再追到上游,cli-service 的 Vue 版本判斷由 @vue\cli-service\lib\util\getVueMajor.js 這段程式決定:

 * Get the major Vue version that the user project uses
 * @param {string} cwd the user project root
 * @returns {2|3}
 */
module.exports = function getVueMajor (cwd) {
  const vue = loadModule('vue', cwd)
  // TODO: make Vue 3 the default version
  const vueMajor = vue ? semver.major(vue.version) : 2
  return vueMajor
}

當只有單一 .vue 檔沒有專案相關檔案,取不到 vue 模組時將預設為 2。(有 TODO 註解,所以未來版本預設值將改為 3)

vue create hello 建立一個 Vue3 專案,把 .vue 擺進去,再執行 vue-cli-service build --target lib,確實就能判斷為 Vue3,但會變成找不到 vue/compiler-sfc 模組錯誤。

感覺問題出在 Vue CLI 版本更新速度偏慢,對 Vue3 元件編譯的支援還沒到位,故用起來種種不順,不像 Vue2 般行雲流水,理論再等一陣子就會改善。

如果你跟我一樣任性「不管,拎杯現在就要」,可參考以下我找到的 Workaround 解法:(註:包含非正規手法,請自行斟酌)

  1. 安裝 @vue/cli-service、@vue/compiler-sfc
    npm i -g @vue/cli-service
    npm i -g @vue/compiler-sfc
    
  2. 找到 C:\Users\使用者名稱\AppData\Roaming\npm\node_modules\@vue\cli-service\lib\util\getVueMajor.js,第 11 行原本為 2,改為 3,把 TODO 變成 JUST DO IT (假設該環境以 Vue3 為主)
      const vueMajor = vue ? semver.major(vue.version) : 3
    
  3. vue crate dummy 新建一個 Vue3 專案,找到其中的 node_modules\vue 資料夾

    將其複製到 C:\Users\使用者名稱\AppData\Roaming\npm\node_modules 目錄下

經過這番魔改,就能用 Vue CLI 將 .vue 快速轉成 Vue3 版 js + css 了,開心!!

[2022-05-02 更新] 讀者 Shu Huan Huang 分享一招,在 .vue 所在目錄執行 npm i vue 產生 node_modules 目錄讓 vue-cli-service 將 .vue 視為 Vue3 版,取代第 2 步修改 getVueMajor.js 的動作,而第 3 步建立 Vue3 專案動作也可用 npm i vue 取代,一樣可得到 node_modules\vue。

There is a issue of Vue3 SFC library building on Vue CLI, this article provide a workaround.


Comments

# by Arthur Chen

我用這種方式打包出來的元件無法使用 props and emit, 只好用ref 存取元件內部function 取代 props. 用mitt 取代 emit.

# by Guest

You cannot use this method to generate .js under latest Vue 3 cli.

# by 小黑

過度熱血

Post a comment