122.single-spa 01.微应用注册流程

86 阅读4分钟

registerApplication 配置项中的app需要传是什么东西?

registerApplication({
  name: "appVue2",
  app: async () => {
    return new Promise((resolve, reject) => {
      (function (factory) {
        typeof define === "function" && define.amd
          ? define(factory)
          : factory();
      })(function () {
        "use strict";

        function load() {
          return new Promise((resolve, reject) => {
            resolve({
              async mount() {
                console.log("load mount");
              },
              async bootstrap() {
                console.log("load bootstrap");
              },
            });
          });
        }

        load();
      });
    });
  },
  activeWhen: (location) => location.pathname.startsWith("/app-vue2"),
});

start();

registerApplication 方法

function registerApplication(
  appNameOrConfig,
  appOrLoadApp,
  activeWhen,
  customProps
  ) {
      //...省略一些其他的方法
      //管理应用,添加一些其他配置项
      apps.push(
        assign(
          {
            loadErrorTime: null,
            status: NOT_LOADED,
            parcels: {},
            devtools: {
              overlays: {
                options: {},
                selectors: [],
              },
            },
          },
          registration
        )
      );
      if (isInBrowser) {
        ensureJQuerySupport();
          //主要函数,路由重写
        reroute();
      }    
  }

reroute 方法,主要作用存储应用的声明周期方法

function reroute() {
  var pendingPromises =
    arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
  var eventArguments = arguments.length > 1 ? arguments[1] : undefined;
  if (appChangeUnderway) {
    return new Promise(function (resolve, reject) {
      peopleWaitingOnAppChange.push({
        resolve: resolve,
        reject: reject,
        eventArguments: eventArguments,
      });
    });
  }
  var _getAppChanges = getAppChanges(),
    appsToUnload = _getAppChanges.appsToUnload,
    appsToUnmount = _getAppChanges.appsToUnmount,
    appsToLoad = _getAppChanges.appsToLoad,
    appsToMount = _getAppChanges.appsToMount;
  var appsThatChanged,
    navigationIsCanceled = false,
    oldUrl = currentUrl,
    newUrl = (currentUrl = window.location.href);

  //调用了start方法后为true
  if (isStarted()) {
    appChangeUnderway = true;
    appsThatChanged = appsToUnload.concat(
      appsToLoad,
      appsToUnmount,
      appsToMount
    );
    return performAppChanges();
  } else {
    appsThatChanged = appsToLoad;
      //加载app
    return loadApps();
  }
}

performAppChanges方法,使用customEvent添加一些自定方法用来监听微应用的一些事件使用,初始化时所有新注册的应用都在loadThenMountPromises里面,然后会在toLoadPromise方法里面加载微应用信息,接下来看toLoadPromise方法

  function performAppChanges() {
    return Promise.resolve().then(function () {
     //...省略一些代码  customEvent注册自定义事件,通常浏览器addEventListener就是用来监听事件的
      var unloadPromises = appsToUnload.map(toUnloadPromise);
      var unmountUnloadPromises = appsToUnmount
        .map(toUnmountPromise)
        .map(function (unmountPromise) {
          return unmountPromise.then(toUnloadPromise);
        });
      var allUnmountPromises = unmountUnloadPromises.concat(unloadPromises);
      var unmountAllPromise = Promise.all(allUnmountPromises);
      unmountAllPromise.then(function () {
        window.dispatchEvent(
          new customEvent(
            "single-spa:before-mount-routing-event",
            getCustomEventDetail(true)
          )
        );
      });

      /* We load and bootstrap apps while other apps are unmounting, but we
       * wait to mount the app until all apps are finishing unmounting
       */
      var loadThenMountPromises = appsToLoad.map(function (app) {
          //加载应用
        return toLoadPromise(app).then(function (app) {
          return tryToBootstrapAndMount(app, unmountAllPromise);
        });
      });
  }

toLoadPromise方法,发现这边是个promise,promise里面是调用app.loadPromise的信息,app.loadPromise也是一个promise,这说明我们在调用注册微应用信息的方法时app 属性的值为promise,接下来看app.loadApp,loadApp就是传入的函数,smellsLikeAPromise也是验证app.loadApp的执行结果是否为promise,如果是promise则往下执行,可以看到这边返回的是loadPromise的执行结果。

必须说明的一点app.loadApp方法必须是umd格式的代码,如何将我们写的一段js代码打包成umd格式进行测试?

使用打包工具,我用的是roolup,使用方法,全局安装rollup,npm i -g roolup

然后使用命令rollup mount.js --file bundle.js --format umd --name "myBundle"

mount.js是我单独写的js文件为了方便打包,bundle.js是指定打包后的文件名, umd是指定打包格式, --name选项,导出的变量为myBundle

也可以参考roolup官网

function toLoadPromise(app) {
  return Promise.resolve().then(function () {
    if (app.loadPromise) {
      return app.loadPromise;
    }
    if (app.status !== NOT_LOADED && app.status !== LOAD_ERROR) {
      return app;
    }
    app.status = LOADING_SOURCE_CODE;
    var appOpts, isUserErr;
      //app.loadPromise -> registerApplication中app属性的值为一个异步函数
    return (app.loadPromise = Promise.resolve()
      .then(function () {
        //此处的app.loadApp就相当于见下面一段代码   loadPromise ->  return new Promise(xxx)应用打包结果需要封装为一个promise
        var loadPromise = app.loadApp(getProps(app));
         //验证加载微应用代码的函数是否为promise
        if (!smellsLikeAPromise(loadPromise)) {}
        return loadPromise.then(function (val) {
          app.loadErrorTime = null;
          appOpts = val;
          var validationErrMessage, validationErrCode;
			//...省略一些验证信息
          app.status = NOT_BOOTSTRAPPED;
          app.bootstrap = flattenFnArray(appOpts, "bootstrap");
          app.mount = flattenFnArray(appOpts, "mount");
          app.unmount = flattenFnArray(appOpts, "unmount");
          app.unload = flattenFnArray(appOpts, "unload");
          app.timeouts = ensureValidAppTimeouts(appOpts.timeouts);
          delete app.loadPromise;
          return app;
        });
      })
  });
}

其实加载的流程就是根据registerApplication的传参,执行app属性里面的代码,只不过在single-spa中里面的这部分代码乾坤已经帮我们封装了,我们只需要传入entry和应用访问地址,qiankun会使用import-html-entry会根据我们传的entry,解析出模板信息,css信息和script信息(这部分就不过多说了,之前有写过乾坤资源加载机制的文章,可以参考这篇文章),然后我们又使用打包工具webpack将其打包格式配置为umd格式帮我们做手动打包这部分工作。

在子系统接入微应用时我们需要在子应用中声明几个生命周期函数,qiankun就通过控制声明周期函数来控制微应用的挂载和卸载以此达到子应用切换的目的。

在single-spa内部发现并非有很多的逻辑代码,都是promise,通过注册的app,对其进行管理和控制。

registerApplication({
  name: "appVue2",
  app: async () => {
      //app.loadApp执行的实际就是该段代码,必须返回是umd格式的会有校验,否则会提示报错
    return new Promise((resolve, reject) => {
      (function (factory) {
        typeof define === "function" && define.amd
          ? define(factory)
          : factory();
      })(function () {
        "use strict";

        function load() {
          return new Promise((resolve, reject) => {
            resolve({
              async mount() {
                console.log("load mount");
              },
              async bootstrap() {
                console.log("load bootstrap");
              },
            });
          });
        }
        load();
      });
    });
  },
  activeWhen: (location) => location.pathname.startsWith("/app-vue2"),
});