What is Single-SPA?

Single-SPA is a JavaScript framework for building microfrontends by enabling multiple single-page applications to run on a single page. Each application, or “microfrontend,” is a separate SPA that can be written in various frameworks (such as React, Angular, or Vue) and combined in a way that allows them to communicate and render seamlessly. Single-SPA handles the lifecycle events of each microfrontend, including mounting, unmounting, and updating, providing a cohesive user experience.

Why Use Single-SPA in a Monorepo?

Single-SPA is particularly well-suited for monorepos as it helps manage dependencies, promotes code reuse, and allows multiple teams to work on separate frontend applications simultaneously. Benefits of using Single-SPA in a monorepo include:

  • Modular Development: Teams can develop and deploy individual microfrontends independently.
  • Unified User Experience: Single-SPA manages microfrontend lifecycles, ensuring smooth transitions and interactions between apps.
  • Technology Agnostic: Single-SPA allows you to use multiple frontend frameworks in a single project, increasing flexibility.
  • Efficient Resource Management: Using a monorepo allows shared libraries and components to be accessed by multiple microfrontends.

Setting Up a Monorepo for Single-SPA

To set up Single-SPA in a monorepo, you’ll first need a monorepo structure. Tools like Nx and Lerna make it easy to organize and manage multiple projects within a single repository. For this setup, we’ll use Nx as an example, but similar principles apply if you’re using Lerna or another tool.

Step 1: Initialize the Monorepo

Install the Nx CLI if you haven’t already:

npm install -g nx

Then, create a new Nx workspace:

npx create-nx-workspace@latest single-spa-monorepo

Follow the prompts to set up your workspace. Choose a structure that aligns with your project’s needs. A typical structure might look like this:

/apps
  ├── root-config
  ├── app1
  ├── app2
/libs
  ├── shared-components

This structure allows for clear separation between each application and any shared libraries.

Installing Single-SPA in the Monorepo

Next, install the Single-SPA dependencies required to configure microfrontends:

npm install single-spa single-spa-layout

You’ll also need a bundler for each microfrontend. Webpack is commonly used in Single-SPA setups. You can install Webpack and the related plugins as follows:

npm install webpack webpack-cli webpack-merge html-webpack-plugin

Step 2: Creating the Root Config

The root config is a central part of a Single-SPA application. It controls the layout and routing of each microfrontend. To set up the root config, create a new project within the apps directory:

nx generate @nrwl/react:app root-config

Inside the root config, create an index.ejs file that will serve as the main HTML template:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Single-SPA Microfrontend</title>
</head>
<body>
  <div id="single-spa-root"></div>
  <script src="/apps/root-config/main.js"></script>
</body>
</html>

Configuring the Root Config Application

In the root-config project, create a file named single-spa-config.js to manage the registration of microfrontends. Here’s an example:

import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@project/app1',
  app: () => System.import('@project/app1'),
  activeWhen: ['/app1']
});

registerApplication({
  name: '@project/app2',
  app: () => System.import('@project/app2'),
  activeWhen: ['/app2']
});

start();

This configuration registers two applications (app1 and app2) to load when the user navigates to /app1 or /app2, respectively.

Setting Up Microfrontends in Single-SPA

Now that we have a root configuration set up, let’s create individual microfrontends. Each microfrontend is a separate application within the monorepo that can be developed, deployed, and updated independently.

Step 3: Creating Microfrontends

Create two microfrontend applications using Nx:

nx generate @nrwl/react:app app1
nx generate @nrwl/react:app app2

Once created, each application will have its own main.js file, which serves as the entry point. Modify main.js for each app to use Single-SPA’s lifecycle methods:

import { registerApplication, start } from 'single-spa';

function mount() {
  ReactDOM.render(<App />, document.getElementById('root'));
}

function unmount() {
  ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}

export const bootstrap = [() => Promise.resolve()];
export const mount = [mount];
export const unmount = [unmount];

start();

Here, the mount and unmount functions control rendering of the microfrontend. Single-SPA will call these functions at the appropriate times, based on the root configuration.

Configuring Webpack for Each Microfrontend

Webpack is essential for bundling each microfrontend into a format that Single-SPA can load. Create or modify the webpack.config.js file for each microfrontend:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { merge } = require('webpack-merge');
const path = require('path');

module.exports = merge(commonConfig, {
  output: {
    filename: '[name].js',
    libraryTarget: 'system'
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, 'index.html')
    })
  ],
  externals: ['react', 'react-dom']
});

This configuration sets libraryTarget to system, making it compatible with Single-SPA’s SystemJS module loader. It also marks React and ReactDOM as external dependencies, so they are not bundled with each microfrontend.

Setting Up Routing for Microfrontends

Routing in Single-SPA allows different microfrontends to render based on the current URL. In the root config, configure routes by updating single-spa-config.js:

import { constructRoutes, constructApplications, constructLayoutEngine } from 'single-spa-layout';
import microfrontendLayout from './microfrontend-layout.html';

const routes = constructRoutes(microfrontendLayout);
const applications = constructApplications({ routes });
const layoutEngine = constructLayoutEngine({ routes });

start();

With this setup, Single-SPA loads applications based on routes defined in microfrontend-layout.html, providing a flexible way to manage routing across microfrontends.

Best Practices for Managing Single-SPA in a Monorepo

Here are some best practices to consider when working with Single-SPA in a monorepo:

1. Keep Microfrontends Decoupled

One of the main benefits of Single-SPA is the ability to develop microfrontends independently. Keep each microfrontend self-contained and avoid direct dependencies between them. Instead, use shared libraries or services as intermediaries if necessary.

2. Use Shared Libraries for Reusable Code

In a monorepo, place reusable components or services in shared libraries, which can be accessed by multiple microfrontends. This approach reduces code duplication and maintains consistency across the application.

3. Optimize Build and Deployment

Single-SPA applications can grow large as new microfrontends are added. Use techniques like code splitting and lazy loading to optimize performance. Consider using a CI/CD pipeline to automate deployment, allowing you to deploy individual microfrontends without affecting the entire monorepo.

4. Monitor Dependency Conflicts

Each microfrontend should use the same versions of shared dependencies (like React) to avoid conflicts. Regularly review dependencies across microfrontends to ensure compatibility.

Testing and Debugging Single-SPA Applications

Testing and debugging are essential in a microfrontend environment. Tools like Jest for unit testing, Cypress for end-to-end testing, and React Testing Library for component testing can be used within each microfrontend.

Setting Up Unit Testing

Run unit tests for individual components in each microfrontend to ensure they function correctly. Each microfrontend can have its own Jest configuration within the monorepo, making it easy to test isolated components.

Using the Single-SPA Inspector

The Single-SPA Inspector is a Chrome extension that helps debug applications by showing information about each microfrontend, including their status and lifecycle events. It’s a valuable tool for troubleshooting any issues that arise when managing multiple microfrontends.

Conclusion

Single-SPA provides a robust framework for managing microfrontends within a monorepo, enabling modular development, seamless integration, and independent deployment of frontend applications. By following a clear setup process and adhering to best practices for routing, dependency management, and testing, you can create a scalable and maintainable microfrontend architecture. Whether you’re working with Nx, Lerna, or another monorepo tool, Single-SPA offers the flexibility and control necessary to build a modern microfrontend-based application.