Version: 5.x

Building single-spa applications

A single-spa registered application is everything that a normal SPA is, except that it doesn’t have an HTML page. In a single-spa world, your SPA contains many registered applications, where each has its own framework. Registered applications have their own client-side routing and their own frameworks/libraries. They render their own HTML and have full freedom to do whatever they want, whenever they are mounted. The concept of being mounted refers to whether a registered application is putting content on the DOM or not. What determines if a registered application is mounted is its activity function. Whenever a registered application is not mounted, it should remain completely dormant until mounted.

To create a registered application, first register the application with single-spa. Once registered, the registered application must correctly implement all of the following lifecycle functions inside of its main entry point.

During the course of a single-spa page, registered applications are loaded, bootstrapped (initialized), mounted, unmounted, and unloaded. single-spa provides hooks into each phase via lifecycles.

A lifecycle function is a function or array of functions that single-spa will call on a registered application. single-spa calls these by finding specific named exports from the registered application’s main file.

Notes:

  • Implementing bootstrap, mount, and unmount is required. But implementing unload is optional.
  • Each lifecycle function must either return a Promise or be an async function.
  • If an array of functions is exported (instead of just one function), the functions will be called one-after-the-other, waiting for the resolution of one function’s promise before calling the next.
  • If single-spa is not started, applications will be loaded, but will not be bootstrapped, mounted or unmounted.
single-spa applications - 图1info

Framework-specific helper libraries exist in the single-spa ecosystem to implement these required lifecycle methods. This documentation is helpful for understanding what those helpers are doing, or for implementing your own.

Lifecycle functions are called with a props argument, which is an object with some guaranteed information and additional custom information.

  1. function bootstrap(props) {
  2. const {
  3. name, // The name of the application
  4. singleSpa, // The singleSpa instance
  5. mountParcel, // Function for manually mounting
  6. customProps, // Additional custom information
  7. } = props; // Props are given to every lifecycle
  8. return Promise.resolve();
  9. }

Each lifecycle function is guaranteed to be called with the following props:

  • name: The string name that was registered to single-spa.
  • singleSpa: A reference to the singleSpa instance, itself. This is intended to allow applications and helper libraries to call singleSpa APIs without having to import it. This is useful in situations where there are multiple webpack configs that are not set up to ensure that only one instance of singleSpa is loaded.
  • mountParcel: The mountParcel function.

In addition to the built-in props that are provided by single-spa, you may optionally specify custom props to be passed to an application. These customProps will be passed into each lifecycle method. The custom props are an object, and you can provide either the object or a function that returns the object. Custom prop functions are called with the application name and current window.location as arguments.

root-config.js

  1. singleSpa.registerApplication({
  2. name: 'app1',
  3. activeWhen,
  4. app,
  5. customProps: { authToken: 'd83jD63UdZ6RS6f70D0' },
  6. });
  7. singleSpa.registerApplication({
  8. name: 'app1',
  9. activeWhen,
  10. app,
  11. customProps: (name, location) => {
  12. return { authToken: 'd83jD63UdZ6RS6f70D0' };
  13. },
  14. });

app1.js

  1. export function mount(props) {
  2. // do something with the common authToken in app1
  3. console.log(props.authToken);
  4. return reactLifecycles.mount(props);
  5. }

Some use cases could be to:

  • share a common access token with all child apps
  • pass down some initialization information, like the rendering target
  • pass a reference to a common event bus so each app may talk to each other

Note that when no customProps are provided during registration, props.customProps defaults to an empty object.

Some helper libraries that implement lifecycle functions for ease of use are available for many popular frameworks/libraries. Learn more on the Ecosystem page.

When registered applications are being lazily loaded, this refers to when the code for a registered application is fetched from the server and executed. This will happen once the registered application’s activity function returns a truthy value for the first time. It is best practice to do as little as possible / nothing at all during load, but instead to wait until the bootstrap lifecycle function to do anything. If you need to do something during load, simply put the code into a registered application’s main entry point, but not inside of an exported function. For example:

  1. console.log("The registered application has been loaded!");
  2. export async function bootstrap(props) {...}
  3. export async function mount(props) {...}
  4. export async function unmount(props) {...}

This lifecycle function will be called once, right before the registered application is mounted for the first time.

  1. export function bootstrap(props) {
  2. return Promise.resolve().then(() => {
  3. // One-time initialization code goes here
  4. console.log('bootstrapped!');
  5. });
  6. }

This lifecycle function will be called whenever the registered application is not mounted, but its activity function returns a truthy value. When called, this function should look at the URL to determine the active route and then create DOM elements, DOM event listeners, etc. to render content to the user. Any subsequent routing events (such as hashchange and popstate) will not trigger more calls to mount, but instead should be handled by the application itself.

  1. export function mount(props) {
  2. return Promise.resolve().then(() => {
  3. // Do framework UI rendering here
  4. console.log('mounted!');
  5. });
  6. }

This lifecycle function will be called whenever the registered application is mounted, but its activity function returns a falsy value. When called, this function should clean up all DOM elements, DOM event listeners, leaked memory, globals, observable subscriptions, etc. that were created at any point when the registered application was mounted.

  1. export function unmount(props) {
  2. return Promise.resolve().then(() => {
  3. // Do framework UI unrendering here
  4. console.log('unmounted!');
  5. });
  6. }

The unload lifecycle is an optionally implemented lifecycle function. It will be called whenever an application should be unloaded. This will not ever happen unless someone calls the unloadApplication API. If a registered application does not implement the unload lifecycle, then it assumed that unloading the app is a no-op.

The purpose of the unload lifecycle is to perform logic right before a single-spa application is unloaded. Once the application is unloaded, the application status will be NOT_LOADED and the application will be re-bootstrapped.

The motivation for unload was to implement the hot-loading of entire registered applications, but it is useful in other scenarios as well when you want to re-bootstrap applications, but perform some logic before applications are re-bootstrapped.

  1. export function unload(props) {
  2. return Promise.resolve().then(() => {
  3. // Hot-reloading implementation goes here
  4. console.log('unloaded!');
  5. });
  6. }

By default, registered applications obey the global timeout configuration, but can override that behavior for their specific application. This is done by exporting a timeouts object from the main entry point of the registered application. Example:

app-1.js

  1. export function bootstrap(props) {...}
  2. export function mount(props) {...}
  3. export function unmount(props) {...}
  4. export const timeouts = {
  5. bootstrap: {
  6. millis: 5000,
  7. dieOnTimeout: true,
  8. warningMillis: 2500,
  9. },
  10. mount: {
  11. millis: 5000,
  12. dieOnTimeout: false,
  13. warningMillis: 2500,
  14. },
  15. unmount: {
  16. millis: 5000,
  17. dieOnTimeout: true,
  18. warningMillis: 2500,
  19. },
  20. unload: {
  21. millis: 5000,
  22. dieOnTimeout: true,
  23. warningMillis: 2500,
  24. },
  25. };

Note that millis refers to the number of milliseconds for the final console warning, and warningMillis refers to the number of milliseconds at which a warning will be printed to the console (on an interval) leading up to the final console warning.

If you find yourself wanting to add transitions as applications are mounted and unmounted, then you’ll probably want to tie into the bootstrap, mount, and unmount lifecycle methods. This single-spa transitions repo is a small proof-of-concept of how you can tie into these lifecycle methods to add transitions as your apps mount and unmount.

Transitions for pages within a mounted application can be handled entirely by the application itself. For example, using react-transition-group for React-based projects.

single-spa applications - 图2Edit this page