single-spa: orchestrating a create-react-app bootstrapped app with craco

2021-04-13

 | 

~4 min read

 | 

785 words

Single-spa is a framework for orchestrating multiple front-end applications together. There are numerous reasons to do this - from mixing and matching technologies to separating concerns and plenty more besides.

Unfortunately, working with React applications bootstrapped with create-react-app is a bit of a headache because single-spa assumes that you’ll have a webpack configuration you can modify.

The FAQs list four possible solutions:

  1. Remove react-scripts and then run create-single-spa on your project. This will merge create-single-spa’s package.json with yours, and provide you with a default webpack config. Run yarn start and fix webpack configuration errors until it’s working.
  2. Use craco-plugin-single-spa-application to modify the webpack config without ejecting. See the project’s README for basic configuration.
  3. Use react-app-rewired to modify the webpack config. See this Gist that shows a basic config you can start with. The example config may not work in every case or solve every problem.
  4. Eject your CRA project’s webpack config so you can modify it.

I’ve tried all of them at this point, except #3 - react-app-rewired’s README suggested it’s not actively maintained. At the end of the day, CRACO was the winner for us, but the steps weren’t quite as straight forward as the line item suggests.

The README for the craco-plugin-single-spa-application doesn’t mention what CRACO is (an acronym for ”Create-React-App Configuration Override) or any other dependencies (e.g., @craco/craco). So, the first time I attempted to use it, it didn’t seem to work and I started exploring other avenues. Multiple hours later, I decided a fresh start was in order.

By the end of this article, we’ll have stepped through each of the steps actually required to get a create-react-app bootstrapped application to work within a single-spa framework.

Adding CRACO as a dependency

Installing CRACO means installing a dependency and modifying scripts within the package.json:

package.json
"scripts": {
-   "start": "react-scripts start",
+   "start": "craco start",
-   "build": "react-scripts build",
+   "build": "craco build"
-   "test": "react-scripts test",
+   "test": "craco test"
}

Once that’s in place, we’re ready to configure the override with the plugin.

CRACO Plugin For single-spa

At this point, the instructions from the FAQ are actually quite accurate - by following the README for the plugin, your application is in a good spot.

Specifically, that means installing the plugin and then adding the

craco.config.js
singleSpaApplicationPlugin = require("craco-plugin-single-spa-application")

module.exports = {
  plugins: [
    {
      plugin: singleSpaApplicationPlugin,
      options: {
        orgName: "my-org",
        projectName: "my-app",
        entry: "src/single-spa-index.tsx", //defaults to src/index.js,
        orgPackagesAsExternal: false, // defaults to false. marks packages that has @my-org prefix as external so they are not included in the bundle
        reactPackagesAsExternal: true, // defaults to true. marks react and react-dom as external so they are not included in the bundle
        externals: ["react-router", "react-router-dom"], // defaults to []. marks the specified modules as external so they are not included in the bundle
        minimize: false, // defaults to false, sets optimization.minimize value
      },
    },
  ],
}

The one potential “gotcha” here is the entry — be sure to update that to be the entry point you create in the next step.

Modifying The CRA to Export single-spa Lifecycle Methods

If you followed the guide for getting started with single-spa or used the create-single-spa CLI, the following will look familiar to you, however, it’s an easy step to miss if you’re just reading the FAQ.

index.tsx
import React from "react"
import ReactDOM from "react-dom"
import singleSpaReact from "single-spa-react"
import App from "./App"

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: App,
  errorBoundary(err, info, props) {
    // Customize the root error boundary for your microfrontend here.
    return <div>Here is an error boundary!</div>
  },
})

export const { bootstrap, mount, unmount } = lifecycles

Wrapping Up

The final steps will be to update your single-spa root-config document to actually register the application. At that point, however, you’re back on the garden path and should follow the documentation on configuration.

Now that I’ve successfully modified a React application bootstrapped with create-react-app to work within single-spa, I understand that it’s not as complicated as I made it out to be. That’s the good news. The bad news is that there’s still a little bit of tribal knowledge involved. Hopefully this blog post helps others who are looking to understand how to use the craco approach to maintain the benefits of a CRA app and avoid having to learn a lot of webpack specific details.


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!