Hello, everyone. I haven't been around for some time, but not because I have abandoned the subject. On the contrary, I have been working hard on my primary project. At this point in time, I have successfully converted the custom implementation we had to a set of root + micro-frontend projects joined together with single-spa
. Yes, I also converted the Create React App projects we had to Vite.
In short: It looks like my primary objective has been fulfilled.
I must tell you, however, that some things changed along the way. The most notable one is that I did not convert the root project to Svelte. It remains in React for the time being. My timetable, however, contemplates full conversion of all projects.
Revised Objectives
So, because I set expectations of what would happen at the beginning of this series of articles, allow me to present the revised list of objectives, so we are all on the same page:
My primary objective remains: Migrate the entire codebase from React to Svelte with the assistance of
single-spa
.
My secondary objectives, however, have suffered some transformation: No longer will I go with the root project to Svelte as my first transformation, but instead I'll make the project go on-demand transformation to Svelte as the priorities arrive. The general rule to follow, and it has already proven to be a great path, is to aggressively convert the pieces that require modification/maintenance into single-spa
micro-frontends with Vite + Svelte instead of correcting the React codebase.
The consequence of this rule is the spawn of additional micro-frontends that might be too fine-grained. To counter this, we will allow the number of micro-frontends to grow until a point is reached where some can coalesce into one. This will be repeated until the entire codebase is converted.
Thankfully, we are using Kubernetes, meaning we can easily deploy all these pods until everything settles into a reasonable number and scheme of micro-frontends.
Enough chit-chat, I would say, let's cover the new learnings since the last article that led to the current iteration of vite-plugin-single-spa
.
CSS Injection
The previous article in the series describes our learning towards gaining full CSS injection in Vite + XXX projects. Well, just like the previous article hinted, vite-plugin-single-spa
v0.1.0 introduced a dynamic ESM module that produces single-spa
lifecycle functions to control the injection of CSS for us automatically when the project is built; CSS injection is not necessary while running micro-frontends in serve mode because Vite takes care of this for us.
After this, I experimented a little bit with Vite + Vue router-enabled projects. The project created using npm create vue@latest
comes with dynamic imports for Vue components. This causes CSS splitting when the project is built by Vite. To account for this, vite-plugin-single-spa
v0.2.0 adds a projectId
property to its list of options. This identifier is added to all CSS asset names created by Vite during the building process. Then, the dynamic module uses this identifier to enable and disable the CSS link elements when the micro-frontend is mounted and unmounted.
More testing is needed for this feature, especially with other technologies such as React Router. However, it looks promising according to my primary objective's project: Routing seems to be working just fine in my React projects, which was another secondary objective. However, my project's projects don't split CSS.
If you are in the routing and CSS splitting business using vite-plugin-single-spa
, make sure you report any issues you find along the way so we can all enjoy a quality plug-in.
Asset-Serving in Serve Mode
Great news! As it turned out, Vite is (and has always been) able to serve assets in serve mode. I was simply unaware of the correct setting. As of v0.2.0, vite-plugin-single-spa
sets the server.origin
property so images and other assets coming from micro-frontends are properly served while in serve mode.
Improving Import Mapping
While experimenting with Vue micro-frontends and a Vue root project, I decided to explore the route of shared libraries. The single-spa
documentation recommends that we only import large libraries like React and Vue as shared libraries.
One great way to do this is to import these libraries from unpkg.com or a similar CDN. Combine this with Vite's (or really, rollup's) ability to exclude libraries from the project's build and we have a solution.
As of v0.3.1 (v0.3.0 had a major flaw) of vite-plugin-single-spa
, the options importMap.dev
and importMap.build
have been redefined: These can now take an array of import map file names instead of a single file name. This allows us developers to create a third file (or more if deemed appropriate) to cater to shared libraries.
Because this is something new to this article series, allow me to pose a quick example:
Add the file src/importMap.shared.json
to a root project. Declare in this file the URL's for things like Vue or React libraries.
{
"imports": {
"vue": "https://cdn.jsdelivr.net/npm/vue@3.3.8/+esm",
"vue-router": "https://cdn.jsdelivr.net/npm/vue-router@4.2.5/+esm"
}
}
This import map uses the JSDelivr network to download both vue
and vue-router
. We can now use this file in both development and build modes to mount built Vue micro-frontends. Why not to mount Vue
micro-frontends not built? Well, because Vite server will always try to serve the vue
and vue-router
modules. The import maps will only be in effect when mounting built micro-frontends. When mounting micro-frontends running in serve mode, just have those NPM packages installed as dev dependencies.
Now vite.config.ts
for the root project would look like this (for a Svelte root project):
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import vitePluginSingleSpa from 'vite-plugin-single-spa';
export default defineConfig({
plugins: [svelte(), vitePluginSingleSpa({
type: 'root',
imo: '3.1.0',
importMaps: {
dev: ['src/importMap.Dev.json', 'src/importMap.shared.json'],
build: ['src/ImportMap.json', 'src/importMap.shared.json']
}
})]
});
We are importing the shared JSON import map in both cases, without having to repeat the import maps in two different files. this is our gain.
Ok, folks, this has brought us up-to-date in the vite-plugin-single-spa
topic as of today. However, there's more coming!
Our next topic will be single-spa
parcels and their unique case regarding CSS mounting. Spoiler: This requires a new version of vite-plugin-single-spa
which is currently in the making! The next article will explain my findings, as per usual, and how the plug-in could or would tackle the problem.
In the meantime, happy coding!