Vue.js defineCustomElementで子コンポーネントのスタイルを反映させるためのViteプラグインを書いてみる
Vue.js では、 defineCustomElement
関数を用いてVueコンポーネントをカスタム要素にラップできます。
しかしながら、Vueコンポーネントが子Vueコンポーネントを持っている場合、子Vueコンポーネントのスタイルが適用されないという問題があります。
たとえば、以下のような二つのコンポーネントについて
// Container.vue
<script setup lang="ts">
import ContainerInner from './ContainerInner.vue'
</script>
<template>
<div class="container-outer">
<p>outer</p>
<container-inner>inner</container-inner>
</div>
</template>
<style scoped>
.container-outer {
background-color: gainsboro;
}
</style>
// ContainerInner.vue
<script setup lang="ts">
</script>
<template>
<div class="container-inner">
<slot></slot>
</div>
</template>
<style scoped>
.container-inner {
color: red;
}
</style>
以下のようにカスタム要素化し、 <my-container>
タグを設置したとします。
import Container from './Container.vue'
import { defineCustomElement } from 'vue'
const ce = defineCustomElement(Container)
customElements.define('my-container', ce)
この場合、画面に表示される「inner」の文字は、ContainerInnerコンポーネントに記述されたCSSによって赤くなることが期待されますが、以下のように黒いままです。
この問題はVue.jsリポジトリにもIssueが投稿されていたり、いくつかのPull Requestで修正案が提案されているようですが、解決されていません。
ざっくりとソースコードリーディングをしてみると、以下のことがわかります。
- defineCustomElement を呼ぶと、VueCustomElementクラスが返却される (ref)
- VueCustomElementクラスはVueElementクラスを継承している。継承元のコンストラクタを呼ぶ際に渡すオブジェクトは InnerComponentDef型
- InnerComponentDef 型は
styles?: string[]
のキーバリューを持っている
ということで、 defineCustomElement
関数に渡すオブジェクトは、Vueコンポーネントに styles
配列を渡したものになります。渡された styles
配列は、カスタム要素のShadow DOM内にインラインスタイルとして読み込まれます。
このとき、 styles
配列に含まれるCSSが最も外側のコンポーネントのみになっているため、子Vueコンポーネント以下のコンポーネントのスタイルが適用されないということのようです。
これを解決できそうな方法を考えてみます。