I'm currently implementing a plugin system in my application using single-spa and file-based tanstack routing.
One of this issue I'm facing is that I need to share routing context between my app and my plugin. For now my app is using a HistoryRouter and my plugin a MemoryRouter as to not collide with the browser's history API.
Here's my plugin declaration :
const lifecycles = singleSpaReact({
React,
ReactDOMClient,
rootComponent: (
/**
* Parameters received from single-spa parent application
* @param hostStore - The global Zustand store provided by the parent application
* @param routingContext - The host tanstack routing context
*/
{
hostStore,
hostRouter,
implementRoutes,
}: {
hostStore: UseBoundStore<StoreApi<ConsoleZustandStore>>;
hostRouter: AnyRouter;
implementRoutes: (routes: string[]) => void;
}
) => {
// Initialize global state management with the provided store
initGlobalStore(hostStore);
// const pluginRoute = {path: "/mypluginroute", component: <p>hello mypluginroute</p>}
//const newTree = hostRouter.options.routeTree.addChildren(pluginRoute)
// hostRouter.update(newTree);
// implementRoutes(["routeone", "routetwo"])
// Create application-specific router configuration
const memoryRouter = createAppRouter();
// Return the main application component wrapped with router
return <RouterProvider router={memoryRouter} />;
},
errorBoundary(err, info) {
// Render a user-friendly error display with technical details
return (
<div style={{ color: "red", padding: "20px" }}>
<h3>There was an error!</h3>
<details style={{ whiteSpace: "pre-wrap", fontFamily: "monospace" }}>
<summary>View error details</summary>
<p>{err.toString()}</p>
<hr />
<p>Component stack trace:</p>
<code>{info.componentStack}</code>
</details>
</div>
);
},
});
Here's my plugin loader implementation :
<Suspense fallback={<div>Loading...</div>}>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
style={{ height: "calc(100% - 47px)" }}
>
<Parcel
key={currentPlugin.url}
config={() => import(/* @vite-ignore */ pluginUrl)}
mountParcel={mountRootParcel}
hostStore={useGlobalStore}
hostRouter={router}
implementRoutes={(routes: string[])=> {
// TODO : dynamically setup defined routes
console.log("routes : ", routes)
}}
/>
</motion.div>
</Suspense>
I've tried passing the router as props and/or sending plugin route to the host application through a callback so that I could do a hot-update of the router but it did not work, no new route was created. I'm slowly thinking of faking the routing state, communicating the current memory internal route of the plugin to the host to be appended to the url and inversely setting up a catch all route where my plugin is located and giving the location to the plugin so that he can parse it a navigate to the correct route.
Is there any way to not cheat over the tanstack router and browser history API ?
Thanks for your guidance.