one-spa-react
one-spa-react is a helper library that helps implement one-spa registered application lifecycle functions (bootstrap, mount and unmount) for use with React. Check out the one-spa-react github.
Installation
npm install --save one-spa-react
# or
yarn add one-spa-react
Alternatively, you can use one-spa-react by adding <script src="https://unpkg.com/one-spa-react"></script> and accessing the oneSpaReact global variable.
Quickstart
Use one-spa-react to create and export one-spa lifecycle functions from your application's entry file.
import React from "react";
import ReactDOMClient from "react-dom/client";
import rootComponent from "./path-to-root-component.js";
// OneSpaContext is a [email protected] (if available) context that provides oneSpa props
import oneSpaReact, { OneSpaContext } from "one-spa-react";
export const { bootstrap, mount, unmount } = oneSpaReact({
React,
ReactDOMClient,
rootComponent,
errorBoundary(err, info, props) {
// https://reactjs.org/docs/error-boundaries.html
return <div>This renders when a catastrophic error occurs</div>;
},
});
Quickstart for React <=17
import React from "react";
import ReactDOM from "react-dom";
import rootComponent from "./path-to-root-component.js";
import oneSpaReact, { OneSpaContext } from "one-spa-react";
export const { bootstrap, mount, unmount } = oneSpaReact({
React,
ReactDOM,
rootComponent,
errorBoundary(err, info, props) {
// https://reactjs.org/docs/error-boundaries.html
return <div>This renders when a catastrophic error occurs</div>;
},
});
Options
All options are passed to one-spa-react via the opts parameter when calling oneSpaReact(opts). The following options are available:
React: (required) The main React object, which is generally either exposed onto the window or is available viarequire('react')import React from 'react'.ReactDOMClient: (required, React 18+) The main ReactDOMbject, which is available viarequire('react-dom')import ReactDOM from 'react-dom'.ReactDOM: (legacy) The main ReactDOMbject if framework version is 17 and below.rootComponent: (required) The top level React component which will be rendered. Can be omitted only ifloadRootComponentis provided.loadRootComponent: (optional) A loading function that takes custom one-spa props and returns a promise that resolves with the component. This takes the place of therootComponentopt, when provided. It is intended to help people who want to lazy load the source code for their root component. The source code will be lazy loaded during the bootstrap lifecycle.errorBoundary: (optional) A function that acceptserr,info, andpropsand must return the UI for a React Error Boundary. This is provided as a convenient way of implementing an Error boundary without having to write your own class component for it. TheerrorBoundaryfunction may be provided in the options to oneSpaReact(), or as a custom prop provided by the root config.errorBoundaryClass: (optional) A React Component class that will be used as the React error boundary. This is an alternative to providing theerrorBoundaryfunction. TheerrorBoundaryClassmay be provided in the options to oneSpaReact(), or as a custom prop provided by the root config.suppressComponentDidCatchWarning: (optional) A boolean that indicates if one-spa-react should warn when the rootComponent does not implement componentDidCatch. Defaults to false. It is preferred to implementerrorBoundaryinstead of suppressing this warning.domElementGetter: (optional) A function that is given the one-spa props and returns a DOMElement. This dom element is where the React application will be bootstrapped, mounted, and unmounted. Note that this opt can be omitted. When omitted, thedomElementGetterordomElementcustom one-spa props are used. To use those, dooneSpa.registerApplication({ name, app, activeWhen, customProps: {domElementGetter: function() {...}} })oroneSpa.registerApplication({ name, app, activeWhen, {domElement: document.getElementById(...)} }). If no dom element can be found through any of those methods, then a container div will be created and appended to document.body, by default.parcelCanUpdate: (optional) A boolean that controls whether an update lifecycle will be created for the returned parcel. Note that option does not impact one-spa applications, but only parcels. It is true by default.renderType: (optional) ENUM of one of the following:'render','hydrate','createRoot','unstable_createRoot','createBlockingRoot', and'unstable_createBlockingRoot'. Defaults to'render'. Allows you to choose which ReactDOM render method you want to use for your application. As of [email protected],renderTypecan be a function that returns a string, for dynamically calculated renderType.
Notes
For react@>=16, it is best practice to have each one-spa application's root application implement componentDidCatch in order to avoid the entire application unmounting unexpectedly when an error occurs. one-spa-react will warn to the console if componentDidCatch is not implemented. See https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html for more details.
OneSpaContext
Parcels
one-spa-react can also be used to create a one-spa parcel (instead of a one-spa application). To do so, simply call oneSpaReact() the same as for an application, except without a domElementGetter (since those are provided by the code that will mount the parcel).
Additionally, one-spa-react provides a <Parcel> component to make using framework agnostic one-spa parcels easier. This allows you to put the parcel into your render method's jsx, instead of having to implement componentDidMount and componentWillUnmount.
You can use the Parcel component either by npm installing the library and importing one-spa-react/parcel or by adding <script src="https://unpkg.com/one-spa-react/parcel"></script> and then accessing the Parcel component with window.Parcel.default.
Parcel props
config(required): Either a one-spa parcel config object, or a "loading function" that returns a Promise that resolves with the parcel config.wrapWith(optional): A string tagName.<Parcel>will create a dom node of that type for the parcel to be mounted into. Defaults todivwrapStyle(optional): Styles that will apply towrapWith.wrapClassName(optional): classNames that will apply towrapWith.appendTo(optional): A dom element to append the parcel to. By default, this is not needed because the parcel will be mounted in the DOM that the<Parcel>component was rendered into. Useful for appending parcels to document.body or other separate parts of the dom.mountParcel(sometimes required, sometimes not): ThemountParcelfunction provided by one-spa. In general, it is preferred to use an application's mountParcel function instead of the one-spa's root mountParcel function, so that one-spa can keep track of the parent-child relationship and automatically unmount the application's parcels when the application unmounts. Note that if the<Parcel>component is being rendered by a one-spa application that uses one-spa-react, it is unnecessary to pass in the prop, since<Parcel>can get the prop from OneSpaContexthandleError(optional): A function that will be called with errors thrown by the parcel. If not provided, errors will be thrown on the window, by default.parcelDidMount(optional): A function that will be called when the parcel finishes loading and mounting.
Examples
// Use this import path in environments that support package.json exports
// See https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_package_entry_points
// and see https://github.com/bitterblossomio/one-spa-react/releases/tag/v3.0.0
// Use this in Webpack 5 and recent versions of Node
import Parcel from 'one-spa-react/parcel'
// Use this import path in environments that don't support package.json exports
// See https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_package_entry_points
// and see https://github.com/bitterblossomio/one-spa-react/releases/tag/v3.0.0
// Use this in Webpack 4 and older versions of Node
import Parcel from 'one-spa-react/lib/esm/parcel'
import * as parcelConfig from './my-parcel.js'
// config is required. The parcel will be mounted inside of the
// of a div inside of the react component tree
<Parcel
config={parcelConfig}
wrapWith="div"
handleError={err => console.error(err)}
customProp1="customPropValue2"
customProp2="customPropValue2"
/>
// If you pass in an appendTo prop, the parcel will be mounted there instead of
// to a dom node inside of the current react component tree
<Parcel
config={parcelConfig}
wrapWith="div"
appendTo={document.body}
/>
// You can also pass in a "loading function" as the config.
// The loading function must return a promise that resolves with the parcel config.
// The parcel will be mounted once the promise resolves.
<Parcel
config={() => import('./my-parcel.js')}
wrapWith="div"
/>
// If you are rendering the Parcel component from a one-spa application, you do not need to pass a mountParcel prop.
// But if you have a separate react component tree that is not rendered by one-spa-react, you **must** pass in a mountParcel prop
// In general, it is preferred to use an application's mountParcel function instead of the one-spa's root mountParcel function,
// so that one-spa can keep track of the parent-child relationship and automatically unmount the application's parcels when the application
// unmounts
<Parcel
mountParcel={oneSpa.mountParcel}
config={parcelConfig}
wrapWith="div"
/>
// Add styles to wrapWith element.
<Parcel
config={parcelConfig}
wrapWith="div"
wrapStyle={{ background: 'black' }}
/>
// Add classNames to wrapWith element.
<Parcel
config={parcelConfig}
wrapWith="div"
wrapClassName="wrapper"
/>
Create React App
See FAQ for CRA.