qiankun源码解析 
qiankun的官网 https://qiankun.umijs.org/zh

qiankun的优势:
- 基于 single-spa 封装,提供了更加开箱即用的 API。
 - 技术栈无关,任意技术栈的应用均可 使用/接入,不论是 React/Vue/Angular/JQuery 还是其他等框架。
 - HTML Entry 接入方式,让你接入微应用像使用 iframe 一样简单。
 - 样式隔离,确保微应用之间样式互相不干扰。
 - JS 沙箱,确保微应用之间 全局变量/事件 不冲突。
 - 资源预加载,在浏览器空闲时间预加载未打开的微应用资源,加速微应用打开速度。
 
首先我们先去Github下载qiankun的源码 https://github.com/umijs/qiankun ,整个工程采用TS来编写的。

应用注册 
首先我们从index.ts入口找到应用注册的方法registerMicroApps,它被定义在apis.ts中。
//src/apis.ts
import { mountRootParcel, registerApplication, start as startSingleSpa } from 'single-spa';
export function registerMicroApps<T extends ObjectType>(
  apps: Array<RegistrableApp<T>>,
  lifeCycles?: FrameworkLifeCycles<T>,
) {
  // Each app only needs to be registered once
  //已经注册过的子应用不需要再次注册,过滤掉已注册的内容
  const unregisteredApps = apps.filter((app) => !microApps.some((registeredApp) => registeredApp.name === app.name));
  //获取子应用的总数量,以进行下次判断
  microApps = [...microApps, ...unregisteredApps];
  unregisteredApps.forEach((app) => {
    const { name, activeRule, loader = noop, props, ...appConfig } = app;
    registerApplication({
      name,
      app: async () => {
        loader(true);
        await frameworkStartedDefer.promise; 
        const { mount, ...otherMicroAppConfigs } = (
          await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
        )();
        return {
          mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
          ...otherMicroAppConfigs,
        };
      },
      activeWhen: activeRule,
      customProps: props,
    });
  });
}registerMicroApps有两个参数,第一个是子应用列表,第二个传递过来的生命周期的方法。然后先过滤一遍已注册的应用,然后再调用single-spa的registerApplication方法去进行注册。可以看出子应用的注册和路由变化是用的single-spa的方法,qiankun基于single-spa进行了一层封装,提供了更为强大的功能。然后注册完应用后调用start去启动微前端框架。
//src/apis.ts
import { mountRootParcel, registerApplication, start as startSingleSpa } from 'single-spa';
export function start(opts: FrameworkConfiguration = {}) {
  frameworkConfiguration = { prefetch: true, singular: true, sandbox: true, ...opts };
  const {
    prefetch,
    sandbox,
    singular,
    urlRerouteOnly = defaultUrlRerouteOnly,
    ...importEntryOpts
  } = frameworkConfiguration;
  if (prefetch) {
    doPrefetchStrategy(microApps, prefetch, importEntryOpts);
  }
  frameworkConfiguration = autoDowngradeForLowVersionBrowser(frameworkConfiguration);
  startSingleSpa({ urlRerouteOnly });
  started = true;
  frameworkStartedDefer.resolve();
}其中也是调用sing-spa的start方法来实现的。在start方法中可以看到qiankun提供了一些预加载、沙箱、单实例等功能。
应用加载 
在调用registerApplication方法中,我们可以看到调用了loadApp,这个是qiankun提供的加载子应用的方法。
registerApplication({
    name,
    app: async () => {
    loader(true);
    await frameworkStartedDefer.promise; 
    const { mount, ...otherMicroAppConfigs } = (
        await loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles)
    )();
    return {
        mount: [async () => loader(true), ...toArray(mount), async () => loader(false)],
        ...otherMicroAppConfigs,
    };
    },
    activeWhen: activeRule,
    customProps: props,
});可以看到这个方法有三个参数,对应的子应用、配置参数、生命周期。
//src/loader.ts
export async function loadApp<T extends ObjectType>(
  app: LoadableApp<T>,
  configuration: FrameworkConfiguration = {},
  lifeCycles?: FrameworkLifeCycles<T>,
): Promise<ParcelConfigObjectGetter> {
  //获取子应用的入口和名称
  const { entry, name: appName } = app;
  //生成唯一的id
  const appInstanceId = genAppInstanceIdByName(appName);
  const markName = `[qiankun] App ${appInstanceId} Loading`;
  if (process.env.NODE_ENV === 'development') {
    performanceMark(markName);
  }
  const {
    singular = false,
    sandbox = true,
    excludeAssetFilter,
    globalContext = window,
    ...importEntryOpts
  } = configuration;
  // get the entry html content and script executor
  //获取应用入口,可能是一个html或者一个js
  const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts);
  // as single-spa load and bootstrap new app parallel with other apps unmounting
  // (see https://github.com/CanopyTax/single-spa/blob/master/src/navigation/reroute.js#L74)
  // we need wait to load the app until all apps are finishing unmount in singular mode
  if (await validateSingularMode(singular, app)) {
    await (prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise);
  }
  //获取子应用渲染的内容
  const appContent = getDefaultTplWrapper(appInstanceId)(template);
  //处理子应用样式
  const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;
  if (process.env.NODE_ENV === 'development' && strictStyleIsolation) {
    console.warn(
      "[qiankun] strictStyleIsolation configuration will be removed in 3.0, pls don't depend on it or use experimentalStyleIsolation instead!",
    );
  }
  const scopedCSS = isEnableScopedCSS(sandbox);
  let initialAppWrapperElement: HTMLElement | null = createElement(
    appContent,
    strictStyleIsolation,
    scopedCSS,
    appInstanceId,
  );
 
  //设置应用渲染容器
  const initialContainer = 'container' in app ? app.container : undefined;
  const legacyRender = 'render' in app ? app.render : undefined;
  //获取子应用render函数
  const render = getRender(appInstanceId, appContent, legacyRender);
  // 第一次加载设置应用可见区域 dom 结构
  // 确保每次应用加载前容器 dom 结构已经设置完毕
  //这一步执行子应用的render函数
  render({ element: initialAppWrapperElement, loading: true, container: initialContainer }, 'loading');
  const initialAppWrapperGetter = getAppWrapperGetter(
    appInstanceId,
    !!legacyRender,
    strictStyleIsolation,
    scopedCSS,
    () => initialAppWrapperElement,
  );
  let global = globalContext;
  let mountSandbox = () => Promise.resolve();
  let unmountSandbox = () => Promise.resolve();
  const useLooseSandbox = typeof sandbox === 'object' && !!sandbox.loose;
  const speedySandbox = typeof sandbox === 'object' && !!sandbox.speedy;
  let sandboxContainer;
  if (sandbox) {
    sandboxContainer = createSandboxContainer(
      appInstanceId,
      // FIXME should use a strict sandbox logic while remount, see https://github.com/umijs/qiankun/issues/518
      initialAppWrapperGetter,
      scopedCSS,
      useLooseSandbox,
      excludeAssetFilter,
      global,
      speedySandbox,
    );
    // 用沙箱的代理对象作为接下来使用的全局对象
    global = sandboxContainer.instance.proxy as typeof window;
    mountSandbox = sandboxContainer.mount;
    unmountSandbox = sandboxContainer.unmount;
  }
  const {
    beforeUnmount = [],
    afterUnmount = [],
    afterMount = [],
    beforeMount = [],
    beforeLoad = [],
  } = mergeWith({}, getAddOns(global, assetPublicPath), lifeCycles, (v1, v2) => concat(v1 ?? [], v2 ?? []));
  await execHooksChain(toArray(beforeLoad), app, global);
  // get the lifecycle hooks from module exports
  //获取子应用的生命周期钩子
  const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox, {
    scopedGlobalVariables: speedySandbox ? lexicalGlobals : [],
  });
  const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
    scriptExports,
    appName,
    global,
    sandboxContainer?.instance?.latestSetProp,
  );
  //获取子应用的state全局项
  const { onGlobalStateChange, setGlobalState, offGlobalStateChange }: Record<string, CallableFunction> =
    getMicroAppStateActions(appInstanceId);
  // FIXME temporary way
  const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
  const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer = initialContainer) => {
    let appWrapperElement: HTMLElement | null;
    let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;
    const parcelConfig: ParcelConfigObject = {
      name: appInstanceId,
      bootstrap,
      mount: [
        async () => {
          if (process.env.NODE_ENV === 'development') {
            const marks = performanceGetEntriesByName(markName, 'mark');
            // mark length is zero means the app is remounting
            if (marks && !marks.length) {
              performanceMark(markName);
            }
          }
        },
        async () => {
          if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) {
            return prevAppUnmountedDeferred.promise;
          }
          return undefined;
        },
        // initial wrapper element before app mount/remount
        async () => {
          appWrapperElement = initialAppWrapperElement;
          appWrapperGetter = getAppWrapperGetter(
            appInstanceId,
            !!legacyRender,
            strictStyleIsolation,
            scopedCSS,
            () => appWrapperElement,
          );
        },
        // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕
        async () => {
          const useNewContainer = remountContainer !== initialContainer;
          if (useNewContainer || !appWrapperElement) {
            // element will be destroyed after unmounted, we need to recreate it if it not exist
            // or we try to remount into a new container
            appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceId);
            syncAppWrapperElement2Sandbox(appWrapperElement);
          }
          render({ element: appWrapperElement, loading: true, container: remountContainer }, 'mounting');
        },
        mountSandbox,
        // exec the chain after rendering to keep the behavior with beforeLoad
        async () => execHooksChain(toArray(beforeMount), app, global),
        async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }),
        // finish loading after app mounted
        async () => render({ element: appWrapperElement, loading: false, container: remountContainer }, 'mounted'),
        async () => execHooksChain(toArray(afterMount), app, global),
        // initialize the unmount defer after app mounted and resolve the defer after it unmounted
        async () => {
          if (await validateSingularMode(singular, app)) {
            prevAppUnmountedDeferred = new Deferred<void>();
          }
        },
        async () => {
          if (process.env.NODE_ENV === 'development') {
            const measureName = `[qiankun] App ${appInstanceId} Loading Consuming`;
            performanceMeasure(measureName, markName);
          }
        },
      ],
      unmount: [
        async () => execHooksChain(toArray(beforeUnmount), app, global),
        async (props) => unmount({ ...props, container: appWrapperGetter() }),
        unmountSandbox,
        async () => execHooksChain(toArray(afterUnmount), app, global),
        async () => {
          render({ element: null, loading: false, container: remountContainer }, 'unmounted');
          offGlobalStateChange(appInstanceId);
          // for gc
          appWrapperElement = null;
          syncAppWrapperElement2Sandbox(appWrapperElement);
        },
        async () => {
          if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) {
            prevAppUnmountedDeferred.resolve();
          }
        },
      ],
    };
    if (typeof update === 'function') {
      parcelConfig.update = update;
    }
    return parcelConfig;
  };
  return parcelConfigGetter;
}首先获取子应用的入口和名称,然后生成唯一的id,通过传进来的configuration获取对应的配置,通过importEntry获取对应的template、execScripts和assetPublicPath路径,然后将template内容调用getDefaultTplWrapper注入到div中,这样我们获取了一个container。接着调用createElement将内容加载进来。
//src/loader.ts
function createElement(
  appContent: string,
  strictStyleIsolation: boolean,
  scopedCSS: boolean,
  appInstanceId: string,
): HTMLElement {
  const containerElement = document.createElement('div');
  containerElement.innerHTML = appContent;
  // appContent always wrapped with a singular div
  const appElement = containerElement.firstChild as HTMLElement;
  if (strictStyleIsolation) {
    if (!supportShadowDOM) {
      console.warn(
        '[qiankun]: As current browser not support shadow dom, your strictStyleIsolation configuration will be ignored!',
      );
    } else {
      const { innerHTML } = appElement;
      appElement.innerHTML = '';
      let shadow: ShadowRoot;
      if (appElement.attachShadow) {
        shadow = appElement.attachShadow({ mode: 'open' });
      } else {
        // createShadowRoot was proposed in initial spec, which has then been deprecated
        shadow = (appElement as any).createShadowRoot();
      }
      shadow.innerHTML = innerHTML;
    }
  }
  if (scopedCSS) {
    const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
    if (!attr) {
      appElement.setAttribute(css.QiankunCSSRewriteAttr, appInstanceId);
    }
    const styleNodes = appElement.querySelectorAll('style') || [];
    forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
      css.process(appElement!, stylesheetElement, appInstanceId);
    });
  }
  return appElement;
}createElement中看是否开启strictStyleIsolation,然后判断是否支持shadowDOM,支持则使用shadowDOM渲染,最终返回appElement。接着获取子应用的render函数,然后再执行render函数,接着创建sandbox沙箱环境,将沙箱添加到运行环境中,最后运行一下子应用对应的生命周期方法。
沙箱隔离 
在代码中可以看到对于沙箱的创建是通过createSandboxContainer来创建的。
//src/sandbox/index.ts
export function createSandboxContainer(
  appName: string,
  elementGetter: () => HTMLElement | ShadowRoot,
  scopedCSS: boolean,
  useLooseSandbox?: boolean,
  excludeAssetFilter?: (url: string) => boolean,
  globalContext?: typeof window,
  speedySandBox?: boolean,
) {
  let sandbox: SandBox;
  if (window.Proxy) {
    sandbox = useLooseSandbox ? new LegacySandbox(appName, globalContext) : new ProxySandbox(appName, globalContext);
  } else {
    sandbox = new SnapshotSandbox(appName);
  }
  // some side effect could be be invoked while bootstrapping, such as dynamic stylesheet injection with style-loader, especially during the development phase
  const bootstrappingFreers = patchAtBootstrapping(
    appName,
    elementGetter,
    sandbox,
    scopedCSS,
    excludeAssetFilter,
    speedySandBox,
  );
  // mounting freers are one-off and should be re-init at every mounting time
  let mountingFreers: Freer[] = [];
  let sideEffectsRebuilders: Rebuilder[] = [];
  return {
    instance: sandbox,
    /**
     * 沙箱被 mount
     * 可能是从 bootstrap 状态进入的 mount
     * 也可能是从 unmount 之后再次唤醒进入 mount
     */
    async mount() {
      /* ------------------------------------------ 因为有上下文依赖(window),以下代码执行顺序不能变 ------------------------------------------ */
      /* ------------------------------------------ 1. 启动/恢复 沙箱------------------------------------------ */
      sandbox.active();
      const sideEffectsRebuildersAtBootstrapping = sideEffectsRebuilders.slice(0, bootstrappingFreers.length);
      const sideEffectsRebuildersAtMounting = sideEffectsRebuilders.slice(bootstrappingFreers.length);
      // must rebuild the side effects which added at bootstrapping firstly to recovery to nature state
      if (sideEffectsRebuildersAtBootstrapping.length) {
        sideEffectsRebuildersAtBootstrapping.forEach((rebuild) => rebuild());
      }
      /* ------------------------------------------ 2. 开启全局变量补丁 ------------------------------------------*/
      // render 沙箱启动时开始劫持各类全局监听,尽量不要在应用初始化阶段有 事件监听/定时器 等副作用
      mountingFreers = patchAtMounting(appName, elementGetter, sandbox, scopedCSS, excludeAssetFilter, speedySandBox);
      /* ------------------------------------------ 3. 重置一些初始化时的副作用 ------------------------------------------*/
      // 存在 rebuilder 则表明有些副作用需要重建
      if (sideEffectsRebuildersAtMounting.length) {
        sideEffectsRebuildersAtMounting.forEach((rebuild) => rebuild());
      }
      // clean up rebuilders
      sideEffectsRebuilders = [];
    },
    /**
     * 恢复 global 状态,使其能回到应用加载之前的状态
     */
    async unmount() {
      // record the rebuilders of window side effects (event listeners or timers)
      // note that the frees of mounting phase are one-off as it will be re-init at next mounting
      sideEffectsRebuilders = [...bootstrappingFreers, ...mountingFreers].map((free) => free());
      sandbox.inactive();
    },
  };
}首先会判断是否支持Proxy,支持则使用LegacySandbox或者ProxySandbox,否则使用快照沙箱SnapshotSandbox。 先看一下SnapshotSandbox的实现:
/**
 * 基于 diff 方式实现的沙箱,用于不支持 Proxy 的低版本浏览器
 */
export default class SnapshotSandbox implements SandBox {
  proxy: WindowProxy;
  name: string;
  type: SandBoxType;
  sandboxRunning = true;
  private windowSnapshot!: Window;
  private modifyPropsMap: Record<any, any> = {};
  constructor(name: string) {
    this.name = name;
    this.proxy = window;
    this.type = SandBoxType.Snapshot;
  }
  active() {
    // 记录当前快照
    this.windowSnapshot = {} as Window;
    iter(window, (prop) => {
      this.windowSnapshot[prop] = window[prop];
    });
    // 恢复之前的变更
    Object.keys(this.modifyPropsMap).forEach((p: any) => {
      window[p] = this.modifyPropsMap[p];
    });
    this.sandboxRunning = true;
  }
  inactive() {
    this.modifyPropsMap = {};
    iter(window, (prop) => {
      if (window[prop] !== this.windowSnapshot[prop]) {
        // 记录变更,恢复环境
        this.modifyPropsMap[prop] = window[prop];
        window[prop] = this.windowSnapshot[prop];
      }
    });
    if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] ${this.name} origin window restore...`, Object.keys(this.modifyPropsMap));
    }
    this.sandboxRunning = false;
  }
}主要由active和inactive两种方法,在active记录当前的变更,在inactive恢复。
//src/sandbox/Legacy/sandbox.ts
/**
 * 基于 Proxy 实现的沙箱
 * TODO: 为了兼容性 singular 模式下依旧使用该沙箱,等新沙箱稳定之后再切换
 */
export default class LegacySandbox implements SandBox {
  /** 沙箱期间新增的全局变量 */
  private addedPropsMapInSandbox = new Map<PropertyKey, any>();
  /** 沙箱期间更新的全局变量 */
  private modifiedPropsOriginalValueMapInSandbox = new Map<PropertyKey, any>();
  /** 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot */
  private currentUpdatedPropsValueMap = new Map<PropertyKey, any>();
  name: string;
  proxy: WindowProxy;
  globalContext: typeof window;
  type: SandBoxType;
  sandboxRunning = true;
  latestSetProp: PropertyKey | null = null;
  private setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) {
    if (value === undefined && toDelete) {
      // eslint-disable-next-line no-param-reassign
      delete (this.globalContext as any)[prop];
    } else if (isPropConfigurable(this.globalContext, prop) && typeof prop !== 'symbol') {
      Object.defineProperty(this.globalContext, prop, { writable: true, configurable: true });
      // eslint-disable-next-line no-param-reassign
      (this.globalContext as any)[prop] = value;
    }
  }
  active() {
    if (!this.sandboxRunning) {
      this.currentUpdatedPropsValueMap.forEach((v, p) => this.setWindowProp(p, v));
    }
    this.sandboxRunning = true;
  }
  inactive() {
    if (process.env.NODE_ENV === 'development') {
      console.info(`[qiankun:sandbox] ${this.name} modified global properties restore...`, [
        ...this.addedPropsMapInSandbox.keys(),
        ...this.modifiedPropsOriginalValueMapInSandbox.keys(),
      ]);
    }
    // renderSandboxSnapshot = snapshot(currentUpdatedPropsValueMapForSnapshot);
    // restore global props to initial snapshot
    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => this.setWindowProp(p, v));
    this.addedPropsMapInSandbox.forEach((_, p) => this.setWindowProp(p, undefined, true));
    this.sandboxRunning = false;
  }
  constructor(name: string, globalContext = window) {
    this.name = name;
    this.globalContext = globalContext;
    this.type = SandBoxType.LegacyProxy;
    const { addedPropsMapInSandbox, modifiedPropsOriginalValueMapInSandbox, currentUpdatedPropsValueMap } = this;
    const rawWindow = globalContext;
    const fakeWindow = Object.create(null) as Window;
    const setTrap = (p: PropertyKey, value: any, originalValue: any, sync2Window = true) => {
      if (this.sandboxRunning) {
        if (!rawWindow.hasOwnProperty(p)) {
          addedPropsMapInSandbox.set(p, value);
        } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
          // 如果当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值
          modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
        }
        currentUpdatedPropsValueMap.set(p, value);
        if (sync2Window) {
          // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据
          (rawWindow as any)[p] = value;
        }
        this.latestSetProp = p;
        return true;
      }
      if (process.env.NODE_ENV === 'development') {
        console.warn(`[qiankun] Set window.${p.toString()} while sandbox destroyed or inactive in ${name}!`);
      }
      // 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,在沙箱卸载的情况下应该忽略错误
      return true;
    };
    const proxy = new Proxy(fakeWindow, {
      set: (_: Window, p: PropertyKey, value: any): boolean => {
        const originalValue = (rawWindow as any)[p];
        return setTrap(p, value, originalValue, true);
      },
      get(_: Window, p: PropertyKey): any {
        // avoid who using window.window or window.self to escape the sandbox environment to touch the really window
        // or use window.top to check if an iframe context
        // see https://github.com/eligrey/FileSaver.js/blob/master/src/FileSaver.js#L13
        if (p === 'top' || p === 'parent' || p === 'window' || p === 'self') {
          return proxy;
        }
        const value = (rawWindow as any)[p];
        return getTargetValue(rawWindow, value);
      },
      // trap in operator
      // see https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/constants.js#L12
      has(_: Window, p: string | number | symbol): boolean {
        return p in rawWindow;
      },
      getOwnPropertyDescriptor(_: Window, p: PropertyKey): PropertyDescriptor | undefined {
        const descriptor = Object.getOwnPropertyDescriptor(rawWindow, p);
        // A property cannot be reported as non-configurable, if it does not exists as an own property of the target object
        if (descriptor && !descriptor.configurable) {
          descriptor.configurable = true;
        }
        return descriptor;
      },
      defineProperty(_: Window, p: string | symbol, attributes: PropertyDescriptor): boolean {
        const originalValue = (rawWindow as any)[p];
        const done = Reflect.defineProperty(rawWindow, p, attributes);
        const value = (rawWindow as any)[p];
        setTrap(p, value, originalValue, false);
        return done;
      },
    });
    this.proxy = proxy;
  }
}它通过window来创建一个新的对象,然后使用Proxy方式来代理。
全局状态管理 
initGlobalState定义全局状态,并返回通信方法,建议在主应用使用,微应用通过 props 获取通信方法。看一下源码是如何定义的:
//src/globalState.ts
export function initGlobalState(state: Record<string, any> = {}) {
  if (process.env.NODE_ENV === 'development') {
    console.warn(`[qiankun] globalState tools will be removed in 3.0, pls don't use it!`);
  }
  if (state === globalState) {
    console.warn('[qiankun] state has not changed!');
  } else {
    const prevGlobalState = cloneDeep(globalState);
    globalState = cloneDeep(state);
    emitGlobal(globalState, prevGlobalState);
  }
  return getMicroAppStateActions(`global-${+new Date()}`, true);
}它接收state作为参数,他先获取当前的环境下的globalState,然后获取更新后的globalState,再通过emitGlobal把变更发送给所有的订阅者,然后看一下emitGlobal方法的定义。
//src/globalState.ts
// 触发全局监听
function emitGlobal(state: Record<string, any>, prevState: Record<string, any>) {
  Object.keys(deps).forEach((id: string) => {
    if (deps[id] instanceof Function) {
      deps[id](cloneDeep(state), cloneDeep(prevState));
    }
  });
}这个方法遍历deps,然后执行每一个id的方法,把state和上一个state通过深度克隆的方式传递过去,这个就是发布。
监听方法是onGlobalStateChange,它的作用是在当前应用监听全局状态,有变更触发 callback,fireImmediately = true 立即触发 callback。在这个方法中将callback添加到订阅者中。这样每次emitGlobal就能通过deps的id触发到。
//src/globalState.ts
/**
 * onGlobalStateChange 全局依赖监听
 *
 * 收集 setState 时所需要触发的依赖
 *
 * 限制条件:每个子应用只有一个激活状态的全局监听,新监听覆盖旧监听,若只是监听部分属性,请使用 onGlobalStateChange
 *
 * 这么设计是为了减少全局监听滥用导致的内存爆炸
 *
 * 依赖数据结构为:
 * {
 *   {id}: callback
 * }
 *
 * @param callback
 * @param fireImmediately
 */
onGlobalStateChange(callback: OnGlobalStateChangeCallback, fireImmediately?: boolean) {
  if (!(callback instanceof Function)) {
    console.error('[qiankun] callback must be function!');
    return;
  }
  if (deps[id]) {
    console.warn(`[qiankun] '${id}' global listener already exists before this, new listener will overwrite it.`);
  }
  deps[id] = callback;
  if (fireImmediately) {
    const cloneState = cloneDeep(globalState);
    callback(cloneState, cloneState);
  }
}然后再看一下setGlobalState,它的作用是按一级属性设置全局状态,微应用中只能修改已存在的一级属性。看一下源码:
//src/globalState.ts
/**
 * setGlobalState 更新 store 数据
 *
 * 1. 对输入 state 的第一层属性做校验,只有初始化时声明过的第一层(bucket)属性才会被更改
 * 2. 修改 store 并触发全局监听
 *
 * @param state
 */
setGlobalState(state: Record<string, any> = {}) {
  if (state === globalState) {
    console.warn('[qiankun] state has not changed!');
    return false;
  }
  const changeKeys: string[] = [];
  const prevGlobalState = cloneDeep(globalState);
  globalState = cloneDeep(
    Object.keys(state).reduce((_globalState, changeKey) => {
      if (isMaster || _globalState.hasOwnProperty(changeKey)) {
        changeKeys.push(changeKey);
        return Object.assign(_globalState, { [changeKey]: state[changeKey] });
      }
      console.warn(`[qiankun] '${changeKey}' not declared when init state!`);
      return _globalState;
    }, globalState),
  );
  if (changeKeys.length === 0) {
    console.warn('[qiankun] state has not changed!');
    return false;
  }
  emitGlobal(globalState, prevGlobalState);
  return true;
},在这个方法中先获取当前环境下的globalState,然后再获取修改之后的globalState,最后通过emitGlobal将当前和变更后的state传递进去。
然后是移除监听方法offGlobalStateChange,通过delete来删除deps下的子应用id来实现的。
// 注销该应用下的依赖
offGlobalStateChange() {
  delete deps[id];
  return true;
},最后看一下子应用是如何获取onGlobalStateChange、setGlobalState、offGlobalStateChange这三个方法的。找到loadApp方法的实现:
export async function loadApp<T extends ObjectType>(
  app: LoadableApp<T>,
  configuration: FrameworkConfiguration = {},
  lifeCycles?: FrameworkLifeCycles<T>,
): Promise<ParcelConfigObjectGetter> {
.....
const { onGlobalStateChange, setGlobalState, offGlobalStateChange }: Record<string, CallableFunction> =
    getMicroAppStateActions(appInstanceId);
  // FIXME temporary way
  const syncAppWrapperElement2Sandbox = (element: HTMLElement | null) => (initialAppWrapperElement = element);
  const parcelConfigGetter: ParcelConfigObjectGetter = (remountContainer = initialContainer) => {
    let appWrapperElement: HTMLElement | null;
    let appWrapperGetter: ReturnType<typeof getAppWrapperGetter>;
    const parcelConfig: ParcelConfigObject = {
      name: appInstanceId,
      bootstrap,
      mount: [
        async () => {
          if (process.env.NODE_ENV === 'development') {
            const marks = performanceGetEntriesByName(markName, 'mark');
            // mark length is zero means the app is remounting
            if (marks && !marks.length) {
              performanceMark(markName);
            }
          }
        },
        async () => {
          if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) {
            return prevAppUnmountedDeferred.promise;
          }
          return undefined;
        },
        // initial wrapper element before app mount/remount
        async () => {
          appWrapperElement = initialAppWrapperElement;
          appWrapperGetter = getAppWrapperGetter(
            appInstanceId,
            !!legacyRender,
            strictStyleIsolation,
            scopedCSS,
            () => appWrapperElement,
          );
        },
        // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕
        async () => {
          const useNewContainer = remountContainer !== initialContainer;
          if (useNewContainer || !appWrapperElement) {
            // element will be destroyed after unmounted, we need to recreate it if it not exist
            // or we try to remount into a new container
            appWrapperElement = createElement(appContent, strictStyleIsolation, scopedCSS, appInstanceId);
            syncAppWrapperElement2Sandbox(appWrapperElement);
          }
          render({ element: appWrapperElement, loading: true, container: remountContainer }, 'mounting');
        },
        mountSandbox,
        // exec the chain after rendering to keep the behavior with beforeLoad
        async () => execHooksChain(toArray(beforeMount), app, global),
        async (props) => mount({ ...props, container: appWrapperGetter(), setGlobalState, onGlobalStateChange }),
        // finish loading after app mounted
        async () => render({ element: appWrapperElement, loading: false, container: remountContainer }, 'mounted'),
        async () => execHooksChain(toArray(afterMount), app, global),
        // initialize the unmount defer after app mounted and resolve the defer after it unmounted
        async () => {
          if (await validateSingularMode(singular, app)) {
            prevAppUnmountedDeferred = new Deferred<void>();
          }
        },
        async () => {
          if (process.env.NODE_ENV === 'development') {
            const measureName = `[qiankun] App ${appInstanceId} Loading Consuming`;
            performanceMeasure(measureName, markName);
          }
        },
      ],
      unmount: [
        async () => execHooksChain(toArray(beforeUnmount), app, global),
        async (props) => unmount({ ...props, container: appWrapperGetter() }),
        unmountSandbox,
        async () => execHooksChain(toArray(afterUnmount), app, global),
        async () => {
          render({ element: null, loading: false, container: remountContainer }, 'unmounted');
          offGlobalStateChange(appInstanceId);
          // for gc
          appWrapperElement = null;
          syncAppWrapperElement2Sandbox(appWrapperElement);
        },
        async () => {
          if ((await validateSingularMode(singular, app)) && prevAppUnmountedDeferred) {
            prevAppUnmountedDeferred.resolve();
          }
        },
      ],
    };
    if (typeof update === 'function') {
      parcelConfig.update = update;
    }
    return parcelConfig;
  };
  ....
}可以看到先获取onGlobalStateChange, setGlobalState, offGlobalStateChange这三个方法,然后在mount生命周期函数通过props将其注入到子应用的执行方法中,这样子应用就可以在mount去获取这三个方法的内容。
预加载 
其中prefetch方法中通过importEntry方法来获取子应用的内容。
//src/prefetch.ts
/**
 * prefetch assets, do nothing while in mobile network
 * @param entry
 * @param opts
 */
function prefetch(entry: Entry, opts?: ImportEntryOpts): void {
  if (!navigator.onLine || isSlowNetwork) {
    // Don't prefetch if in a slow network or offline
    return;
  }
  requestIdleCallback(async () => {
    const { getExternalScripts, getExternalStyleSheets } = await importEntry(entry, opts);
    requestIdleCallback(getExternalStyleSheets);
    requestIdleCallback(getExternalScripts);
  });
}prefetchAfterFirstMounted方法是在第一个子应用加载完成之后再去加载其他的子应用。通过single-spa:first-mount事件监听来加载子应用,这个事件是single-spa触发的,然后调用prefetch来加载子应用。
//src/prefetch.ts
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);
  });
}prefetchImmediately表示立即加载子应用,直接调用prefetch方法。
export function prefetchImmediately(apps: AppMetadata[], opts?: ImportEntryOpts): void {
  if (process.env.NODE_ENV === 'development') {
    console.log('[qiankun] prefetch starting for apps...', apps);
  }
  apps.forEach(({ entry }) => prefetch(entry, opts));
}相关文章 
- 微前端框架 之 qiankun 从入门到源码分析
 - 基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇
 - 基于 qiankun 的微前端最佳实践(图文并茂) - 应用间通信篇
 - 基于 qiankun 的微前端最佳实践(图文并茂) - 应用部署篇
 - 万字长文+图文并茂+全面解析微前端框架 qiankun 源码 - qiankun 篇
 - 万字长文-落地微前端 qiankun 理论与实践指北
 - qiankun(微前端)快问快答
 - 微前端之qiankun实践
 - 记录使用 qiankun 遇到的一些问题
 - 微前端接入Sentry的不完美但已尽力的实践总结
 - 微前端项目难点解决(一)
 - 我终于把微前端(qiankun)落地生产项目了
 - qiankun多重应用嵌套(父 --> 子 --> 孙)
 - qiankun多重应用嵌套
 - qiankun 微前端实践总结(二)
 - 在微前端中加载 Vite 应用
 - 网易严选企业级微前端解决方案与落地实践
 
