On my current project I use single-spa framework to create a dashboard where developers can publish small bits of data visualization.
The decision for using micro-frontends in general, and single-spa in particular, has happened long before I have joined the team, and we have more than five other teams integrating with it - so the adoption has been broad.
Single-spa is good at solving the complex problem of interoperability with different javascript frameworks as micro-frontends. It also has some architectural choices forcing us to write code that is hard to reason about.
In short, I am not quite sure how I feel about it. I have no other micro-frontend framework to compare it to, but I often struggle with the way the integration works.
Examples of issues we ran into
Unhandled Exception
We are using the <Parcel/>
component from single-spa-react
package to load
the widgets onto the dashboard.
We want to replace the widget with a user friendly error message when it
breaks at mounting. The <Parcel/>
component allows us to handle such an
error in a following way:
<Parcel
config = {parcelConfig}
handleError = {err
=>
console.error(err)
}
...
/>
however, single-spa still throws the error asynchronously on the window, without any entry point to catch it.
A solution we came up with is wrapping it in a stateful component. We replace the Parcel component in a switch-case logic whenever the handleError function is called, toggling the widget to a broken state. Although it works, it seems that it is more complicated than it should be.
On top of that, an error thrown on the window
means that we can not
write an automated unit test for it. Jest unit tests are always broken with
an unhandled asynchronous exception. So we resort to testing it in a browser test.
The whole journey reminded me of the article by Raymond Chen from 2005: "Cleaner, more elegant, and harder to recognize."
Unchecked required props
In another instance, each of the lifecycle functions (bootstrap, mount, and
unmount) requires a name
prop.
But single-spa does not check for the presence of it when the function is
called.
When we created an entrypoint to an application with
function mount(): {
return singleSpa.mount(props);
}
our props did not contain the name
property.
The application was mounted and shown properly on a page but did not get removed when needed. Only after quite some research, we discovered that the name prop was required. We suspect that it needs it for singleSpa to find our application in its internal registry. Note to self: open a ticket for improving the documentation on this one.
We are currently switching to using the single-spa parcel API directly,
effectively replacing the single-spa-react/parcel
module for our needs.
Using low-level API allows us to be more granular.
Conclusion
The framework solves a difficult interoperability problem between micro-frontends written in multiple frameworks. It does so quite well. It also, unfortunately, hides a lot of issues under the rug when I am inevitably making a mistake.
I prefer that errors escalate as close to where they appear as possible, so I don’t need to trace the root cause of my mistakes throughout the codebase. And if I make a mistake, I’d like to be told about it via types or automated tests before my code reaches production. However, I found that some things are quite difficult to test in this framework.
After two months with it, my impression is that it still has some ways to go. I will work with it longer and hope to get a much more complete overview of its merits. But for now, I am not sure if I would start with it on a new project right away.