Single-spa events

In the previous sharing of navigation-events and Reroute, there are actually a lot of events triggered. Here we will further introduce the events in single-SPA. Single-spa sends different events at different stages of route navigation and in different state processing of the application; By understanding and handling these events, we can add some custom functionality.

In single-SPA, events are roughly divided into the following two categories:

Native events

In navigation-events, single-SPA implements listening for the browser’s route navigation events:

  • popstate
  • hashchange
  • pushstate
  • replacestate

Custom popState events

Single-spa adds some custom properties to native PopState events:

  • SingleSpa: Indicates that the popState event was triggered by single-SPA
  • SingleSpaTrigger: Identifies the original method name that this PopState event triggered
function createPopStateEvent(state, originalMethodName) {
  let evt;
  try {
    evt = new PopStateEvent("popstate", { state });
  } catch (err) {
    evt = document.createEvent("PopStateEvent");
    evt.initPopStateEvent("popstate".false.false, state);
  }
  // Add singleSpa and singleSpaTrigger attributes to native PopState events
  evt.singleSpa = true;
  evt.singleSpaTrigger = originalMethodName;
  return evt;
}
Copy the code

trigger

This method is triggered when the following method is called:

  • history.pushState
  • history.replaceState
  • TriggerAppChange may trigger callCapturedEventListeners single – such as spa custom method

We can listen to the popState event to see if it was triggered by a single-SPA:

// This is a piece of code in the official document
window.addEventListener('popstate'.evt= > {
  if (evt.singleSpa) {
    console.log(
      'This event was fired by single-spa to forcibly trigger a re-render',);console.log(evt.singleSpaTrigger); // pushState | replaceState
  } else {
    console.log('This event was fired by native browser behavior'); }});Copy the code

Custom Events

In the execution of the core method, Reroute, single-spa triggers a series of custom events with a fixed event name format: single-spa:event-name. For the input parameters of these custom events, single-SPA is encapsulated by the getCustomEventDetail method, providing a unified event interface.

  • Event encapsulation and dispatch

Single-spa’s encapsulation of custom events is based on a library called Custom-Event, which provides cross-browser support for custom events; Created events are dispatched using window.dispatchEvent, similar to:

window.dispatchEvent('single-spa:event-name'.new CustomEvent(/ * * /))
Copy the code

For more information on custom-events, see MDN CustomEvent.

  • Event listeners

These events can be treated as if they were native to the browser and handled by event listening:

window.addEventListener('single-spa:event-name'./* callback to custom Event detail */)
Copy the code

The event list

Sequence of events The name of the event trigger
1 single-spa:before-app-change / single-spa:before-no-app-change When you enter the Reroute method, it triggers according to the number of applications that have changed; Corresponds to event sequence 6
2 single-spa:before-routing-event Every time reroute starts, it must happen, corresponding to event 7
3 single-spa:before-mount-routing-event After the URL is released and the old application is uninstalled, this event is triggered to load the application
4 single-spa:before-first-mount This event is triggered before an application is mounted for the first time.This event will only be triggered once, defined inmount.jsIn the
5 single-spa:first-mount This event is triggered after an application is first mounted.This event will only be triggered once, which is defined inmount.jsIn the
6 single-spa:app-change / single-spa:no-app-change Corresponds to event sequence 1
7 single-spa:routing-event Corresponding to event 2, occurs at the end of Reroute

Custom event triggering process

Get event details — getCustomEventDetail

In single-SPA, the unified encapsulation of event details of Custom Events is realized by this method. The specific implementation is as follows:


  function getCustomEventDetail(isBeforeChanges = false, extraProperties) {
    const newAppStatuses = {};
    const appsByNewStatus = {
      // for apps that were mounted
      [MOUNTED]: [],
      // for apps that were unmounted
      [NOT_MOUNTED]: [],
      // apps that were forcibly unloaded
      [NOT_LOADED]: [],
      // apps that attempted to do something but are broken now
      [SKIP_BECAUSE_BROKEN]: [],
    };

    if (isBeforeChanges) {
      appsToLoad.concat(appsToMount).forEach((app, index) = > {
        addApp(app, MOUNTED);
      });
      appsToUnload.forEach((app) = > {
        addApp(app, NOT_LOADED);
      });
      appsToUnmount.forEach((app) = > {
        addApp(app, NOT_MOUNTED);
      });
    } else {
      appsThatChanged.forEach((app) = > {
        addApp(app);
      });
    }

    const result = {
      detail: {
        // Hash of the application status. Key is the application name and value is the application status
        newAppStatuses,
        // Hash of applications corresponding to each state. Key is the application state and value is the application list of the corresponding state
        appsByNewStatus,
        // The number of applications whose state has changed
        totalAppChanges: appsThatChanged.length,
        // Initial event information
        originalEvent: eventArguments? .0].// The original URL
        oldUrl,
        // The url to which to navigate
        newUrl,
        // Whether the navigation is cancelled
        navigationIsCanceled,
      },
    };

    if (extraProperties) {
      assign(result.detail, extraProperties);
    }

    return result;
    function addApp(app, status) {
      const appName = toName(app);
      status = status || getAppStatus(appName);
      newAppStatuses[appName] = status;
      conststatusArr = (appsByNewStatus[status] = appsByNewStatus[status] || []); statusArr.push(appName); }}Copy the code

Custom event usage

Example scenario

  • Cancel the navigation

In the single-spa:before-routing-event, the cancelNavigation method is passed as an input to the event. Call this method to cancel the routing:

// route.js
window.dispatchEvent(
  new CustomEvent(
    "single-spa:before-routing-event",
      getCustomEventDetail(true, { cancelNavigation })
  )
);

function cancelNavigation() {
  navigationIsCanceled = true;
}
Copy the code
// This is a piece of code in the official document; If you listen for the event and cancelNavigation is called, you can cancel the navigation
window.addEventListener(
  'single-spa:before-routing-event'.({ detail: { oldUrl, newUrl, cancelNavigation } }) = > {
    if (
      new URL(oldUrl).pathname === '/route1' &&
      new URL(newUrl).pathname === '/route2') { cancelNavigation(); }});Copy the code
CancelNavigation will set navigationIsCanceled = false to end this routing and navigate back to the previous URL
if (navigationIsCanceled) {
  window.dispatchEvent(
    new CustomEvent(
      "single-spa:before-mount-routing-event",
        getCustomEventDetail(true))); finishUpAndReturn(); navigateToUrl(oldUrl);return;
}
Copy the code

In The well-known micro-front-end framework Qiankun based on single-SPA package, there are also some custom processing based on events:

  • Set the default mount micro front-end application
/ / reference https://qiankun.umijs.org/zh/api#setdefaultmountappapplink
export function setDefaultMountApp(defaultAppLink: string) {
  window.addEventListener('single-spa:no-app-change'.function listener() {
    const mountedApps = getMountedApps();
    if(! mountedApps.length) { navigateToUrl(defaultAppLink); }window.removeEventListener('single-spa:no-app-change', listener);
  });
}
Copy the code
  • If you want to do monitoring/burying after loading the micro front end application, or you need to prefetch the micro front end
/ / reference https://qiankun.umijs.org/zh/api#runafterfirstmountedeffect
export function runAfterFirstMounted(effect: () => void) {
  // can not use addEventListener once option for ie support
  window.addEventListener('single-spa:first-mount'.function listener() {
    if (process.env.NODE_ENV === 'development') {
      console.timeEnd(firstMountLogLabel);
    }
    effect();
    window.removeEventListener('single-spa:first-mount', listener);
  });
}
Copy the code
/ / if in the start parameter configuration prefetch, or manually invoked prefetchApps, will trigger prefetchAfterFirstMounted
function prefetchAfterFirstMounted(apps: AppMetadata[], opts? : ImportEntryOpts) :void {
  window.addEventListener('single-spa:first-mount'.function listener() {
    const notLoadedApps = apps.filter((app) = > getAppStatus(app.name) === NOT_LOADED);
    if (process.env.NODE_ENV === 'development') {
      const mountedApps = getMountedApps();
      console.log(`[qiankun] prefetch starting after ${mountedApps}mounted... `, notLoadedApps);
    }
    notLoadedApps.forEach(({ entry }) = > prefetch(entry, opts));
    window.removeEventListener('single-spa:first-mount', listener);
  });
}
Copy the code

The resources

Events

custom-event

qiankun