Working with Single-SPA
Tags: microfrontends, singlespa, react, tutorial
Published: 2020-12-06 16:50:37

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](https://single-spa.js.org/docs/recommended-setup/#module-federation) 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](https://www.mulesoft.com/resources/api/microservices-vs-monolithic#:~:text=A%20monolithic%20application%20is%20built,and%20a%20server%2Dside%20application.) to learn more the differences.

###Why use it? 
1. 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.

2. 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.

3. 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.

4. 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.
{% youtube L4jqow7NTVg %}

Hopefully you got an idea. If not, read [this article](https://single-spa.js.org/docs/getting-started-overview#javascript-microfrontends) 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,
<ol>
  <li>The Root Configuration
  <ul style="list-style-type": none;">
    <li>This will contain the HTML page needed for rendering the javascript framework components and the functions responsible for handling the application routing.</li>
  </ul>
</li>
  <li>The Application's Themselves
  <ul>
    <li>These are the application written in frameworks and libraries such as React, Vue, Svelte or even Angular</li>
  </ul>
</li>
</ol>

We'll start by setting up the Root Configuration Application using <br/>`create-single-spa` CLI tool provided by Single-SPA

Install `create-single-spa` tool by installing it through npm or yarn

```bash
npm install -g create-single-spa
```
Or if you're using yarn
```bash
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
```bash
npx create-single-spa
```

- After running that, you should see this 

![Directory Name](https://dev-to-uploads.s3.amazonaws.com/i/pnp4wh80br2n0nbjdli9.PNG)

Just leave it blank & click enter to use the Current Directory.

- Then Choose `single-spa root config`

![create-single-spa menu](https://dev-to-uploads.s3.amazonaws.com/i/lr2nydv5m4ugf765qebo.PNG)

- 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.

![Full Configuration](https://dev-to-uploads.s3.amazonaws.com/i/8wv5r0y0zd702sg8rig0.PNG)

Just click enter and single-spa should create the required files for your root application.

Your Folder structure will end up looking like this.

![Folder Structure](https://dev-to-uploads.s3.amazonaws.com/i/jw1nn4rays9wsd1xro4u.PNG)

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.

![Single SPA Welcome Page](https://dev-to-uploads.s3.amazonaws.com/i/d2t87k8kwhov2dd7mwvn.PNG)


### A bit of a run through
Inside the src folder, you will notice 2 files.
1. 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.

2. 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 <br/>`single-spa root-config`<br/> We choose <br/> `single-spa application/parcel`

Like below.

![Pick Application from Menu](https://dev-to-uploads.s3.amazonaws.com/i/h04c40higgv3uaod4fo9.PNG)

####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

![Configuration for Application](https://dev-to-uploads.s3.amazonaws.com/i/fk3llt9evzrpme9711nm.PNG)

After clicking enter again. Single-SPA should create the necessary files and your folder structure should look like this.

![App1 Folder Structure](https://dev-to-uploads.s3.amazonaws.com/i/nxxngo7v7x7ofb1bczq9.PNG)

If you see the above structure, check if you app is working by starting your app like below.

```bash
yarn start --port 8500
```

And visit http://single-spa-playground.org/playground/instant-test?name=@ryank/app1&url=8500 <br/> 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

![App1 Playground](https://dev-to-uploads.s3.amazonaws.com/i/5unmu7fwfczg8573fytv.png)

###__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.

1. 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` and `react-dom` across all of them. But remember that this is conditionally optional based on the requirements of your app.

2. 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.

3. SystemJS
    - Please read single-spa's description of [SystemJS](https://single-spa.js.org/docs/recommended-setup/#systemjs)

4. 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 inside `set-public-path.js`to set this. 

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
```js
<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 

```diff
<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.

![ryank-root-config.js](https://dev-to-uploads.s3.amazonaws.com/i/nl5hn17xpm1q5m0yr9tl.png)

- The `registerApplication()` function is what will help us register our app to single-spa. It takes 3 arguments
    1. name : This is your project identifier which has a format of `@organization/project`
    2. app: This is a SystemJS import call that make a call to your app in order to bring it into the root application
    3. 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.

To register `app1` to single spa,

Uncomment the commented code and replace it so that it looks like the following.

```diff
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

```js
<% 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

```diff
<% 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 : 

![Mounted App](https://dev-to-uploads.s3.amazonaws.com/i/10s4duryc4xrqrgdicz5.png)

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

```diff
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.

##References
- [single-spa.js](https://single-spa.js.org/)