mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-06 03:59:53 +08:00
feat(plugin-qiankun): 支持微应用使用useModel('@@qiankunStateFromMain')获取主应用状态
This commit is contained in:
parent
d5c267f5d1
commit
8cb00db93c
@ -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';
|
||||||
|
@ -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>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
let initState;
|
||||||
|
const setModelState = (val) => {
|
||||||
|
initState = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => reactive(initState);
|
||||||
|
|
||||||
|
export { setModelState };
|
@ -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(() => ({
|
||||||
|
@ -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);
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
let initState;
|
||||||
|
const setModelState = (val) => {
|
||||||
|
initState = val;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default () => reactive(initState);
|
||||||
|
|
||||||
|
export { setModelState };
|
Loading…
x
Reference in New Issue
Block a user