template refs on suspended components expose component instance early, before it has been resolved or properties been exposed.
Vue version
3.2.45
Link to minimal reproduction
Steps to reproduce
Run sample It uses watch on a component ref to know when there is something to act on. When the component ref is set we try to call a method on that child component. However, in 3.2.45 something changed and if the child component is async we can't see the method.
3.2.44 version (working as intended)
3.2.45 version (example breaks because method isn't exposed)
What is expected?
Receiving the child component back in the component ref and being able to see and call exposed methods on it.
What is actually happening?
The object returned doesn't have the exposed method available.
System Info
No response
Any additional comments?
We have a microfrontend that in some cases need to be able to call methods on "top-level" components from outside Vue.
Might be a better way to do it than this example (would be happy to be pointed in a good direction), however it did work prior to 3.2.45.
as workaround
// Move this to the front of top level await
defineExpose({
callMe,
});
// Make async
await new Promise((resolve) => setTimeout(resolve, 1));
duplicate of https://github.com/vuejs/core/issues/4930 In fact, version 3.2.45 will also have problems with production.
as workaround
// Move this to the front of top level await defineExpose({ callMe, }); // Make async await new Promise((resolve) => setTimeout(resolve, 1));
That works, but would require all teams to make changes and remember the order. Is there no way to work around it from outside the component? Or will there be a fix and we just have to postpone upgrading?
@SimmeNilsson this PR https://github.com/vuejs/core/pull/4951 will fix it.
@edison1105 #4951 was too outdated, author just closed it. I'll reopen this issue here until we have a fresh fix. Also, since it works in 3.2.44, it must be something that changed in the meantime, right?
it must be something that changed in the meantime, right?
@LinusBorg Yes, and is not working all the time in reproduction mode.
Ah, got it. in 3.2.45, we closed a few loopholes in the difference between development and production behaviour of compiled script-setup components.
One of these "loopholes" made the code in this example work in development with 3.2.44. And as you said, it never worked in production in either version.
That's because the ref is being set before instance.exposed
is being set. The ref is set when the async component is being mounted by Suspense into an inactive side branch, where it stays until it has resolved. at that point, exposed()
hasn't been called yet.
Now I see two possible ways around that:
- The ref should be set later: We set the ref only after the async component has resolved.
- The ref should still be set as early as it is now, but the expose proxy should be properly provided, independent of when
exposed()
is being called.
But 2. isn't really a proper solution: We would expose the correct proxy now, but the callMe
method still would not be exposed on that proxy until right before the component resolved, because only then does the example call expose()
.