Skip to content

Commit c739452

Browse files
committed
feat: 添加子应用支持的运行时插件和生命周期钩子,包含路由修改和客户端渲染选项的处理。
1 parent 95b34f1 commit c739452

File tree

3 files changed

+415
-0
lines changed

3 files changed

+415
-0
lines changed

libs/childRuntimePlugin.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* runtime
3+
* @Author: liwb ([email protected])
4+
* @Date: 2024-05-30 15:36
5+
* @LastEditTime: 2024-05-30 15:36
6+
* @Description: runtime
7+
*/
8+
// @ts-nocheck
9+
import { createHistory } from '@@/core/history';
10+
import qiankunRender, { clientRenderOptsStack } from './lifecycles';
11+
12+
export function render(oldRender: any) {
13+
return qiankunRender().then(oldRender);
14+
}
15+
16+
function modifyRoutesWithAttachMode({ routes, base }) {
17+
if (!routes.length) return;
18+
routes.forEach((route) => {
19+
if (route.path) {
20+
const path = route.path[0] === '/' ? route.path : `/${route.path}`;
21+
// 防止重复添加前缀
22+
if (path.startsWith(base)) return path;
23+
route.path = base + route.path;
24+
}
25+
if (route.children?.length) {
26+
modifyRoutesWithAttachMode({
27+
routes: route.children,
28+
base
29+
});
30+
}
31+
});
32+
}
33+
34+
export function modifyClientRenderOpts(memo) {
35+
// 每次应用 render 的时候会调 modifyClientRenderOpts,这时尝试从队列中取 render 的配置
36+
const clientRenderOpts = clientRenderOptsStack.shift();
37+
const { basename, historyType, routes } = memo;
38+
// use ?? instead of ||, incase clientRenderOpts.basename is ''
39+
// only break when microApp has a config.base and mount path is /*
40+
const newBasename = clientRenderOpts?.basename ?? basename;
41+
const newHistoryType = clientRenderOpts?.historyType || historyType;
42+
43+
if (newHistoryType !== historyType || newBasename !== basename) {
44+
clientRenderOpts.history = createHistory({
45+
type: newHistoryType,
46+
basename: newBasename,
47+
...clientRenderOpts.historyOpts
48+
});
49+
}
50+
51+
// 运行环境是乾坤且检测 hash 路由时,进行重写子系统理由,添加路由前缀
52+
if (window.__POWERED_BY_QIANKUN__ && historyType === 'hash') {
53+
const base = clientRenderOpts.history.base;
54+
const routesArray = Object.values(routes);
55+
56+
modifyRoutesWithAttachMode({ routes: routesArray, base });
57+
}
58+
59+
return {
60+
...memo,
61+
...clientRenderOpts,
62+
basename: newBasename,
63+
historyType: newHistoryType
64+
};
65+
}

libs/lifecycles.ts

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* lifecycle
3+
* @Author: liwb ([email protected])
4+
* @Date: 2024-05-30 15:35
5+
* @LastEditTime: 2024-05-30 15:35
6+
* @Description: lifecycle
7+
*/
8+
// @ts-nocheck
9+
import { getPluginManager } from '@@/core/plugin';
10+
import { ApplyPluginsType } from '@@/exports';
11+
12+
const noop = () => {
13+
};
14+
15+
type Defer = {
16+
promise: Promise<any>;
17+
resolve(value?: any): void;
18+
};
19+
20+
// @ts-ignore
21+
const defer: Defer = {};
22+
defer.promise = new Promise((resolve) => {
23+
defer.resolve = resolve;
24+
});
25+
26+
let render = noop;
27+
let hasMountedAtLeastOnce = false;
28+
let cacheAppPromise = null;
29+
const cache = {};
30+
31+
export default () => defer.promise;
32+
33+
export const clientRenderOptsStack = [];
34+
35+
function normalizeHistory(
36+
history?: 'string' | Record<string, any>,
37+
base?: string
38+
) {
39+
let normalizedHistory: Record<string, any> = {};
40+
if (base) normalizedHistory.basename = base;
41+
if (history) {
42+
if (typeof history === 'string') {
43+
normalizedHistory.type = history;
44+
} else {
45+
normalizedHistory = history;
46+
}
47+
}
48+
49+
return normalizedHistory;
50+
}
51+
52+
async function getChildRuntime() {
53+
const config = await getPluginManager().applyPlugins({
54+
key: 'qiankun',
55+
type: ApplyPluginsType.modify,
56+
initialValue: {},
57+
async: true
58+
});
59+
// 应用既是 main 又是 child 的场景,运行时 child 配置方式为 export const qiankun = { child: {} }
60+
const { child } = config;
61+
return child || config;
62+
}
63+
64+
// 子应用生命周期钩子Bootstrap
65+
export function genBootstrap(oldRender: typeof noop) {
66+
return async (props: any) => {
67+
if (typeof props !== 'undefined') {
68+
const childRuntime = await getChildRuntime();
69+
if (childRuntime.bootstrap) {
70+
await childRuntime.bootstrap(props);
71+
}
72+
}
73+
render = oldRender;
74+
};
75+
}
76+
77+
// 子应用生命周期钩子Mount
78+
export function genMount(mountElementId) {
79+
return async (props?: any) => {
80+
// props 有值时说明应用是通过 lifecycle 被主应用唤醒的,而不是独立运行时自己 mount
81+
if (typeof props !== 'undefined') {
82+
const childRuntime = await getChildRuntime();
83+
if (childRuntime.mount) {
84+
await childRuntime.mount(props);
85+
}
86+
87+
const { type, ...historyOpts } = normalizeHistory(
88+
props?.history || {},
89+
props?.base
90+
);
91+
92+
// 更新 clientRender 配置
93+
const clientRenderOpts = {
94+
callback: () => {
95+
// 默认开启
96+
// 如果需要手动控制 loading,通过主应用配置 props.autoSetLoading false 可以关闭
97+
if (props.autoSetLoading && typeof props.setLoading === 'function') {
98+
props.setLoading(false);
99+
}
100+
101+
// 支持将子应用的 history 回传给父应用
102+
if (typeof props?.onHistoryInit === 'function') {
103+
props.onHistoryInit(history);
104+
}
105+
},
106+
// 支持通过 props 注入 container 来限定子应用 mountElementId 的查找范围
107+
// 避免多个子应用出现在同一主应用时出现 mount 冲突
108+
rootElement:
109+
props.container?.querySelector(`#${mountElementId}`) ||
110+
document.getElementById(mountElementId),
111+
112+
// 乾坤环境则使用主应用前缀 history 模式
113+
basename: window.__POWERED_BY_QIANKUN__ ? props.routerPrefix : props.base,
114+
115+
// 支持 MicroAppWithMemoHistory 需要
116+
historyType: type,
117+
historyOpts: historyOpts
118+
};
119+
120+
clientRenderOptsStack.push(clientRenderOpts);
121+
}
122+
123+
// 第一次 mount 会自动触发 render,非第一次 mount 则需手动触发
124+
if (hasMountedAtLeastOnce) {
125+
cacheAppPromise = render();
126+
cacheAppPromise.then((app) => {
127+
if (props?.name && !cache[props.name]) {
128+
cache[props.name] = app;
129+
}
130+
});
131+
} else {
132+
defer.resolve();
133+
}
134+
hasMountedAtLeastOnce = true;
135+
};
136+
}
137+
138+
export function genUpdate() {
139+
return async (props: any) => {
140+
const childRuntime = await getChildRuntime();
141+
if (childRuntime.update) {
142+
await childRuntime.update(props);
143+
}
144+
};
145+
}
146+
147+
// 子应用生命周期钩子Unmount
148+
export function genUnmount() {
149+
return async (props: any) => {
150+
if (cache[props.name]) {
151+
setTimeout(() => {
152+
let { app, router } = cache[props.name];
153+
app.$destroy();
154+
app.$el.innerHTML = '';
155+
app = null;
156+
router = null;
157+
delete cache[props.name];
158+
cacheAppPromise = null;
159+
}, 0);
160+
}
161+
const childRuntime = await getChildRuntime();
162+
if (childRuntime.unmount) {
163+
await childRuntime.unmount(props);
164+
}
165+
};
166+
}

0 commit comments

Comments
 (0)