Parcels are an advanced feature of single-spa. Avoid using them until you have a better understanding of single-spa’s registerApplication api. A single-spa parcel is a framework agnostic component. It is a chunk of functionality meant to be mounted manually by an application, without having to worry about which framework was used to implement the parcel or application. Parcels use similar methodology as registered applications but are mounted by a manual function call rather than the activity function. A parcel can be as large as an application or as small as a component and written in any language as long as it exports the correct lifecycle events. In a single-spa world, your SPA contains many registered applications and potentially many parcels. Typically we recommend you mount a parcel within the context of an application because the parcel will be unmounted with the application.

If you are only using one framework, it is recommended to prefer framework components (i.e., React, Vue, and Angular components) over single-spa parcels. This is because framework components interop easier with each other than when there is an intermediate layer of single-spa parcels. You may import components between registered applications via import statements. You should only create a single-spa parcel if you need it to work with multiple frameworks. (More details)

Quick Example

  1. // The parcel implementation
  2. const parcelConfig = {
  3. bootstrap() {
  4. // one time initialization
  5. return Promise.resolve()
  6. },
  7. mount() {
  8. // use a framework to create dom nodes and mount the parcel
  9. return Promise.resolve()
  10. },
  11. unmount() {
  12. // use a framework to unmount dom nodes and perform other cleanup
  13. return Promise.resolve()
  14. }
  15. }
  16. // How to mount the parcel
  17. const domElement = document.getElementById('place-in-dom-to-mount-parcel')
  18. const parcelProps = {domElement, customProp1: 'foo'}
  19. const parcel = singleSpa.mountRootParcel(parcelConfig, parcelProps)
  20. // The parcel is being mounted. We can wait for it to finish with the mountPromise.
  21. parcel.mountPromise.then(() => {
  22. console.log('finished mounting parcel!')
  23. // If we want to re-render the parcel, we can call the update lifecycle method, which returns a promise
  24. parcelProps.customProp1 = 'bar'
  25. return parcel.update(parcelProps)
  26. })
  27. .then(() => {
  28. // Call the unmount lifecycle when we need the parcel to unmount. This function also returns a promise
  29. return parcel.unmount()
  30. })

Parcel configuration

A parcel is just an object with 3 or 4 functions on it. When mounting a parcel, you can provide either the object itself or a loading function that asynchronously downloads the parcel object. Each function on a parcel object is a lifecycle method, which is a function that returns a promise. Parcels have three required lifecycle methods (bootstrap, mount, and unmount) and one optional lifecycle method (update). When implementing a parcel, it’s strongly recommended that you use the lifecycle helper methods. An example of a parcel written in React would look like this:

  1. // myParcel.js
  2. import React from 'react'
  3. import ReactDom from 'react-dom'
  4. import singleSpaReact from 'single-spa-react'
  5. import MyParcelComponent from './my-parcel-component.component.js'
  6. export const MyParcel = singleSpaReact({
  7. React,
  8. ReactDom,
  9. rootComponent: MyParcelComponent
  10. })
  11. // in this case singleSpaReact is taking our inputs and generating an object with the required lifecycles.

Then to use the parcel you just created all you need to do is use the Parcel component provided in single-spa-react

  1. // mycomponent.js
  2. import Parcel from 'single-spa-react/parcel'
  3. import MyParcel from './myparcel.js'
  4. export class myComponent extends React.Component {
  5. render () {
  6. return (
  7. <Parcel
  8. config={MyParcel}
  9. { /* optional props */ }
  10. { /* and any extra props you want here */ }
  11. />
  12. )
  13. }
  14. }

Note that in some cases the optional props are required (see additional examples).

Parcel Lifecycles

Start with applications to learn more about the functionality of single-spa’s lifecycle methods.

Bootstrap

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

  1. function bootstrap(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // This is where you do one-time initialization
  6. console.log('bootstrapped!')
  7. });
  8. }

Mount

If the parcel is not mounted this lifecycle function is called when ever mountParcel is called. When called, this function should create DOM elements, DOM event listeners, etc. to render content to the user.

  1. function mount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // This is where you tell a framework (e.g., React) to render some ui to the dom
  6. console.log('mounted!')
  7. });
  8. }

Unmount

This lifecycle function will be called whenever the parcel is mounted and one of the following cases is true:

  • unmount() is called
  • The parent parcel or application is unmounted

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 parcel was mounted.

  1. function unmount(props) {
  2. return Promise
  3. .resolve()
  4. .then(() => {
  5. // This is where you tell a framework (e.g., React) to unrender some ui from the dom
  6. console.log('unmounted!');
  7. });
  8. }

Update (optional)

The update lifecycle function will be called whenever the user of the parcel calls parcel.update(). Single this lifecycle is optional, the user of a parcel needs to check whether the parcel has implemented the update lifecycle before attempting to make the call.

Example use cases

Modals

App1 handles everything related to contacts (highly cohesive) but somewhere in App2 we need to create a contact. We could do any number of things to share the functionality between application 1 and 2:

  • If both are written in the same framework we could export/import components.
  • We could reimplement creating a contact (loss of cohesion)
  • We could use single-spa parcels.

Exporting a parcel from App1 that wraps the createContact modal component gives us the ability to share components and behavior across disparate frameworks, without losing application cohesion. App1 can export a modal as a single-spa parcel and App2 can import the parcel and use it easily. One major advantage is that in the below example the parcel/modal from App1 that is being used by App2 will also be unmounted, without unmounting/mounting of App1.

  1. // App1
  2. export const AddContactParcel = {
  3. bootstrap: bootstrapFn,
  4. mount: mountFn,
  5. unmount: unmountFn,
  6. }
  7. // App2
  8. // get the parcel configuration in this case I'm using systemJS and react
  9. ...
  10. componentDidMount() {
  11. SystemJS.import('App1').then(App1 => {
  12. const domElement = document.body
  13. App2MountProps.mountParcel(App1.AddContactParcel, {domElement})
  14. })
  15. }
  16. ...

mountRootParcel vs mountParcel

Single spa exposes two APIs for working with parcels. These API’s are differentiated primarily by the context in which the parcel is created and how to access the API’s

mountRootParcel mountParcel
context singleSpa application
unmount condition manual only manual + application unmount
api location singleSpa named export provided in lifecycle prop

Which should I use?

In general we suggest using the application-aware mountParcel API. mountParcel allows you to treat the parcel just like a component inside your application without considering what framework it was written in and being forced to remember to call unmount.

How do I get the mountParcel API?

In order to keep the function contextually bound to an application it is provided to the application as a lifecycle prop. You will need to store and manage that function yourself in your application.

Example of storing the application specific mountParcel API:

  1. // App1
  2. let mountParcel
  3. export const bootstrap = [
  4. (props) => {
  5. mountParcel = props.mountParcel
  6. return Promise.resolve()
  7. },
  8. // more bootstrap lifecycles if necessary
  9. ]
  10. ...

note: some libraries (such as react) support a framework specific context that makes it easy to store/manage. In those cases we’ve written some helper methods to abstract away the need to manage and store the mountParcel method.