A single-spa child 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 child applications, where each has its own framework. Child 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 child application is putting content on the DOM or not. What determines if a child application is mounted is its activity function. Whenever a child application is not mounted, it should remain completely dormant until mounted.
To create a child application, first register the application with single-spa. Once registered, the child application must correctly implement all of the following lifecycle functions inside of its main entry point.
During the course of a single-spa page, child applications are loaded, initialized (bootstrapped), 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 child application. Single-spa calls these by finding exported functions from the child application's main file.
Notes:
- Lifecycle functions are called with a
props
argument, which is an object with achildAppName
string. - Implementing
bootstrap
,mount
, andunmount
is required. But implementingunload
is optional. - Each lifecycle function must either return a
Promise
or be anasync 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.
Middleware that helps implement lifecycle functions for specific frameworks, libraries, and applications is available for many popular technologies. See the ecosystem docs for details.
When child applications are being lazily loaded, this refers to when the code for a child application
is fetched from the server and executed. This will happen once the child 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 child application's main entry point,
but not inside of an exported function.
For example:
console.log("The child application has been loaded!");
export async function bootstrap(props) {...}
export async function mount(props) {...}
export async function unmount(props) {...}
This lifecycle function will be called once, right before the child application is mounted for the first time.
export function bootstrap(props) {
return Promise
.resolve()
.then(() => {
// This is where you do one-time initialization
console.log('bootstrapped!')
});
}
This lifecycle function will be called whenever the child 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 childl application itself.
export function mount(props) {
return Promise
.resolve()
.then(() => {
// This is where you tell a framework (e.g., React) to render some ui to the dom
console.log('mounted!')
});
}
This lifecycle function will be called whenever the child 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 child application was mounted.
export function unmount(props) {
return Promise
.resolve()
.then(() => {
// This is where you tell a framework (e.g., React) to unrender some ui from the dom
console.log('unmounted!');
});
}
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 unloadChildApplication
API.
If a child 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 child 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.
export function unload(props) {
return Promise
.resolve()
.then(() => {
// This is where you would normally do whatever it is you need to hot reload the code.
console.log('unloaded!');
});
}
By default, child applications obey the global dieOnTimeout 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 child application. Example:
// app-1.main-entry.js
export function bootstrap(props) {...}
export function mount(props) {...}
export function unmount(props) {...}
export const timeouts = {
bootstrap: {
millis: 5000,
dieOnTimeout: true,
},
mount: {
millis: 5000,
dieOnTimeout: false,
},
unmount: {
millis: 5000,
dieOnTimeout: true,
},
unload: {
millis: 5000,
dieOnTimeout: true,
},
};