UPDATE: This article was written with single-spa-react v2.14.0
Micro-frontends are gradually gaining popularity as a way to separate frontend applications in to small performant and easy to maintain parts doing this can lead to a variety of issues in terms of maintenance but libraries like Single-SPA and Piral make that easier for us.
This tutorial will cover the steps on how to set up the single-spa.js library and get your micro-frontend setup working.
If it helps you. Leave a like so I can keep pushing stuff like this for you guys
Before you jump in
Before we start working with this I'm going to let you know that as of Webpack 5, a new plugin has been released for webpack to enable "Module Federation" which is Webpack's way of achieving Micro-Frontend Architectures it is usually used as more of a performance enhancer but can be used to achieve the same thing as this.
To understand the difference between these and what they can do check this article snippet by the single-spa team
This article simply covers Single-SPA as (what I feel) an easier alternative to Webpack 5's Module Federation.
So let's get started.
What is Single-SPA?
The single SPA team refers to it as a
A javascript router for front-end microservices.
To elaborate on this, Single SPA is a javascript library which allows you to connect together multiple isolated frontend services to look and behave as a single monolith application.
Read Microservices vs Monolith Application to learn more the differences.
Why use it?
-
Multiple Frameworks. One single entry point.
- single-spa lets you mix and match supported frameworks or libraries and make them act and appear seamless for example you can use React and Angular to make two sections of the same app and it will look like it was built with just one of the two frameworks.
-
Performant
- While this more or less depends on the applications themselves. By using single-spa to join together separate applicaitons. You can reduce the bundle size of any single application and still connect them as you require without any performance loss between apps.
-
Extremely easy to setup.
- Most of the setup is done in the root application and beyond that, the
create-single-spa
CLI does most of the work.
- Most of the setup is done in the root application and beyond that, the
Lazy Loading enabled by default.
So how does it work?
I personally think this video gives a great introduction to how Single-SPA works in a really basic way.
Hopefully you got an idea. If not, read this article to understand it a bit better
Okay, lets start writing
I'll be using Visual Studio Code as my editor of choice and React as the framework of choice for each micro-frontend application to keep things simple. But you can do the same thing to mix and match with other frameworks as well.
Single-SPA has two main parts which need to be considered,
- The Root Configuration
- This will contain the HTML page needed for rendering the javascript framework components and the functions responsible for handling the application routing.
- The Application's Themselves
- These are the application written in frameworks and libraries such as React, Vue, Svelte or even Angular
We'll start by setting up the Root Configuration Application using create-single-spa
CLI tool provided by Single-SPA
Install create-single-spa
tool by installing it through npm or yarn
npm install -g create-single-spa
Or if you're using yarn
yarn global add create-single-spa
Then follow these steps to do the create-single-spa
setup.
Step 1 : Create the root configuration by using the following command
npx create-single-spa
- After running that, you should see this
Just leave it blank & click enter to use the Current Directory.
- Then Choose
single-spa root config
Choose the package manager of your choice. I'm going to pick
yarn
Choose whether to create the project as a Typescript or Javascript project. I'll be using Javascript.
Decide whether you'll be using Single-SPA's template engine. In this tutorial we won't be using it for the sake of simplicity.
Finally, provide an organization name
The organization name should be the same across all the applications. This is because single-spa allows module resolution in the browser, it requires the organization name to be the same in order for it to work.
I will use my name (ryank) as the organization for now (This will usually be the name of your application).
At the this point your config should look like this.
Just click enter and single-spa should create the required files for your root application.
Your Folder structure will end up looking like this.
Now to see if everything works, just run either yarn start
or npm run start
.
If you see this page on localhost:9000
, you have successfully setup the root configuration application for single-spa.
A bit of a run through
Inside the src folder, you will notice 2 files.
-
index.ejs
- This is the file which will be used as our index.html file after compilation. It will hold important information regarding our micro-frontend applications such as import-maps which our root-config.js file will use to navigate apps as well as the different common modules that each of our applications will use.
-
ryank-root-config.js
- This is our root configuration file where we will register our micro-frontends to single-spa.
These two files will control a lot in a very little amount of work.
So how do I register my app ?
Well in order to do that. We first need to need to create an application so that we can register it to our root component.
Step 1
Replicate the previous steps but instead of choosing single-spa root-config
We choose
single-spa application/parcel
Like below.
Step 2
Choose the framework of your choice. In our case, we'll use React.
Step 3
Pick the package manager. We'll stick with yarn.
Step 4
Choose whether to use Typescript or not. We'll use plain Javascript
Step 5
Add the same organization name as you did with your root configuration.
Step 6
Here you need to add the name of your project. So in my case the project name will simply be app1
Your configuration should look similar to this
After clicking enter again. Single-SPA should create the necessary files and your folder structure should look like this.
If you see the above structure, check if you app is working by starting your app like below.
yarn start --port 8500
And visit http://single-spa-playground.org/playground/instant-test?name=@ryank/app1&url=8500
where "app1" in the url is whatever you named your project and 8500 is the port you used.
If you see this, you're on the right path
Now we connect them.
To connect your application to single-spa's config you need to do a couple of things. Before that lets introduce some new terms.
-
Shared Dependencies
- These are packages/libraries that are used across your micro-frontends. For example, if you were making a bunch of react micro-frontends, you would have to use
react
andreact-dom
across all of them. But remember that this is conditionally optional based on the requirements of your app.
- These are packages/libraries that are used across your micro-frontends. For example, if you were making a bunch of react micro-frontends, you would have to use
-
Import Maps
- Import Maps are a browser specification for aliasing "import specifiers" to a URL. An import specifier is the string indicating which module to load. This will act as sort of an index for single-spa to follow when looking for apps to run.
-
SystemJS
- Please read single-spa's description of SystemJS
-
Public Path
- The Public Path is the public location of your application. This could be your local host or even a hosted URL from google cloud, AWS or Azure.
We use the
setPublicPath
function insideset-public-path.js
to set this.
- The Public Path is the public location of your application. This could be your local host or even a hosted URL from google cloud, AWS or Azure.
We use the
Alright now that that's done, let's connect our react app to our root app.
Remember, we are working on the context that all our microfrontends are built in react.
Step 1 : Configuring the Root App
Navigate to your root application and open up the index.ejs file.
Then copy these CDN's providing us with react
and react-dom
"react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
"react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"
Locate this code snippet
<script type="systemjs-importmap">
{
"imports": {
"single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.5/lib/system/single-spa.min.js"
}
}
</script>
And add the CDN's you copied after the single-spa CDN seperated by commas like this
<script type="systemjs-importmap">
{
"imports": {
- "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.5/lib/system/single-spa.min.js"
+ "single-spa": "https://cdn.jsdelivr.net/npm/single-spa@5.5.5/lib/system/single-spa.min.js",
+ "react": "https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js",
+ "react-dom": "https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"
}
}
</script>
This is done so that react
and react-dom
will be used across any of the new micro-frontends that we add.
Step 2 : Registering the New App
To register the app, you need to first
- Navigate to your microfrontend app (app1 in our case)
- Navigate to
src/set-public-path.js
- Copy the text inside the
setPublicPath()
function.
The text inside is a combination of your organization name and project name in the following format
@organization/project
After copying the text. Head back to your root config file and open the ryank-root-config.js file (ryank will be whatever your organization name is)
You should see a file like this.
- The
registerApplication()
function is what will help us register our app to single-spa. It takes 3 arguments- name : This is your project identifier which has a format of
@organization/project
- app: This is a SystemJS import call that make a call to your app in order to bring it into the root application
- activeWhen: This is either an array of strings which denote the path or a function that returns a string. This tell single-spa when your application should be active and when it shouldn't show.
- name : This is your project identifier which has a format of
To register app1
to single spa,
Uncomment the commented code and replace it so that it looks like the following.
import { registerApplication, start } from "single-spa";
registerApplication({
name: "@single-spa/welcome",
app: () =>
System.import(
"https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
),
activeWhen: ["/"],
});
-// registerApplication({
-// name: "@ryank/navbar",
-// app: () => System.import("@ryank/navbar"),
-// activeWhen: ["/"]
-// });
+ registerApplication({
+ name: "@ryank/app1",
+ app: () => System.import("@ryank/app1"),
+ activeWhen: ["/app1"]
+ });
start({
urlRerouteOnly: true,
});
This will tell single-spa to render app1
when we navigate to http://localhost:9000/app1
After this we need to one more thing and that is adding your application to the import map.
To do this. You need to find this section of code in your index.ejs file
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@ryank/root-config": "//localhost:9000/ryank-root-config.js"
}
}
</script>
<% } %>
and then add your application url to it
<% if (isLocal) { %>
<script type="systemjs-importmap">
{
"imports": {
"@ryank/root-config": "//localhost:9000/ryank-root-config.js"
+ "@ryank/app1":"//localhost:8500/ryank-app1.js"
}
}
</script>
<% } %>
The reason we need to add this to two places (index.ejs and root-config.js) is because single-spa runs the registerApplication
function and then calls SystemJS which in turn refers to the import map located in your index.ejs file to find the relevant location of your micro-frontends.
If you followed these steps correctly your app should show up when you navigate to http://localhost:9000/app1
and you should see something like this :
The are surrounded in red is your app.
But if you notice both the Home Page (at localhost:9000
) and your app (at localhost:9000/app1
) are rendered in the same page.
This is single-spa's normal behavior so there's nothing to worry but we can change it by making a small change to the registerApplication
function holding the home page.
To do this, navigate to your root-config.js file and change your file like the following
import { registerApplication, start } from "single-spa";
- registerApplication({
- name: "@single-spa/welcome",
- app: () =>
- System.import(
- "https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
- ),
- activeWhen: ['/'],
-});
+ registerApplication(
+ "@single-spa/welcome",
+ () =>
+ System.import("https://unpkg.com/single-spa-welcome/dist/single-spa-welcome.js"
+ ),
+ (location) => location.pathname.endsWith('/'),
+);
registerApplication({
name: "@ryank/app1",
app: () => System.import("@ryank/app1"),
activeWhen: ["/app1"]
});
start({
urlRerouteOnly: true,
});
If you navigate to http://localhost:9000/app1
you'll see that it will only say @ryank/app1
is mounted. And your app routes are now properly separated.
Just in case you don't see the apps. Make sure that both apps (app1 and root) are running before you navigate to the page
Congrats !! You've setup Single-SPA and run it with your own app. Best way to get used to it now is to carry on your own side project and try implementing it again.
Hopefully this article helped you out in setting up your single-spa project.
Top comments (3)
As an enthusiast of
single-spa
, I have created a plug-in for Vite that converts any regular Vite + XXX project into a single-spa one. It can be found here.I basically dislike
create-single-spa
because I don't share the idea of having UI-less roots, or not getting micro-frontend projects the way I like them. For example, I like them in TypeScript and with Vite.Very helpful article. A couple of notes from my experience:
1) The "create-single-spa" CLI doesn't seem to generate a
set-public-path.js
anymore. Used CLI version 1.18.22) Ensure you are running both the "app1" app on port 8500 and the root app (defaults to port 900) to see changes made
Thanks Jake for the input. I'm currently using an older version of single-spa and I haven't checked it out recently. So I'll definitely update this based on your comment soon.