-
-
Notifications
You must be signed in to change notification settings - Fork 44
Description
In our teams we have been using single SPA for a while and love it. The different teams use Angular, React and Vue applications. They all work really well together. However, recently we tried to convert one of the Vue applications from version 2 to version 3 with Typescript and that's when we encountered problems.
To Replicate
I have setup an example repository with instructions to replicate here: https://github.com/smilyanp/mf-setup
The error
The error we see is this:
In more detail
I don't claim that we're doing everything correctly, but this is what has worked previously and now it doesn't, thus me raising the issue.
The setup is simple, in the host application we load:
{
"imports": {
"@mf-setup/react-app": "/node_modules/@mf-setup/react-app/dist/react-app.umd.js",
"@mf-setup/vue-app": "/node_modules/@mf-setup/vue-app/dist/index.umd.js"
}
}
And we register the application as:
singleSpa.registerApplication(
'react-app',
() => load('@mf-setup/react-app'),
showWhenPrefix(['/user-management', '/user/profile']),
{ environment, eventBus, features },
);
singleSpa.registerApplication(
'vue-app',
() => load('@mf-setup/vue-app'),
showWhenPrefix(['/user/profile']),
{ environment, eventBus, features },
);
That all works ok and there is no issue loading the source for each application under the respective route.
This is where things become a bit more difficult and potentially we are doing it incorrectly, but again, it has worked previously.
The React application loads the contents of the Vue application for a given route
In our case, the React application has a lot of different functionality and we want it to load the contents of the Vue application which should be independent. To do so, the React application provides a placeholder element on it's route, a <div id="vue-app"></div> with an ID that the Vue application can bind to:
<Router>
<Switch>
<Route path="/user-management" component={() => {
return (
<div>
<h2>React App Content:</h2>
<div>Some content provided by the react app</div>
</div>
)
}} />
<Route exact path="/user/profile" component={() => {
return (
<div>
<h2>Vue App Content:</h2>
<div id="vue-app"></div>
</div>)
}} />
</Switch>
</Router>
When the user goes to /user/profile route the @mf-setup/vue-app source code correctly loads on the page (the console.logs show that and also the network tab).
Inside of the Vue application we abstract the mount to wait until the HTML element is available in the DOM thus:
vueLifecycles = singleSpaVue({
createApp,
appOptions: {
render () {
return h(App, {
name: this.name,
// @ts-expect-error single-spa docs claim these properties exist...
mountParcel: this.mountParcel,
// @ts-expect-error single-spa docs claim these properties exist...
singleSpa: this.singleSpa,
});
},
el: `#vue-app`,
},
replaceMode: true,
});
mount = async (props) => {
waitForElement('vue-app', () => vueLifecycles.mount(props));
};
export { mount, unmount, bootstrap };
This has worked previously without issues, and also we can see that the <App /> component is mounted as well, because in the created() lifecycle hook there is a console.log that appears in the console.
However, the contents of that component are never rendered on the page and instead we see the error shown above.
Any help on this would greatly be appreciated.
