From 0e1bba2a1d364da5850b852355e2d684ccc564b7 Mon Sep 17 00:00:00 2001 From: bailicangdu <1264889788@qq.com> Date: Wed, 26 Apr 2023 20:47:54 +0800 Subject: [PATCH] feat(1.0.0-beta.4): Optimize iframe support for disable memory router --- dev/children/vite2/.gitignore | 1 + dev/children/vite2/components.d.ts | 2 - dev/children/vite4/.gitignore | 1 + dev/children/vite4/package.json | 2 +- dev/main-react16/src/pages/vite4/vite4.js | 27 ++-- src/create_app.ts | 7 + src/sandbox/iframe/index.ts | 52 +++++--- src/sandbox/iframe/route.ts | 32 ----- src/sandbox/router/core.ts | 149 ++++++++++++---------- src/sandbox/router/history.ts | 4 +- src/sandbox/router/index.ts | 4 +- src/sandbox/router/location.ts | 1 + src/sandbox/with/index.ts | 10 +- typings/global.d.ts | 1 + 14 files changed, 158 insertions(+), 135 deletions(-) diff --git a/dev/children/vite2/.gitignore b/dev/children/vite2/.gitignore index 3b520b4a0..ef55d152e 100644 --- a/dev/children/vite2/.gitignore +++ b/dev/children/vite2/.gitignore @@ -1,6 +1,7 @@ node_modules .DS_Store dist +vite2 dist-ssr *.local /vite diff --git a/dev/children/vite2/components.d.ts b/dev/children/vite2/components.d.ts index 2fc8b29de..b7c03aea1 100644 --- a/dev/children/vite2/components.d.ts +++ b/dev/children/vite2/components.d.ts @@ -66,8 +66,6 @@ declare module '@vue/runtime-core' { ElTimePicker: typeof import('element-plus/es')['ElTimePicker'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] HelloWorld: typeof import('./src/components/HelloWorld.vue')['default'] - NButton: typeof import('naive-ui')['NButton'] - NPopover: typeof import('naive-ui')['NPopover'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } diff --git a/dev/children/vite4/.gitignore b/dev/children/vite4/.gitignore index a547bf36d..cb9e2ada3 100644 --- a/dev/children/vite4/.gitignore +++ b/dev/children/vite4/.gitignore @@ -9,6 +9,7 @@ lerna-debug.log* node_modules dist +vite4 dist-ssr *.local diff --git a/dev/children/vite4/package.json b/dev/children/vite4/package.json index 48db146d7..0c809e3a7 100644 --- a/dev/children/vite4/package.json +++ b/dev/children/vite4/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "start": "vite --force", + "start": "vite", "build": "vite build", "preview": "vite preview" }, diff --git a/dev/main-react16/src/pages/vite4/vite4.js b/dev/main-react16/src/pages/vite4/vite4.js index adb16c72a..4ef322557 100644 --- a/dev/main-react16/src/pages/vite4/vite4.js +++ b/dev/main-react16/src/pages/vite4/vite4.js @@ -40,17 +40,21 @@ function vite4 (props) { microApp.router.push({name: 'vite4', path: '/micro-app/vite4/ant-design-vue'}) } + function consoleRouteCurrent () { + console.log('router.current', microApp.router.current.get('vite4')) + } + useEffect(() => { console.time('vite4') - // const releaseBeforeEach1 = microApp.router.beforeEach((to, from, appName) => { - // console.log('全局 beforeEach: ', to, from, appName) - // }) + const releaseBeforeEach1 = microApp.router.beforeEach((to, from, appName) => { + console.log('全局 beforeEach: ', to, from, appName) + }) - // const releaseBeforeEach2 = microApp.router.beforeEach({ - // vite4 (to, from) { - // console.log('指定 beforeEach: ', to, from) - // } - // }) + const releaseBeforeEach2 = microApp.router.beforeEach({ + vite4 (to, from) { + console.log('指定 beforeEach: ', to, from) + } + }) // const releaseAfterEach1 = microApp.router.afterEach((to, from, appName) => { // console.log('全局 afterEach: ', to, from, appName) @@ -65,8 +69,8 @@ function vite4 (props) { microApp.router.setBaseAppRouter(props.history) return () => { - // releaseBeforeEach1() - // releaseBeforeEach2() + releaseBeforeEach1() + releaseBeforeEach2() // releaseAfterEach1() // releaseAfterEach2() } @@ -82,6 +86,7 @@ function vite4 (props) { + { @@ -106,7 +111,7 @@ function vite4 (props) { keep-router-state disable-memory-router // disable-patch-request - // keep-alive + keep-alive // default-page='/micro-app/vite4/page2' baseroute='/micro-app/demo/vite4' > diff --git a/src/create_app.ts b/src/create_app.ts index 16422590e..48135e8df 100644 --- a/src/create_app.ts +++ b/src/create_app.ts @@ -503,6 +503,7 @@ export default class CreateApp implements AppInterface { keepRouteState: keepRouteState && !destroy, destroy, clearData: clearData || destroy, + useMemoryRouter: this.useMemoryRouter, }) // dispatch unmount event to base app @@ -589,6 +590,12 @@ export default class CreateApp implements AppInterface { false, ) + /** + * TODO: + * 1. iframe沙箱在关闭虚拟路由系统时,重新展示时不更新浏览器地址,这样和with沙箱保持一致。 + * 但是iframe是可以做到重新展示时更新浏览器地址的,这里临时不支持,等待后续with沙箱也支持的时候再优化 + * 只需要去除 if (this.useMemoryRouter) 的判断即可 + */ if (this.useMemoryRouter) { // called before lifeCyclesEvent this.sandBox?.setRouteInfoForKeepAliveApp() diff --git a/src/sandbox/iframe/index.ts b/src/sandbox/iframe/index.ts index 6842c9f09..75fe1790a 100644 --- a/src/sandbox/iframe/index.ts +++ b/src/sandbox/iframe/index.ts @@ -28,7 +28,6 @@ import { } from '../../interact' import { patchIframeRoute, - actionsForDisableMemoryRoute, } from './route' import { router, @@ -160,19 +159,33 @@ export default class IframeSandbox { }: SandBoxStartParams): void { if (this.active) return this.active = true - if (useMemoryRouter) { - this.initRouteState(defaultPage) - // unique listener of popstate event for sub app - this.removeHistoryListener = addHistoryListener( - this.microAppWindow.__MICRO_APP_NAME__, - ) - } else { - // actions when memory-route disable - actionsForDisableMemoryRoute( - this.microAppWindow.__MICRO_APP_NAME__, - this.microAppWindow, - baseroute, - ) + /** + * Sync router info to iframe when exec sandbox.start with disable or enable memory-router + * e.g.: + * vue-router@4.x get target path by remove the base section from rawLocation.pathname + * code: window.location.pathname.slice(base.length) || '/'; (base is baseroute) + * NOTE: + * 1. iframe router and browser router are separated, we should update iframe router manually + * 2. withSandbox location is browser location when disable memory-router, so no need to do anything + */ + /** + * 做一些记录: + * 1. iframe关闭虚拟路由系统后,default-page无法使用,推荐用户直接使用浏览器地址控制首页渲染 + * 补充:keep-router-state 也无法配置,因为keep-router-state一定为true。 + * 2. 导航拦截、current.route 可以正常使用 + * 3. 可以正常控制子应用跳转,方式还是自上而下(也可以是子应用内部跳转,这种方式更好一点,减小对基座的影响,不会导致vue的循环刷新) + * 4. 关闭虚拟路由以后会对应 route-mode='custom' 模式,包括with沙箱也会这么做 + * 5. 关闭虚拟路由是指尽可能模式没有虚拟路由的情况,子应用直接获取浏览器location和history,控制浏览器跳转 + */ + this.initRouteState(defaultPage) + + // unique listener of popstate event for child app + this.removeHistoryListener = addHistoryListener( + this.microAppWindow.__MICRO_APP_NAME__, + ) + + if (!useMemoryRouter) { + this.microAppWindow.__MICRO_APP_BASE_ROUTE__ = this.microAppWindow.__MICRO_APP_BASE_URL__ = baseroute } /** @@ -198,15 +211,16 @@ export default class IframeSandbox { keepRouteState, destroy, clearData, + useMemoryRouter, }: SandBoxStopParams): void { if (!this.active) return this.recordAndReleaseEffect({ clearData }, !umdMode || destroy) - if (this.removeHistoryListener) { - this.clearRouteState(keepRouteState) - // release listener of popstate - this.removeHistoryListener() - } + // if keep-route-state is true or disable memory-router, preserve microLocation state + this.clearRouteState(keepRouteState || !useMemoryRouter) + + // release listener of popstate for child app + this.removeHistoryListener?.() if (!umdMode || destroy) { this.deleteIframeElement() diff --git a/src/sandbox/iframe/route.ts b/src/sandbox/iframe/route.ts index 93492be79..db0847464 100644 --- a/src/sandbox/iframe/route.ts +++ b/src/sandbox/iframe/route.ts @@ -12,7 +12,6 @@ import { import { assign, } from '../../libs/utils' -import globalEnv from '../../libs/global_env' export function patchIframeRoute ( appName: string, @@ -52,34 +51,3 @@ export function patchIframeRoute ( childHost, ) } - -/** - * actions when memory-route disable - * @param appName app name - * @param microAppWindow iframeWindow - * @param baseroute base route for child app - */ -export function actionsForDisableMemoryRoute ( - appName: string, - microAppWindow: microAppWindowType, - baseroute: string, -): void { - microAppWindow.__MICRO_APP_BASE_ROUTE__ = microAppWindow.__MICRO_APP_BASE_URL__ = baseroute - - /** - * Sync browser router info to iframe when disable memory-router - * e.g.: - * vue-router@4.x get target path by remove the base section from location.pathname - * code: window.location.pathname.slice(base.length) || '/'; (base is baseroute) - * NOTE: - * 1. iframe router and browser router are separated, we should update iframe router manually - * 2. withSandbox location is browser location when disable memory-router, so no need to do anything - */ - const rawLocation = globalEnv.rawWindow.location - updateMicroLocation( - appName, - rawLocation.href, - rawLocation, - 'prevent' - ) -} diff --git a/src/sandbox/router/core.ts b/src/sandbox/router/core.ts index 85d77fbc9..beb3173b1 100644 --- a/src/sandbox/router/core.ts +++ b/src/sandbox/router/core.ts @@ -23,35 +23,48 @@ export function setMicroState ( appName: string, microState: MicroState, ): MicroState { - if (!isMemoryRouterEnabled(appName)) return microState - const rawState = globalEnv.rawWindow.history.state - const additionalState: Record = { - microAppState: assign({}, rawState?.microAppState, { - [appName]: microState - }) + if (isMemoryRouterEnabled(appName)) { + const rawState = globalEnv.rawWindow.history.state + const additionalState: Record = { + microAppState: assign({}, rawState?.microAppState, { + [appName]: microState + }) + } + + // create new state object + return assign({}, rawState, additionalState) } - // create new state object - return assign({}, rawState, additionalState) + return microState } // delete micro app state form origin state export function removeMicroState (appName: string, rawState: MicroState): MicroState { - if (isPlainObject(rawState?.microAppState)) { - if (!isUndefined(rawState.microAppState[appName])) { - delete rawState.microAppState[appName] - } - if (!Object.keys(rawState.microAppState).length) { - delete rawState.microAppState + if (isMemoryRouterEnabled(appName)) { + if (isPlainObject(rawState?.microAppState)) { + if (!isUndefined(rawState.microAppState[appName])) { + delete rawState.microAppState[appName] + } + if (!Object.keys(rawState.microAppState).length) { + delete rawState.microAppState + } } + + return assign({}, rawState) } - return assign({}, rawState) + return rawState } // get micro app state form origin state export function getMicroState (appName: string): MicroState { - return globalEnv.rawWindow.history.state?.microAppState?.[appName] || null + const rawState = globalEnv.rawWindow.history.state + + if (isMemoryRouterEnabled(appName)) { + return rawState?.microAppState?.[appName] || null + } + + return rawState } const ENC_AD_RE = /&/g // %M1 @@ -92,58 +105,62 @@ function formatQueryAppName (appName: string) { */ export function getMicroPathFromURL (appName: string): string | null { const rawLocation = globalEnv.rawWindow.location - const queryObject = getQueryObjectFromURL(rawLocation.search, rawLocation.hash) - const microPath = queryObject.hashQuery?.[formatQueryAppName(appName)] || queryObject.searchQuery?.[formatQueryAppName(appName)] - return isString(microPath) ? decodeMicroPath(microPath) : null + if (isMemoryRouterEnabled(appName)) { + const queryObject = getQueryObjectFromURL(rawLocation.search, rawLocation.hash) + const microPath = queryObject.hashQuery?.[formatQueryAppName(appName)] || queryObject.searchQuery?.[formatQueryAppName(appName)] + return isString(microPath) ? decodeMicroPath(microPath) : null + } + return rawLocation.pathname + rawLocation.search + rawLocation.hash } /** * Attach child app fullPath to browser url * @param appName app.name - * @param microLocation location of child app + * @param targetLocation location of child app or rawLocation of window */ -export function setMicroPathToURL (appName: string, microLocation: MicroLocation): HandleMicroPathResult { - const targetFullPath = microLocation.pathname + microLocation.search + microLocation.hash +export function setMicroPathToURL (appName: string, targetLocation: MicroLocation): HandleMicroPathResult { + const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash let isAttach2Hash = false - if (!isMemoryRouterEnabled(appName)) { - return { - fullPath: targetFullPath, - isAttach2Hash, - } - } - let { pathname, search, hash } = globalEnv.rawWindow.location - const queryObject = getQueryObjectFromURL(search, hash) - const encodedMicroPath = encodeMicroPath(targetFullPath) - - /** - * Is parent is hash router - * In fact, this is not true. It just means that the parameter is added to the hash - */ - // If hash exists and search does not exist, it is considered as a hash route - if (hash && !search) { - isAttach2Hash = true - if (queryObject.hashQuery) { - queryObject.hashQuery[formatQueryAppName(appName)] = encodedMicroPath - } else { - queryObject.hashQuery = { - [formatQueryAppName(appName)]: encodedMicroPath + if (isMemoryRouterEnabled(appName)) { + let { pathname, search, hash } = globalEnv.rawWindow.location + const queryObject = getQueryObjectFromURL(search, hash) + const encodedMicroPath = encodeMicroPath(targetFullPath) + + /** + * Is parent is hash router + * In fact, this is not true. It just means that the parameter is added to the hash + */ + // If hash exists and search does not exist, it is considered as a hash route + if (hash && !search) { + isAttach2Hash = true + if (queryObject.hashQuery) { + queryObject.hashQuery[formatQueryAppName(appName)] = encodedMicroPath + } else { + queryObject.hashQuery = { + [formatQueryAppName(appName)]: encodedMicroPath + } } - } - const baseHash = hash.includes('?') ? hash.slice(0, hash.indexOf('?') + 1) : hash + '?' - hash = baseHash + stringifyQuery(queryObject.hashQuery) - } else { - if (queryObject.searchQuery) { - queryObject.searchQuery[formatQueryAppName(appName)] = encodedMicroPath + const baseHash = hash.includes('?') ? hash.slice(0, hash.indexOf('?') + 1) : hash + '?' + hash = baseHash + stringifyQuery(queryObject.hashQuery) } else { - queryObject.searchQuery = { - [formatQueryAppName(appName)]: encodedMicroPath + if (queryObject.searchQuery) { + queryObject.searchQuery[formatQueryAppName(appName)] = encodedMicroPath + } else { + queryObject.searchQuery = { + [formatQueryAppName(appName)]: encodedMicroPath + } } + search = '?' + stringifyQuery(queryObject.searchQuery) + } + + return { + fullPath: pathname + search + hash, + isAttach2Hash, } - search = '?' + stringifyQuery(queryObject.searchQuery) } return { - fullPath: pathname + search + hash, + fullPath: targetFullPath, isAttach2Hash, } } @@ -155,18 +172,20 @@ export function setMicroPathToURL (appName: string, microLocation: MicroLocation */ export function removeMicroPathFromURL (appName: string, targetLocation?: MicroLocation): HandleMicroPathResult { let { pathname, search, hash } = targetLocation || globalEnv.rawWindow.location - const queryObject = getQueryObjectFromURL(search, hash) - let isAttach2Hash = false - if (queryObject.hashQuery?.[formatQueryAppName(appName)]) { - isAttach2Hash = true - delete queryObject.hashQuery?.[formatQueryAppName(appName)] - const hashQueryStr = stringifyQuery(queryObject.hashQuery) - hash = hash.slice(0, hash.indexOf('?') + Number(Boolean(hashQueryStr))) + hashQueryStr - } else if (queryObject.searchQuery?.[formatQueryAppName(appName)]) { - delete queryObject.searchQuery?.[formatQueryAppName(appName)] - const searchQueryStr = stringifyQuery(queryObject.searchQuery) - search = searchQueryStr ? '?' + searchQueryStr : '' + + if (isMemoryRouterEnabled(appName)) { + const queryObject = getQueryObjectFromURL(search, hash) + if (queryObject.hashQuery?.[formatQueryAppName(appName)]) { + isAttach2Hash = true + delete queryObject.hashQuery?.[formatQueryAppName(appName)] + const hashQueryStr = stringifyQuery(queryObject.hashQuery) + hash = hash.slice(0, hash.indexOf('?') + Number(Boolean(hashQueryStr))) + hashQueryStr + } else if (queryObject.searchQuery?.[formatQueryAppName(appName)]) { + delete queryObject.searchQuery?.[formatQueryAppName(appName)] + const searchQueryStr = stringifyQuery(queryObject.searchQuery) + search = searchQueryStr ? '?' + searchQueryStr : '' + } } return { diff --git a/src/sandbox/router/history.ts b/src/sandbox/router/history.ts index 65c668c50..696b3423a 100644 --- a/src/sandbox/router/history.ts +++ b/src/sandbox/router/history.ts @@ -141,7 +141,9 @@ export function navigateWithNativeEvent ( const oldHref = result.isAttach2Hash && oldFullPath !== result.fullPath ? rawLocation.href : null // navigate with native history method nativeHistoryNavigate(appName, methodName, result.fullPath, state, title) - if (oldFullPath !== result.fullPath) dispatchNativeEvent(appName, onlyForBrowser, oldHref) + if (oldFullPath !== result.fullPath && isMemoryRouterEnabled(appName)) { + dispatchNativeEvent(appName, onlyForBrowser, oldHref) + } } } diff --git a/src/sandbox/router/index.ts b/src/sandbox/router/index.ts index 07184bd1d..0ed440d37 100644 --- a/src/sandbox/router/index.ts +++ b/src/sandbox/router/index.ts @@ -62,7 +62,9 @@ export function initRouteStateWithURL ( /** * initialize browser information according to microLocation - * called on sandbox.start or reshow of keep-alive app + * Scenes: + * 1. sandbox.start + * 2. reshow of keep-alive app */ export function updateBrowserURLWithLocation ( appName: string, diff --git a/src/sandbox/router/location.ts b/src/sandbox/router/location.ts index 809b313b9..a45c0e22e 100644 --- a/src/sandbox/router/location.ts +++ b/src/sandbox/router/location.ts @@ -258,6 +258,7 @@ export function updateMicroLocation ( ): void { // record old values of microLocation to `from` const from = createGuardLocation(appName, microLocation) + // if is iframeSandbox, microLocation muse be rawLocation of iframe, not proxyLocation const newLocation = createURL(path, microLocation.href) if (isIframeSandbox(appName)) { const microAppWindow = appInstanceMap.get(appName)!.sandBox.microAppWindow diff --git a/src/sandbox/with/index.ts b/src/sandbox/with/index.ts index be686f39d..38471b575 100644 --- a/src/sandbox/with/index.ts +++ b/src/sandbox/with/index.ts @@ -139,6 +139,7 @@ export default class WithSandBox implements WithSandBoxInterface { }: SandBoxStartParams): void { if (this.active) return this.active = true + // TODO: with沙箱关闭虚拟路由保持和iframe一致 if (useMemoryRouter) { if (isUndefined(this.microAppWindow.location)) { this.setMicroAppRouter( @@ -190,22 +191,25 @@ export default class WithSandBox implements WithSandBoxInterface { * @param keepRouteState prevent reset route * @param destroy completely destroy, delete cache resources * @param clearData clear data from base app + * @param useMemoryRouter use virtual router */ public stop ({ umdMode, keepRouteState, destroy, clearData, + useMemoryRouter, }: SandBoxStopParams): void { if (!this.active) return this.recordAndReleaseEffect({ umdMode, clearData, destroy }, !umdMode || destroy) - if (this.removeHistoryListener) { + if (useMemoryRouter) { this.clearRouteState(keepRouteState) - // release listener of popstate - this.removeHistoryListener() } + // release listener of popstate for child app + this.removeHistoryListener?.() + /** * NOTE: * 1. injectedKeys and escapeKeys must be placed at the back diff --git a/typings/global.d.ts b/typings/global.d.ts index 7922323a5..78806b042 100644 --- a/typings/global.d.ts +++ b/typings/global.d.ts @@ -52,6 +52,7 @@ declare module '@micro-app/types' { keepRouteState: boolean destroy: boolean clearData: boolean + useMemoryRouter: boolean } interface releaseGlobalEffectParams {