feat(plugin-qiankun): 支持微应用使用useModel('@@qiankunStateFromMain')获取主应用状态

This commit is contained in:
万纯 2021-03-24 15:27:41 +08:00
parent d5c267f5d1
commit 8cb00db93c
6 changed files with 150 additions and 65 deletions

View File

@ -1,2 +1,4 @@
export const defaultMainRootId = '#root-master'; export const defaultMainRootId = '#root-master';
export const defaultHistoryType = 'hash'; export const defaultHistoryType = 'hash';
export const qiankunStateForMicroModelNamespace = '@@qiankunStateForMicro';
export const qiankunStateFromMainModelNamespace = '@@qiankunStateFromMain';

View File

@ -23,6 +23,9 @@ export const MicroApp = defineComponent({
type: String, type: String,
required: true, required: true,
}, },
settings: Object,
lifeCycles: Object,
className: String,
}, },
setup(props) { setup(props) {
const { const {
@ -32,82 +35,84 @@ export const MicroApp = defineComponent({
...globalSettings ...globalSettings
} = getMasterOptions(); } = getMasterOptions();
// 挂载节点
const containerRef = ref(null);
const microAppRef = ref();
const updatingPromiseRef = ref();
const updatingTimestampRef = ref(Date.now());
const appConfigRef = computed(() => {
const appConfig = apps.find((app) => app.name === props.name);
if (!appConfig) {
throw new Error(
`[@fesjs/plugin-qiankun]: Can not find the configuration of ${props.name} app!`
);
}
return appConfig;
});
const propsFromConfigRef = computed(() => {
const appConfig = appConfigRef.value;
if (appConfig) {
return appConfig.props;
}
return {};
});
const propsFromParamsRef = computed(() => {
const { const {
name, name,
settings: settingsFromProps = {}, settings,
loader,
lifeCycles, lifeCycles,
className, className,
...propsFromParams ...propsFromParams
} = props; } = props;
return propsFromParams || {};
});
const containerRef = ref(null); // 只有当name变化时才重新加载新的子应用
const microAppRef = ref();
const updatingPromise = ref();
const updatingTimestamp = ref(Date.now());
const getAppConfig = () => {
const appConfig = apps.find((app) => app.name === name);
if (!appConfig) {
throw new Error(
`[@fesjs/plugin-qiankun]: Can not find the configuration of ${name} app!`
);
}
return appConfig;
};
const loadApp = () => { const loadApp = () => {
const appConfig = getAppConfig(); const appConfig = appConfigRef.value;
const { name, entry, props: propsFromConfig = {} } = appConfig; const { name, entry } = appConfig;
// 加载新的 // 加载新的
microAppRef.value = loadMicroApp( microAppRef.value = loadMicroApp(
{ {
name: name, name: name,
entry: entry, entry: entry,
container: containerRef.value, container: containerRef.value,
props: { ...propsFromConfig, ...propsFromParams }, props: {
...propsFromConfigRef.value,
...propsFromParamsRef.value,
},
}, },
{ {
...globalSettings, ...globalSettings,
...settingsFromProps, ...(props.settings || {}),
}, },
mergeWith({}, globalLifeCycles, lifeCycles, (v1, v2) => mergeWith({}, globalLifeCycles || {}, props.lifeCycles || {}, (v1, v2) =>
concat(v1 ?? [], v2 ?? []) concat(v1 ?? [], v2 ?? [])
) )
); );
}; };
onMounted(() => { // 当参数变化时update子应用
loadApp(); const updateApp = () => {
});
onBeforeUnmount(() => {
unmountMicroApp(microAppRef.value);
});
watch(props, () => {
unmountMicroApp(microAppRef.value);
loadApp();
});
watch(microAppRef, () => {
const microApp = microAppRef.value; const microApp = microAppRef.value;
const appConfig = getAppConfig();
const { props: propsFromConfig = {} } = appConfig;
if (microApp) { if (microApp) {
if (!updatingPromise.value) { if (!updatingPromiseRef.value) {
// 初始化 updatingPromise 为 microApp.mountPromise从而确保后续更新是在应用 mount 完成之后 // 初始化 updatingPromiseRef 为 microApp.mountPromise从而确保后续更新是在应用 mount 完成之后
updatingPromise.value = microApp.mountPromise; updatingPromiseRef.value = microApp.mountPromise;
} else { } else {
// 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成 // 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成
updatingPromise.value = updatingPromise.value.then(() => { updatingPromiseRef.value = updatingPromiseRef.value.then(
() => {
const canUpdate = (app) => const canUpdate = (app) =>
app?.update && app.getStatus() === "MOUNTED"; app?.update && app.getStatus() === "MOUNTED";
if (canUpdate(microApp)) { if (canUpdate(microApp)) {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
if ( if (
Date.now() - updatingTimestamp.value < Date.now() -
updatingTimestampRef.value <
200 200
) { ) {
console.warn( console.warn(
@ -119,20 +124,38 @@ export const MicroApp = defineComponent({
`[@fesjs/plugin-qiankun] MicroApp ${props.name} is updating with props: `, `[@fesjs/plugin-qiankun] MicroApp ${props.name} is updating with props: `,
props props
); );
updatingTimestamp.value = Date.now(); updatingTimestampRef.value = Date.now();
} }
// 返回 microApp.update 形成链式调用 // 返回 microApp.update 形成链式调用
return microApp.update({ return microApp.update({
...propsFromConfig, ...propsFromConfigRef.value,
...propsFromParams, ...propsFromParamsRef.value,
});
}
}); });
} }
} }
);
}
}
};
onMounted(() => {
loadApp();
}); });
return () => <div ref={containerRef} className={className}></div>; onBeforeUnmount(() => {
unmountMicroApp(microAppRef.value);
});
watch(appConfigRef, () => {
unmountMicroApp(microAppRef.value);
loadApp();
});
watch([propsFromConfigRef, propsFromParamsRef], () => {
updateApp();
});
return () => <div ref={containerRef} className={props.className}></div>;
}, },
}); });

View File

@ -0,0 +1,11 @@
import { reactive } from 'vue';
let initState;
const setModelState = (val) => {
initState = val;
};
export default () => reactive(initState);
export { setModelState };

View File

@ -3,6 +3,7 @@ import address from 'address';
import { lodash } from '@umijs/utils'; import { lodash } from '@umijs/utils';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { qiankunStateFromMainModelNamespace } from '../constants';
const namespace = 'plugin-qiankun/micro'; const namespace = 'plugin-qiankun/micro';
@ -15,6 +16,10 @@ export function isSlaveEnable(api) {
} }
export default function (api) { export default function (api) {
const {
utils: { Mustache }
} = api;
api.describe({ api.describe({
enableBy: () => isSlaveEnable(api) enableBy: () => isSlaveEnable(api)
}); });
@ -84,11 +89,25 @@ export default function (api) {
const absLifeclesPath = join(namespace, 'lifecycles.js'); const absLifeclesPath = join(namespace, 'lifecycles.js');
const absMicroOptionsPath = join(namespace, 'slaveOptions.js'); const absMicroOptionsPath = join(namespace, 'slaveOptions.js');
const absPublicPath = join(namespace, 'publicPath.js'); const absPublicPath = join(namespace, 'publicPath.js');
const absModelPath = join(namespace, 'qiankunModel.js');
// 更改public path // 更改public path
api.addEntryImportsAhead(() => [{ source: `@@/${absPublicPath}` }]); api.addEntryImportsAhead(() => [{ source: `@@/${absPublicPath}` }]);
api.register({
key: 'addExtraModels',
fn: () => {
const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']);
return HAS_PLUGIN_MODEL ? [{
absPath: `@@/${absModelPath}`,
namespace: qiankunStateFromMainModelNamespace
}] : [];
}
});
api.onGenerateFiles(() => { api.onGenerateFiles(() => {
const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']);
api.writeTmpFile({ api.writeTmpFile({
path: absRuntimePath, path: absRuntimePath,
content: readFileSync( content: readFileSync(
@ -99,10 +118,12 @@ export default function (api) {
api.writeTmpFile({ api.writeTmpFile({
path: absLifeclesPath, path: absLifeclesPath,
content: readFileSync( content: Mustache.render(readFileSync(
join(__dirname, 'runtime/lifecycles.tpl'), join(__dirname, 'runtime/lifecycles.tpl'),
'utf-8' 'utf-8'
) ), {
HAS_PLUGIN_MODEL
})
}); });
api.writeTmpFile({ api.writeTmpFile({
@ -110,6 +131,7 @@ export default function (api) {
content: ` content: `
if (window.__POWERED_BY_QIANKUN__) { if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
window.public_path = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} }
` `
}); });
@ -124,6 +146,13 @@ export default function (api) {
export const setSlaveOptions = (newOpts) => options = ({ ...options, ...newOpts }); export const setSlaveOptions = (newOpts) => options = ({ ...options, ...newOpts });
` `
}); });
if (HAS_PLUGIN_MODEL) {
api.writeTmpFile({
path: absModelPath,
content: readFileSync(join(__dirname, 'runtime/qiankunModel.tpl'), 'utf-8')
});
}
}); });
api.addEntryImports(() => ({ api.addEntryImports(() => ({

View File

@ -1,4 +1,7 @@
import { plugin, ApplyPluginsType } from '@@/core/coreExports'; import { plugin, ApplyPluginsType } from '@@/core/coreExports';
{{#HAS_PLUGIN_MODEL}}
import { setModelState } from './qiankunModel';
{{/HAS_PLUGIN_MODEL}}
const defer = {}; const defer = {};
defer.promise = new Promise((resolve) => { defer.promise = new Promise((resolve) => {
@ -47,6 +50,9 @@ export function genMount() {
return async (props) => { return async (props) => {
// props 有值时说明应用是通过 lifecycle 被主应用唤醒的,而不是独立运行时自己 mount // props 有值时说明应用是通过 lifecycle 被主应用唤醒的,而不是独立运行时自己 mount
if (typeof props !== 'undefined') { if (typeof props !== 'undefined') {
{{#HAS_PLUGIN_MODEL}}
setModelState(props);
{{/HAS_PLUGIN_MODEL}}
const slaveRuntime = getSlaveRuntime(); const slaveRuntime = getSlaveRuntime();
if (slaveRuntime.mount) { if (slaveRuntime.mount) {
await slaveRuntime.mount(props); await slaveRuntime.mount(props);
@ -68,6 +74,9 @@ export function genMount() {
export function genUpdate() { export function genUpdate() {
return async (props) => { return async (props) => {
{{#HAS_PLUGIN_MODEL}}
setModelState(props);
{{/HAS_PLUGIN_MODEL}}
const slaveRuntime = await getSlaveRuntime(); const slaveRuntime = await getSlaveRuntime();
if (slaveRuntime.update) { if (slaveRuntime.update) {
await slaveRuntime.update(props); await slaveRuntime.update(props);

View File

@ -0,0 +1,11 @@
import { reactive } from 'vue';
let initState;
const setModelState = (val) => {
initState = val;
};
export default () => reactive(initState);
export { setModelState };