diff --git a/packages/fes-plugin-qiankun/src/constants.js b/packages/fes-plugin-qiankun/src/constants.js index 51451582..1bd6bbc5 100644 --- a/packages/fes-plugin-qiankun/src/constants.js +++ b/packages/fes-plugin-qiankun/src/constants.js @@ -1,2 +1,4 @@ export const defaultMainRootId = '#root-master'; export const defaultHistoryType = 'hash'; +export const qiankunStateForMicroModelNamespace = '@@qiankunStateForMicro'; +export const qiankunStateFromMainModelNamespace = '@@qiankunStateFromMain'; diff --git a/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl b/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl index 08f8072e..43c21cd0 100644 --- a/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl +++ b/packages/fes-plugin-qiankun/src/main/runtime/MicroApp.tpl @@ -23,6 +23,9 @@ export const MicroApp = defineComponent({ type: String, required: true, }, + settings: Object, + lifeCycles: Object, + className: String, }, setup(props) { const { @@ -32,51 +35,110 @@ export const MicroApp = defineComponent({ ...globalSettings } = getMasterOptions(); - const { - name, - settings: settingsFromProps = {}, - loader, - lifeCycles, - className, - ...propsFromParams - } = props; - + // 挂载节点 const containerRef = ref(null); const microAppRef = ref(); - const updatingPromise = ref(); - const updatingTimestamp = ref(Date.now()); + const updatingPromiseRef = ref(); + const updatingTimestampRef = ref(Date.now()); - const getAppConfig = () => { - const appConfig = apps.find((app) => app.name === name); + 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 ${name} app!` + `[@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 { + name, + settings, + lifeCycles, + className, + ...propsFromParams + } = props; + return propsFromParams || {}; + }); + + // 只有当name变化时才重新加载新的子应用 const loadApp = () => { - const appConfig = getAppConfig(); - const { name, entry, props: propsFromConfig = {} } = appConfig; + const appConfig = appConfigRef.value; + const { name, entry } = appConfig; // 加载新的 microAppRef.value = loadMicroApp( { name: name, entry: entry, container: containerRef.value, - props: { ...propsFromConfig, ...propsFromParams }, + props: { + ...propsFromConfigRef.value, + ...propsFromParamsRef.value, + }, }, { ...globalSettings, - ...settingsFromProps, + ...(props.settings || {}), }, - mergeWith({}, globalLifeCycles, lifeCycles, (v1, v2) => + mergeWith({}, globalLifeCycles || {}, props.lifeCycles || {}, (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(() => { loadApp(); }); @@ -85,54 +147,15 @@ export const MicroApp = defineComponent({ unmountMicroApp(microAppRef.value); }); - watch(props, () => { + watch(appConfigRef, () => { unmountMicroApp(microAppRef.value); - loadApp(); }); - watch(microAppRef, () => { - const microApp = microAppRef.value; - 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, - }); - } - }); - } - } + watch([propsFromConfigRef, propsFromParamsRef], () => { + updateApp(); }); - return () =>
; + return () =>
; }, }); diff --git a/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl b/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl new file mode 100644 index 00000000..7348a0b5 --- /dev/null +++ b/packages/fes-plugin-qiankun/src/main/runtime/qiankunModel.tpl @@ -0,0 +1,11 @@ + +import { reactive } from 'vue'; + +let initState; +const setModelState = (val) => { + initState = val; +}; + +export default () => reactive(initState); + +export { setModelState }; diff --git a/packages/fes-plugin-qiankun/src/micro/index.js b/packages/fes-plugin-qiankun/src/micro/index.js index 31c22d59..2540031e 100644 --- a/packages/fes-plugin-qiankun/src/micro/index.js +++ b/packages/fes-plugin-qiankun/src/micro/index.js @@ -3,6 +3,7 @@ import address from 'address'; import { lodash } from '@umijs/utils'; import { readFileSync } from 'fs'; import { join } from 'path'; +import { qiankunStateFromMainModelNamespace } from '../constants'; const namespace = 'plugin-qiankun/micro'; @@ -15,6 +16,10 @@ export function isSlaveEnable(api) { } export default function (api) { + const { + utils: { Mustache } + } = api; + api.describe({ enableBy: () => isSlaveEnable(api) }); @@ -84,11 +89,25 @@ export default function (api) { const absLifeclesPath = join(namespace, 'lifecycles.js'); const absMicroOptionsPath = join(namespace, 'slaveOptions.js'); const absPublicPath = join(namespace, 'publicPath.js'); + const absModelPath = join(namespace, 'qiankunModel.js'); // 更改public path 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(() => { + const HAS_PLUGIN_MODEL = api.hasPlugins(['@fesjs/plugin-model']); + api.writeTmpFile({ path: absRuntimePath, content: readFileSync( @@ -99,10 +118,12 @@ export default function (api) { api.writeTmpFile({ path: absLifeclesPath, - content: readFileSync( + content: Mustache.render(readFileSync( join(__dirname, 'runtime/lifecycles.tpl'), 'utf-8' - ) + ), { + HAS_PLUGIN_MODEL + }) }); api.writeTmpFile({ @@ -110,6 +131,7 @@ export default function (api) { content: ` if (window.__POWERED_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 }); ` }); + + if (HAS_PLUGIN_MODEL) { + api.writeTmpFile({ + path: absModelPath, + content: readFileSync(join(__dirname, 'runtime/qiankunModel.tpl'), 'utf-8') + }); + } }); api.addEntryImports(() => ({ diff --git a/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl b/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl index 7781329f..4c71315d 100644 --- a/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl +++ b/packages/fes-plugin-qiankun/src/micro/runtime/lifecycles.tpl @@ -1,4 +1,7 @@ import { plugin, ApplyPluginsType } from '@@/core/coreExports'; +{{#HAS_PLUGIN_MODEL}} +import { setModelState } from './qiankunModel'; +{{/HAS_PLUGIN_MODEL}} const defer = {}; defer.promise = new Promise((resolve) => { @@ -47,6 +50,9 @@ export function genMount() { return async (props) => { // props 有值时说明应用是通过 lifecycle 被主应用唤醒的,而不是独立运行时自己 mount if (typeof props !== 'undefined') { +{{#HAS_PLUGIN_MODEL}} + setModelState(props); +{{/HAS_PLUGIN_MODEL}} const slaveRuntime = getSlaveRuntime(); if (slaveRuntime.mount) { await slaveRuntime.mount(props); @@ -68,6 +74,9 @@ export function genMount() { export function genUpdate() { return async (props) => { +{{#HAS_PLUGIN_MODEL}} + setModelState(props); +{{/HAS_PLUGIN_MODEL}} const slaveRuntime = await getSlaveRuntime(); if (slaveRuntime.update) { await slaveRuntime.update(props); diff --git a/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl b/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl new file mode 100644 index 00000000..7348a0b5 --- /dev/null +++ b/packages/fes-plugin-qiankun/src/micro/runtime/qiankunModel.tpl @@ -0,0 +1,11 @@ + +import { reactive } from 'vue'; + +let initState; +const setModelState = (val) => { + initState = val; +}; + +export default () => reactive(initState); + +export { setModelState };