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,51 +35,110 @@ export const MicroApp = defineComponent({
...globalSettings ...globalSettings
} = getMasterOptions(); } = getMasterOptions();
const { // 挂载节点
name,
settings: settingsFromProps = {},
loader,
lifeCycles,
className,
...propsFromParams
} = props;
const containerRef = ref(null); const containerRef = ref(null);
const microAppRef = ref(); const microAppRef = ref();
const updatingPromise = ref(); const updatingPromiseRef = ref();
const updatingTimestamp = ref(Date.now()); const updatingTimestampRef = ref(Date.now());
const getAppConfig = () => { const appConfigRef = computed(() => {
const appConfig = apps.find((app) => app.name === name); const appConfig = apps.find((app) => app.name === props.name);
if (!appConfig) { if (!appConfig) {
throw new Error( throw new Error(
`[@fesjs/plugin-qiankun]: Can not find the configuration of ${name} app!` `[@fesjs/plugin-qiankun]: Can not find the configuration of ${props.name} app!`
); );
} }
return appConfig; return appConfig;
}; });
const propsFromConfigRef = computed(() => {
const appConfig = appConfigRef.value;
if (appConfig) {
return appConfig.props;
}
return {};
});
const propsFromParamsRef = computed(() => {
const {
name,
settings,
lifeCycles,
className,
...propsFromParams
} = props;
return propsFromParams || {};
});
// 只有当name变化时才重新加载新的子应用
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 ?? [])
) )
); );
}; };
// 当参数变化时update子应用
const updateApp = () => {
const microApp = microAppRef.value;
if (microApp) {
if (!updatingPromiseRef.value) {
// 初始化 updatingPromiseRef 为 microApp.mountPromise从而确保后续更新是在应用 mount 完成之后
updatingPromiseRef.value = microApp.mountPromise;
} else {
// 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成
updatingPromiseRef.value = updatingPromiseRef.value.then(
() => {
const canUpdate = (app) =>
app?.update && app.getStatus() === "MOUNTED";
if (canUpdate(microApp)) {
if (process.env.NODE_ENV === "development") {
if (
Date.now() -
updatingTimestampRef.value <
200
) {
console.warn(
`[@fesjs/plugin-qiankun] It seems like microApp ${props.name} is updating too many times in a short time(200ms), you may need to do some optimization to avoid the unnecessary re-rendering.`
);
}
console.info(
`[@fesjs/plugin-qiankun] MicroApp ${props.name} is updating with props: `,
props
);
updatingTimestampRef.value = Date.now();
}
// 返回 microApp.update 形成链式调用
return microApp.update({
...propsFromConfigRef.value,
...propsFromParamsRef.value,
});
}
}
);
}
}
};
onMounted(() => { onMounted(() => {
loadApp(); loadApp();
}); });
@ -85,54 +147,15 @@ export const MicroApp = defineComponent({
unmountMicroApp(microAppRef.value); unmountMicroApp(microAppRef.value);
}); });
watch(props, () => { watch(appConfigRef, () => {
unmountMicroApp(microAppRef.value); unmountMicroApp(microAppRef.value);
loadApp(); loadApp();
}); });
watch(microAppRef, () => { watch([propsFromConfigRef, propsFromParamsRef], () => {
const microApp = microAppRef.value; updateApp();
const appConfig = getAppConfig();
const { props: propsFromConfig = {} } = appConfig;
if (microApp) {
if (!updatingPromise.value) {
// 初始化 updatingPromise 为 microApp.mountPromise从而确保后续更新是在应用 mount 完成之后
updatingPromise.value = microApp.mountPromise;
} else {
// 确保 microApp.update 调用是跟组件状态变更顺序一致的,且后一个微应用更新必须等待前一个更新完成
updatingPromise.value = updatingPromise.value.then(() => {
const canUpdate = (app) =>
app?.update && app.getStatus() === "MOUNTED";
if (canUpdate(microApp)) {
if (process.env.NODE_ENV === "development") {
if (
Date.now() - updatingTimestamp.value <
200
) {
console.warn(
`[@fesjs/plugin-qiankun] It seems like microApp ${props.name} is updating too many times in a short time(200ms), you may need to do some optimization to avoid the unnecessary re-rendering.`
);
}
console.info(
`[@fesjs/plugin-qiankun] MicroApp ${props.name} is updating with props: `,
props
);
updatingTimestamp.value = Date.now();
}
// 返回 microApp.update 形成链式调用
return microApp.update({
...propsFromConfig,
...propsFromParams,
});
}
});
}
}
}); });
return () => <div ref={containerRef} className={className}></div>; 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 };