Subscribe on changes!

When defined as a WebComponent, `useSlots()` and `$slots` return an empty object

avatar
Jul 6th 2023

Vue version

3.3.4

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-xycchc?file=src%2FApp.vue

Steps to reproduce

Just run and see the rendered content of useSlots() and $slots props:

vue-wc-issue

What is expected?

Expected WebComponent to act similarly as vue component when accessing useSlots() or $slots.

What is actually happening?

useSlots() and $slots return an empty object when using as WebComponent.

System Info

System:
    OS: Linux 5.10 Ubuntu 20.04 LTS (Focal Fossa)
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i7-1265U
    Memory: 16.62 GB / 24.62 GB
    Container: Yes
    Shell: 5.0.17 - /bin/bash
  Binaries:
    Node: 18.12.1 - /usr/local/bin/node
    npm: 8.19.2 - /usr/local/bin/npm
  npmPackages:
    vue: ^3.3.4 => 3.3.4

Any additional comments?

No response

avatar
Jul 10th 2023

Vue slots content comes in the form of functions that retrun vnodes. But when you wrapp a vue component as a custom element, this component will not receive vue slots content.

Instead, the custom element uses native <slot> elements, and those inject actual HTML elements directly from the parent DOM. Vue doesn'T even see these elements, really. And I would not see it as a good idea to have $slots suddently return HTML elements instead of vnode factory functions when used in a custom element.

However I do see an opportunity to improve the docs about this. The docs team would surely be thankful for an issue in their repo over at vuejs/docs

avatar
Jul 11th 2023

@LinusBorg thanks for respond.

I understand that there might be some differences and limitations when wrapping Vuejs component into a WebComponent. Although IMO every framework should strive to minimize those and make DX as fluent as possible.

By only updating docs, the problem won't be solved. Still Vuejs devs would have to struggle to come up with a workaround for such cases.

I would not see it as a good idea to have $slots suddently return HTML elements instead of vnode factory functions when used in a custom element.

When $slots suddenly returning an empty object, that seems to be much worse idea, than returning at least a list of HTML Elements, wouldn't you agree?

Can you please consider implementing some workaround baked into Vuejs for better compatibility with WebComponents, and may be transforming this bug report into a feature request? 🙏

avatar
Jul 11th 2023

When $slots suddenly returning an empty object, that seems to be much worse idea, than returning at least a list of HTML Elements, wouldn't you agree?

I don't really agree. for one, the types would then need to reflect this, and developers would be forced to always take into account the possibilty of a slot suddenly conatining plain HTML elements instead of a vnode factory, even though they don't develop component to be used as web components.

If anything, a new API would be a better solution, or even just setting a template ref on a <slot /> (didn't try wether that already works right now with web component slots)

avatar
Jul 11th 2023

I was hoping ref on a <slot /> would work, but it doesn't, I have just checked.

Indeed a union return-types increases cognitive load and doesn't seem to be a good solution. And indeed apparently a new API is required. At the end of the day it still looks like a bug, and not like a feature or well reasoned limitation.

I ended up with this dirty workaround:

export function useSlot() {
    const instance = getCurrentInstance()
    const slot = ref<VNode[] | HTMLCollection | undefined>()
    if (instance) {
        onMounted(() => {
            const root = instance.root.vnode.el
            const isWebComponent = root?.parentNode instanceof ShadowRoot
            if (!isWebComponent) {
                const slots = useSlots()
                slot.value = slots.default && slots.default()
            } else if (root) {
                const host = root.parentNode.host as HTMLElement
                slot.value = host.children
            }
        })
    }
    return slot
}

If anyone has a better idea please reply in this issue.