DEV Community

loading...
Cover image for Working with Single-SPA

Working with Single-SPA

aisirachcha21 profile image Ryan Kuruppu ・9 min read

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.

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?

  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.

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,

  1. 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.
  2. 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
Enter fullscreen mode Exit fullscreen mode

Or if you're using yarn

yarn global add create-single-spa
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • After running that, you should see this

Directory Name

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

  • Then Choose single-spa root config

create-single-spa menu

  • 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

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

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

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

Like below.

Pick Application from Menu

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

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

App1 Folder Structure

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

yarn start --port 8500
Enter fullscreen mode Exit fullscreen mode

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

App1 Playground

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
  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 setPublicPathfunction inside set-public-path.jsto 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"
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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

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,
});

Enter fullscreen mode Exit fullscreen mode

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>
<% } %>

Enter fullscreen mode Exit fullscreen mode

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>
<% } %>

Enter fullscreen mode Exit fullscreen mode

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

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,
});

Enter fullscreen mode Exit fullscreen mode

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.

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

Discussion (2)

pic
Editor guide
Collapse
jakemetzdev profile image
Jake Metz

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.2
2) Ensure you are running both the "app1" app on port 8500 and the root app (defaults to port 900) to see changes made

Collapse
aisirachcha21 profile image
Ryan Kuruppu Author

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.