Subscribe on changes!

@vue/compat doesn't render slot fallback content for Vue 2 components

avatar
wpj
Nov 29th 2023

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?

https://github.com/vuejs/core/blob/2cece5ba1b9b254cface23096d17ed0e1910467d/packages/runtime-core/src/compat/renderHelpers.ts#L79

I believe this might be caused by legacyRenderSlot wrapping fallback in a function. Inspecting fallback shows that it's already wrapped in a function.

avatar
Nov 30th 2023

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

avatar
wpj
Nov 30th 2023

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?

avatar
Dec 5th 2023

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.

avatar
Dec 5th 2023

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