diff --git a/docs/api/README.md b/docs/api/README.md index e7182ff55..999293988 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -61,6 +61,8 @@ By linking the micro-application to some url rules, the function of automaticall This function is called when the browser url changes, and `activeRule` returns `true` to indicate that the subapplication needs to be activated. + - loader - `(loading: boolean) => void` - optional, function will be invoked while the loading state changed. + - props - `object` - optional, data that the primary application needs to pass to the child application. - `LifeCycles` diff --git a/docs/zh/api/README.md b/docs/zh/api/README.md index c51450893..4de3b7819 100644 --- a/docs/zh/api/README.md +++ b/docs/zh/api/README.md @@ -61,6 +61,8 @@ 浏览器 url 发生变化会调用 activeRule 里的规则,`activeRule` 任意一个返回 `true` 时表明该微应用需要被激活。 + - loader - `(loading: boolean) => void` - 可选,loading 状态发生变化时会调用的方法。 + - props - `object` - 可选,主应用需要传递给微应用的数据。 - `LifeCycles` diff --git a/examples/main/index.js b/examples/main/index.js index 8567e101a..5336404e1 100644 --- a/examples/main/index.js +++ b/examples/main/index.js @@ -16,6 +16,8 @@ import render from './render/ReactRender'; */ render({ loading: true }); +const loader = loading => render({ loading }); + /** * Step2 注册子应用 */ @@ -26,30 +28,35 @@ registerMicroApps( name: 'react16', entry: '//localhost:7100', container: '#subapp-viewport', + loader, activeRule: '/react16', }, { name: 'react15', entry: '//localhost:7102', - render, + container: '#subapp-viewport', + loader, activeRule: '/react15', }, { name: 'vue', entry: '//localhost:7101', container: '#subapp-viewport', + loader, activeRule: '/vue', }, { name: 'angular9', entry: '//localhost:7103', container: '#subapp-viewport', + loader, activeRule: '/angular9', }, { name: 'purehtml', entry: '//localhost:7104', container: '#subapp-viewport', + loader, activeRule: '/purehtml', }, ], diff --git a/examples/main/render/ReactRender.jsx b/examples/main/render/ReactRender.jsx index 66d8a91bf..0e62b1934 100644 --- a/examples/main/render/ReactRender.jsx +++ b/examples/main/render/ReactRender.jsx @@ -5,23 +5,17 @@ import ReactDOM from 'react-dom'; * 渲染子应用 */ function Render(props) { - const { appContent, loading } = props; + const { loading } = props; return ( <> - {loading && ( -

Loading...

- )} -
-
+ {loading &&

Loading...

} +
); } -export default function render({ appContent, loading }) { +export default function render({ loading }) { const container = document.getElementById('subapp-container'); - ReactDOM.render( - , - container, - ); + ReactDOM.render(, container); } diff --git a/src/apis.ts b/src/apis.ts index 8b57a4263..9b0aac1d3 100644 --- a/src/apis.ts +++ b/src/apis.ts @@ -1,8 +1,9 @@ +import { noop } from 'lodash'; import { mountRootParcel, registerApplication, start as startSingleSpa } from 'single-spa'; import { FrameworkConfiguration, FrameworkLifeCycles, LoadableApp, MicroApp, RegistrableApp } from './interfaces'; import { loadApp } from './loader'; import { doPrefetchStrategy } from './prefetch'; -import { Deferred } from './utils'; +import { Deferred, toArray } from './utils'; window.__POWERED_BY_QIANKUN__ = true; @@ -22,13 +23,24 @@ export function registerMicroApps( microApps = [...microApps, ...unregisteredApps]; unregisteredApps.forEach(app => { - const { name, activeRule, props, ...appConfig } = app; + const { name, activeRule, loader = noop, props, ...appConfig } = app; registerApplication({ name, app: async () => { + loader(true); await frameworkStartedDefer.promise; - return loadApp({ name, props, ...appConfig }, frameworkConfiguration, lifeCycles); + + 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, diff --git a/src/interfaces.ts b/src/interfaces.ts index 637a8965e..72d80d8f6 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -43,6 +43,7 @@ export type LoadableApp = AppMetadata & { /* props pass t // for the route-based apps export type RegistrableApp = LoadableApp & { + loader?: (loading: boolean) => void; activeRule: RegisterApplicationConfig['activeWhen']; }; diff --git a/src/loader.ts b/src/loader.ts index 2b7f594bd..480851bd7 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -10,7 +10,7 @@ import getAddOns from './addons'; import { getMicroAppStateActions } from './globalState'; import { FrameworkConfiguration, FrameworkLifeCycles, HTMLContentRender, LifeCycleFn, LoadableApp } from './interfaces'; import { createSandbox } from './sandbox'; -import { Deferred, getDefaultTplWrapper, getWrapperId, validateExportLifecycle } from './utils'; +import { Deferred, getDefaultTplWrapper, getWrapperId, toArray, validateExportLifecycle } from './utils'; function assertElementExist(element: Element | null | undefined, msg?: string) { if (!element) { @@ -22,10 +22,6 @@ function assertElementExist(element: Element | null | undefined, msg?: string) { } } -function toArray(array: T | T[]): T[] { - return Array.isArray(array) ? array : [array]; -} - function execHooksChain(hooks: Array>, app: LoadableApp): Promise { if (hooks.length) { return hooks.reduce((chain, hook) => chain.then(() => hook(app)), Promise.resolve()); diff --git a/src/sandbox/patchers/dynamicHeadAppend.ts b/src/sandbox/patchers/dynamicHeadAppend.ts index cb810bd5f..8a30f1ec7 100644 --- a/src/sandbox/patchers/dynamicHeadAppend.ts +++ b/src/sandbox/patchers/dynamicHeadAppend.ts @@ -161,7 +161,7 @@ function getNewRemoveChild(...args: any[]) { } try { - // container may had been removed while app unmounting + // container may had been removed while app unmounting if the removeChild action was async const container = appWrapperGetter(); if (container.contains(child)) { return rawRemoveChild.call(container, child) as T; diff --git a/src/utils.ts b/src/utils.ts index 4d9c215c3..10ec6ce06 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,10 @@ import { isFunction, snakeCase } from 'lodash'; +export function toArray(array: T | T[]): T[] { + return Array.isArray(array) ? array : [array]; +} + export function sleep(ms: number) { return new Promise(resolve => setTimeout(resolve, ms)); }