使用 Vue3 開發 Web Component 入門

Mike
I am Mike
Published in
10 min readOct 31, 2022

透過 vite + vue3 快速開發 Web Component 共用組件。

為什麼不直接寫原生的 Web Component ?
因為透過 vite + vue3 的方式來開發 web component ,過程會簡單很多,而且可以使用 Vue SFC 的方式來開發,程式碼看起來比較簡潔,可以達到跟原生 Web Component 一樣的效果。

有關 web component 相關介紹在請各位看一下 MDN,連結幫各位附上了。https://developer.mozilla.org/en-US/docs/Web/Web_Components

需求

假設今天我需要開發一個 component 給很多人專案使用,而這些專案可能有的是用 Vue、React、Svelte 等等,但是同一個樣式的組件卻要寫好幾個版本,很容易造成溝通及維護上的問題,所以我的想法就是把共用的組件包成 web component ,這樣一來就可以在任何專案上面來使用這個組件。

那要怎麼做呢? Vue 提供了一個 API 叫 defineCustomElement,可以透過這個 API 來使用 Vue SFC 開發 web component。https://cn.vuejs.org/guide/extras/web-components.html#vue-and-web-components

defineCustomElement 是 vue3.2 出來的新功能

  1. 首先我們先來 create 一個新的 Vite 專案,記得框架選 Vue
npm create vite@latest

2. 然後先來設定 build 的細節,這邊我們選擇 build 出去的是使用 Library Mode (庫模式)

vite.config.js

3. 然後刪除專案內的 App.vue,因為我們現在是要開發 UI 組件,所以就不需要 App.vue 了。

4. 新建立一個 .ce.vue 的 component,使用 SFC 就要這樣命名。
我們先假設今天共用的組件是我們的 header ,所以我要新增一個組件叫做 Header.ce.vue

Header.ce.vue

5. 改寫你的 main.js

main.js

這邊就不是去 createApp 註冊 Vue,而我們是使用 defineCustomElement 來將 SFC 轉譯成 web component,這邊我們有兩個導出的東西,一個是直接組件的導出,不幫你在網站上註冊這個 web component ,你可以先做完你要的事情後再單獨使用, customElements.define 註冊,或是寫一個 register 函式,讓外部直接 call 就可以註冊寫好的 web component。

在這邊我把我的組件名稱註冊為 m-header 所以等等我使用的時候就是

<m-header></m-header>

6. 執行 npm run build,將 vue 的 SFC 打包成 web component。

build 完成

vite.svg 是 public 有的,所以會一起打包過來,如果不想要可以刪掉 public 資料夾裡面的 vite.svg。

如何使用 Web Component?

好了之後我們就可以使用原本專案內的 html 搭配 vscode 的套件 Live Server 來搭配測試,在一般非框架的環境下要如何使用。

因為我們這邊是使用 ES module 的模組來導出,但我們還沒有使用其他自動化工具來編譯,所以這邊我們在 index.html 上面加上 type="module" ,可以直接使用 import。

然後我們導出 register 函式,然後註冊它,我們就可以在 html 上面使用這個 web component。

這時候打開瀏覽器的開發者工具可能會看到這個錯誤

這是因為環境變數找不到的錯誤,所以我們可以在 vite.config.js 加入環境變數的設定

在執行一次 npm run build就可以就可以解決這問題,你看我的 header 在index.html 給 render 出來。

header 被 render 出來

如何偵聽 emit 事件

畢竟現在 build 成 web component,所以像是 vue 的 v-on:emit的方式就不能在其他地方使用,所以我們要在其他專案內要接收來自 emit 的事件及 return 的資料,我們可以使用 addEventListener 來監聽這個 web component。

透過 document.querySelector(‘m-header’) 的方式取得它的實體,然後偵聽onTrigger 這個 event,當今天只要 web component 內部的觸發 emit ,外部的偵聽就會觸發回傳我需要的內容,所以我在 header.ce.vue 內,是在 click button 後觸發 emit 。

<!-- Header.ce.vue -->
<button @click="emit('onTrigger', { event: 'click' })">
click
</button>

關於使用 props

我們先對 Header.ce.vue 新增 props,我希望可以由外部來控制這個組件的顯示模式,就是現在很流行的 dark mode,所以我的 class 新增了 lightdark ,透過 props.mode 來進行切換style 。

Header.en.vue
light 模式

然後我在外部使用的時候就像這樣帶入 props,就可以成功的帶入到這個組件了。

<m-header mode="dark"></m-header>
dark 模式

如果要用 JavaScript 動態去 帶入 / 修改 props,我們可以使用 document.querySelector 去抓去 DOM 中的 m-header ,然後塞值進去

document.querySelector('m-header').mode = 'dark'

這麼一來也可以給任何框架中去使用。

設計 props 的時候最好都以原始型別為主( String、Number、Boolean) 比較好,因為如果傳入 Object 的話在 SFC 定義就會有點問題,像是下面這樣…

然後我傳入物件

<m-header obj="{name: 'mike'}"></m-header>

但是因為我們不是在 Vue 的環境中開發,所以現在這樣帶入其實是帶入字串,而不是真的物件格式

log string

我們可以看到 typeof props.obj 回傳的是一個 string

console.log(typeof props.obj);  -> string

所以這邊稍微修正一下我的程式,首先我們要改一下我們帶入的props的格式,原本的寫法是以前我們在 Vue SFC 傳物件,但是這次我們要傳入一個正確的 JSON 格式。

// Bad (X) 
<m-header obj="{name: 'mike'}"></m-header>
// Good (O)
<m-header obj='{"name": "mike"}'></m-header>
Header.en.vue

這樣的話我們就可以確保傳入的資料就可以變成我們要的物件。

特別注意

我們Vue官方文件上面會看到這個傳入物件的寫法(官方文件),但是這個傳入物件的寫法是給使用 Vite 或是 Vue-cli 開發 Vue 的時候可以使用的方式

<!-- 傳入複雜資料格式-->
<my-element :user.prop="{ name: 'Mike' }"></my-element>
<!-- 縮寫 -->
<my-element .user="{ name: 'Mike' }"></my-element>

要在 Vite 或 Vue-cli 使用的時候還需要在 config 中做一些設定

vite.config.js
vue.config.js

因為預設情況下,Vue 會將任何非原生的 HTML 標籤優先當作 Vue 組件處理,所以這會在開發時導致 Vue 噴出一個 解析組件失敗 的警告。我們可以指定 compilerOptions.isCustomElement 這個選項,讓 Vue 知道特定元素應該被視為自定義元件並跳過組件解析。

另外一種傳入複雜資料類型的方式

如果我們不透過 props,而是去定義 web component 本身的參數以及修改參數的方法,再由外部去執行,就可以達到一樣的效果。

我定義了一個 defaultData 的 ref 物件,還有一個 setPropsData 函式負責去修改 defaultData,然後外部需要透過 setPropsData 去修改 defaultData,所以我用 defineExpose 把 setPropsData 給丟出去,讓外部可以使用。

現在畫面上面可以看到我預設定義好的資料

現在我在外部可以直接調用 setPropsData 來修改這個資料

現在可以看到我們畫面上 render 我們丟進去的內容

是不是非常的方便!這樣子就可以輕易開發 web component 的組件,在各種不同環境的專案使用,Vue3.2 提供了 defineCustomElement 這個 API,大幅度簡化 Vue 開發者開發 web component 麻煩度。

基本上我目前在專案上面的開發沒有遇到什麼太大的問題,像是 composables 、一些輕量的第三方 JS 套件、非同步處理都完美運行,不過我看網路上有些人在整合一些 CSS framework 有遇到一些狀況,不過我就沒有特別去測試了,有需求的朋友在自己整合看看吧,我自己目前是整合了 UnoCSS 來搭配開發,目前沒啥問題就是了…

最後

那如果對於 JS 不熟的朋友,我也有開設 JS 跟 TS 的入門課程,目前早鳥預購中,可以參考這個連結,現在就加入 👉 https://thecodingpro.com/courses/javascript

現在輸入折扣碼:mikemedium (單堂課程折 200元 ,組合包再折 500 元)

訂閱Mike的頻道享受精彩的教學與分享

MIke Youtube : https://www.youtube.com/MikeCheng
MIke 的官方 line 帳號,好友搜尋 @mike_cheng

--

--

Mike
I am Mike

如果有一行code無法解決的bug,那就寫兩行!