@vue/compat doesn't render slot fallback content for Vue 2 components
Vue version
vue/@vue/compat v3.3.9
Link to minimal reproduction
https://stackblitz.com/edit/stackblitz-starters-ceuhq7?file=packages%2Fapp%2Fsrc%2FApp.vue
Steps to reproduce
There are two examples provided in the repro, both of which use a Test component (which is compiled/run in Vue 2 compat mode).
The first example (Component that should have fallback content for the default slot
) does not provide content for Test
's default slot and is missing fallback slot content in the rendered output. It should contain <span>Fallback slot content</span>
.
The second example (Component with default slot content
) does provide content for Test
's default slot, and you can see that in the rendered output.
What is expected?
When a Vue 2 compat component is not provided content for a slot, @vue/compat should render the fallback content for that slot.
What is actually happening?
@vue/compat does not render fallback content for Vue 2 compat components.
System Info
No response
Any additional comments?
I believe this might be caused by legacyRenderSlot
wrapping fallback
in a function. Inspecting fallback
shows that it's already wrapped in a function.
In my understanding, compat should be a component using vue2 syntax, compiled and run in the vue3 environment, then vue2-component
should be configured according to the document. In my attempt, this configuration can work. But I'm not sure if this understanding is correct
https://github.com/vuejs/core/tree/main/packages/vue-compat#installation
vue2-component
is a stand-in for a third party component that's been compiled with Vue 2 and installed from npm, so I wouldn't necessarily have control over whether or not it's been compiled with @vue/compat
. My understanding of how @vue/compat
works is that only the application is required to be compiled with it, and that application can depend on third party components that might still be compiled with Vue 2. Is that understanding correct?
Ran into this as well.
It can be worked around with:
<slot name="mySlotName" v-if="'mySlotName' in $slots"></slot>
<template v-else>Fallback Content</template>
Note that you have to use in
rather than $slots.mySlotName
as the latter seems to trigger some side effect via Getter/proxy which breaks other things.
I'm working on a compat mode build fix though... if it's straightforward I'll submit a PR, otherwise will just (temporarily) monkey patch our code as above.
I found that compat/vue.runtime.esm-bundler.js
has a function:
function legacyRenderSlot(instance, name, fallback, props, bindObject) {
if (bindObject) {
props = mergeProps(props, bindObject);
}
return renderSlot(instance.slots, name, props, fallback && (() => fallback));
}
Changing the last line to the following fixes it:
return renderSlot(instance.slots, name, props, fallback);
fallback
is already supplied as a function, but gets wrapped again with an unnecessary function wrapper. I'm not familiar enough at all with the Vue code to know if this is the right fix, but it works at a surface level. Perhaps there are cases where fallback
is indeed a vnode and not a function. Can add a check for that case