Vue 3 : disable shadow dom while using custom element

Viewed 730

I would like to add in a non-vue application a custom element.

For that, I've created a classical SFC :

//test.ce.vue
<template>
    <div class="text-primary">Test</div>
</template>

<script>
export default {
    name: 'test',
};
</script>

<style>
    .text-primary {
        color: red;
    }

</style>

And then a main script :

//app.js
import Test from 'test.ce.vue';
const testElement = defineCustomElement(Test);
customElements.define('test-element', testElement);

document.body.appendChild(document.createElement('test-element'));

Everything is running normally with the creation of a shadow dom element :

<test-component>
    #shadow-root (open)
        <style>
            .text-primary {
                color: red;
            }
        </style>
        <div class="text-primary">Test</div>
</test-component>

I would like to avoid to redefine .text-primary class in the component as this class is already defined in the main css file. I also don't need to define specific classes for this component only, so in other terms, I would like to remove the shadow dom like a classical custom element will do.

So basically, render this :

<test-component>
    <div class="text-primary">Test</div>
</test-component>

Is there's any option to define in vue that permit that ?

2 Answers

Older question, but in case someone still needs a solution for this...

there is currently no way to tell Vue not to use the shadow-dom. In Vue 2 there was a official package for creating web-components without shadow-root. And there is a community port for Vue 3 of that:

https://www.npmjs.com/package/vue3-webcomponent-wrapper

It was only meant to help people who have just migrated from Vue 2 to keep there application working. It was never intended to replace the official solution and should only be used until the official package can handle Vue 3.

Unfortunately that never happend.

The community port still works, but the package does not contain any source code, so it is a bit scary to use.

I came up with another solution for our project. Using defineCustomElement on a more complex vue component wich is composed by a bunch of nested components reveals another problem. The css of the child components wont be copied to shadow root. So only the css of the root component will work. You can find the related issue and a workaround with full example here:

https://github.com/vuejs/core/issues/4662#issuecomment-1116001438

It basically grabs the css from the head and appends it to the shadow root. You just have to extend it to also copy your main.css, like

<template>
    <div id="app" ref="injectionElementRef">
        <img alt="Vue logo" src="./assets/logo.png">
        <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
    </div>
</template>

<script lang="ts">
    import {defineComponent} from 'vue';
    import HelloWorld from './components/HelloWorld.vue';

    export default defineComponent({
        name: 'App',
        components: {
            HelloWorld
        },
        mounted() {
            const el = this.$refs.injectionElementRef as HTMLElement

            const root = el.getRootNode()

            const linkTag = document.getElementById('main-css-id')

            root.insertBefore(linkTag.cloneNode(), el)
            
        }
    });
</script>

The downside of this method is, there is a short flicker because the css is applied after mount. You could show an empty element till css is applied to work around that.

You are using Vue as a Tool to create Web Components, but why use a Tool over Native Technology?
Tools are not better; Tools are only faster in performing a task.
And in your case the Tool does something you do not want it to do.

Using native Web Components Technology, all you need is:

<style>
  .text-primary {
    color: red;
  }
</style>

<test-component></test-component>

<script>
  customElements.define("test-component", class extends HTMLElement {
    connectedCallback() {
      this.innerHTML = `<div class="text-primary">Test</div>`;
    }
  })
</script>

Related