Vue3.5からの改善と振り返るdefineCustomElementについて
Vue3.5の更新では defineCustomElement
に関する改善が含まれています。今回の記事ではその defineCustomElement
に関連する事項についてと、改善された内容を紹介します。
Custom Elementsとは?
defineCustomElement
についてを紹介する前に、まずは根底の知識となる「Custom Elements」についてを解説します。
Custom ElementsはWeb Componentsを構成する一部で、独自のHTML要素を作成するためのJavaScript APIです。
独自の定義のため、通常のHTML要素とは異なりダッシュが使われている名前( kebab-case
)である必要があります。
<!-- 純粋なHTML要素 -->
<progress></progress>
<!-- Custom Elements -->
<custom-progress></custom-progress>
Custom Elementsの利点としては、UIライブラリ・フレームワークを使用しているかに関わらずに利用できることが挙げられます。例えばフロントエンドの技術スタックが異なるアプリケーション間で共通のUIコンポーネントを使用する場合に有用です。
Web標準技術の相互運用向上プロジェクトであるInteropではWeb Componentsの改善が行われており、Custom Elementsはほぼほぼテストケースを通過しているためクロスブラウザでもある程度安定して使用できると言えます。
Custom Elementsの具体的な使用例を上げると、GitHubのリポジトリ内での更新日付部分で使用されており、開発ツールで該当部分を見ると独自のHTML要素で定義されていることがわかります[2]。
VueコンポーネントをCustom Elementsとして配布する
Vue3.2から defineCustomElement
というAPIが追加され、VueコンポーネントをCustom Elementsとして使用できるようになりました。
defineCustomElement
の使い方
使い方自体はシンプルで、コンポーネントの拡張子を .vue
から .ce.vue
に変更して defineCustomElement
で呼び出すことで、VueのコンポーネントをCustom Elementsとして使用できます。
import { defineCustomElement } from 'vue';
import Example from './Example.ce.vue';
// Custom Elementsのコンストラクタに変換
const ExampleElement = defineCustomElement(Example);
// Custom Elementsとして登録
customElements.define('vue-app-element', ExampleElement);
<!-- バンドルされたJSを読み込むと以下Custom Elementsが使用できます -->
<vue-app-element></vue-app-element>
公式のSFCツール(vue-loader@^16.5.0、@vitejs/plugin-vue@^1.4.0)では、"Custom Elements Mode"
をサポートしており、ce.vue
拡張子でなくとも customElement
オプションを追加することでCustom Elementsで呼び出せます。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue({
customElement: true, // Custom Elements Mode
})],
})
defineCustomElement
のサンプルコードとデモ
defineCustomElement
のデモとして以下リポジトリを用意しています。
Viteを起動すると、描画されているVueコンポーネント部分がCustom Elementsとして表示されます。内部のボタンをクリックするとカウント数が増えることも確認できます。
実際の使用事例
次に defineCustomElement
を実際にはどのように使われているかを紹介します。
VueやNuxtアプリケーション内でVueコンポーネントをわざわざCustom Elementsに変換して使用することはまずないと思います。VueやNuxtの世界外でVueコンポーネントを共通で使用する際に有用だと思います。
LINEヤフーさんの事例ではtoB向けとtoC向けでコンポーネントの共通化する際にCustom Elementsに変換して使用しています。
GMOペパボさんの事例でもUIコンポーネントを提供する際に defineCustomElement
を変換層として活用しています。
defineCustomElement
はVueのランタイムに依存しているためビルドサイズも実装内容に応じて変動するため、単一のCustom Elementsのみを提供する場合はVueから作成するのは過剰となります。Custom Elementsの内容が複雑なロジックであったり、GMOペパボさんのように大規模なUIコンポーネント群として提供する際には向いていると言われています[3]。
defineCustomElement
の改善
v3.5から入った そんな defineCustomElement
ですがVue3.5のアップデートに伴い、様々な改善が入りました。以下はその改善された内容についてを紹介します。
useShadowRoot()
helper
custom-element: Custom Elementsの shadowRoot
を取得する useShadowRoot()
ヘルパーが実装されました。
useHost()
helper
custom-element: Custom Elementsの host
を取得する useHost()
ヘルパーが実装されました。
this.$host
in Options API
custom-element: expose Options APIでCustom Elementsのホスト要素を公開できる this.$host
属性が実装されました。
custom-element: inject child components styles to custom element shadow root
子コンポーネントのスタイルをCustom Elementsの shadowRoot
に挿入する機能が実装されました。
import { defineCustomElement } from 'vue';
import Root from './Root.ce.vue';
customElements.define('root-element', defineCustomElement(Root));
<!-- Root.ce.vue -->
<script setup>
import Child from './Child.ce.vue';
</script>
<!-- Child.ce.vue -->
<style>
div { color: red }; /* Root.ce.vue コンポーネントに挿入される style */
</style>
custom-element: support configurable app instance in defineCustomElement
defineCustomElement
でCustom Elementsを登録する際に configureApp
でVueアプリのインスタンスを設定できるようになりました。これによりdevtoolsでも defineCustomElement
で作成されたコンポーネントを認識できるようになりました。
defineCustomElement({
// ...
}, {
configureApp(app) {
// ...
}
})
:host
selector by applying css vars on host element
custom-element: support css :host
セレクタをサポートするために、host
要素にCSS変数を適用する実装がされました。
custom-element: support emit with options
最初のイベント引数がオブジェクトである場合 CustomEvent
のオプションオブジェクトとして使用されるようになりました。引数リスト全体は CustomEvent
の detail
プロパティを通して公開されます。
emit('event', { bubbles: true });
custom-element: support expose on customElement
defineCustomElementで expose
をサポートされるようになり、Custom Elementsから参照できるようになりました。
const HelloExposeElement = defineCustomElement({
props: {
value: String,
},
setup(props, { expose }) {
expose({
value: 'hello',
})
return () => h('div', null, [props.value])
},
customElements.define('hello-expose-el', HelloExposeElement);
<!-- 以下のCustom Elementsにてvalueプロパティはすでに公開されている旨の警告が出る -->
<hello-expose-el value="world"></hello-expose-el>
nonce
option for injected style tags
custom-element: support nonce
オプションがサポートされて、Custom Elementsに挿入されるstyleタグに nonce
属性を追加できるようになりました。
これはコンテンツ・セキュリティ・ポリシーを満たすために nonce
属性を含める必要があるため、追加されました。
custom-element: support passing custom-element-specific options via 2nd argument of defineCustomElement
defineCustomElement
の第2引数に CustomElementOptions
を渡せるようになりました。
shadowRoot: false
in defineCustomElement()
custom-element: support defineCustomElement
で shadowRoot: false
の指定をサポートするようになりました。これによりShadow DOMを使わずにCustom Elementsを作成できるようになりました。
おわりに
この記事では defineCustomElement
についてと、Vue3.5からの改善内容についてを紹介しました。今回の改善にでは2〜3年前から起票されていた問題を解消するものも含まれており、突然改善が含められた理由は明らかになっておりません(ご存知の方が居たら教えて下さい…)。
いずれにせよ、VueからのCustom Elementsの出力がより安定的になり、より多くの開発者で利用しやすくなることが期待されます。
Discussion