Skip to content

Commit

Permalink
fix(1.0.0-beta.7): Fix error when navigate before app.mount by microA…
Browse files Browse the repository at this point in the history
…pp.router
  • Loading branch information
bailicangdu committed Sep 20, 2023
1 parent 9133229 commit 4f2526b
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 52 deletions.
1 change: 1 addition & 0 deletions dev/main-react16/src/pages/react16/react16.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default class App extends React.Component {

handleCreated = () => {
console.log(`生命周期:created -- ${this.state.name}`)
// Promise.resolve().then(() => microApp.router.push({name: this.state.name, path: this.state.baseroute + '/page2'}))
}

beforemount = (e) => {
Expand Down
2 changes: 1 addition & 1 deletion dev/main-react16/src/pages/vite2/vite2.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function vite2 (props) {
url={`${config.vite2}micro-app/vite2/`}
// url={`http://127.0.0.1:8080/micro-app/vite2/`}
data={data}
onCreated={() => Promise.resolve().then(() => jumpToElementPlus())}
// onCreated={() => Promise.resolve().then(() => jumpToElementPlus())}
// onBeforemount={() => jumpToElementPlus()}
onMounted={handleMounted}
onDataChange={handleDataChange}
Expand Down
1 change: 1 addition & 0 deletions docs/1.x/zh-cn/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ interface RenderAppOptions {
name: string, // 应用名称,必传
url: string, // 应用地址,必传
container: string | Element, // 应用容器或选择器,必传
iframe?: boolean, // 是否切换为iframe沙箱,可选
inline?: boolean, // 开启内联模式运行js,可选
'disable-scopecss'?: boolean, // 关闭样式隔离,可选
'disable-sandbox'?: boolean, // 关闭沙箱,可选
Expand Down
8 changes: 5 additions & 3 deletions docs/1.x/zh-cn/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@

### 1.0.0-beta.7

`2023-09-19`
`2023-09-20`

- **New**

- 🆕 新增了子应用内部状态`before_mount`,用于标记子应用在资源加载后和执行js前的中间状态。

- **Bug Fix**

- 🐞 修复了在iframe模式下,子应用使用`monaco-editor`时代码输入框光标失效的问题。
- 🐞 修复了在iframe沙箱模式下,子应用使用`monaco-editor`时代码输入框光标失效的问题。
- 🐞 修复了在`window.mount`为Promise时抛出的错误无法捕获的问题。
- 🐞 修复了在iframe模式下,子应用加载完成之前进行导航导致报错的问题。
- 🐞 修复了在iframe沙箱模式下,子应用加载完成之前进行导航导致报错的问题。
- 🐞 修复了在with沙箱模式下,异步创建路由系统导致部分场景下location未定义的问题,issue [#908](https://github.com/micro-zoe/micro-app/issues/908)
- 🐞 修复了在micro-app子应用开始渲染到渲染完成之前通过路由API无法控制跳转的问题。

- **Update**

Expand Down
14 changes: 10 additions & 4 deletions src/create_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export default class CreateApp implements AppInterface {
public isPrerender: boolean
public prefetchLevel?: number
public fiber = false
public routerMode: string = DEFAULT_ROUTER_MODE
public routerMode: string

constructor ({
name,
Expand All @@ -93,6 +93,7 @@ export default class CreateApp implements AppInterface {
ssrUrl,
isPrefetch,
prefetchLevel,
routerMode,
}: CreateAppParam) {
appInstanceMap.set(name, this)
// init actions
Expand All @@ -102,6 +103,11 @@ export default class CreateApp implements AppInterface {
this.scopecss = this.useSandbox && scopecss
this.inline = inline ?? false
this.iframe = iframe ?? false
/**
* NOTE:
* 1. Navigate after micro-app created, before mount
*/
this.routerMode = routerMode || DEFAULT_ROUTER_MODE

// not exist when prefetch 👇
this.container = container ?? null
Expand Down Expand Up @@ -220,6 +226,8 @@ export default class CreateApp implements AppInterface {

this.createSandbox()

this.setAppState(appStates.BEFORE_MOUNT)

const nextAction = () => {
/**
* Special scenes:
Expand Down Expand Up @@ -264,8 +272,6 @@ export default class CreateApp implements AppInterface {
this.fiber = fiber
this.routerMode = routerMode

this.setAppState(appStates.BEFORE_MOUNT)

const dispatchBeforeMount = () => dispatchLifecyclesEvent(
this.container!,
this.name,
Expand Down Expand Up @@ -654,7 +660,7 @@ export default class CreateApp implements AppInterface {
}

// set app state
private setAppState (state: string): void {
public setAppState (state: string): void {
this.state = state
}

Expand Down
9 changes: 8 additions & 1 deletion src/micro_app_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import {
ObservedAttrName,
lifeCycles,
appStates,
} from './constants'
import CreateApp, {
appInstanceMap,
Expand Down Expand Up @@ -346,6 +347,7 @@ export function defineElement (tagName: string): void {
inline: this.getDisposeResult('inline'),
iframe: this.getDisposeResult('iframe'),
ssrUrl: this.ssrUrl,
routerMode: this.getMemoryRouterMode(),
})

/**
Expand Down Expand Up @@ -377,7 +379,12 @@ export function defineElement (tagName: string): void {
*/
private handleMount (app: AppInterface): void {
app.isPrefetch = false
// TODO: Can defer be removed?
/**
* Fix error when navigate before app.mount by microApp.router.push(...)
* Issue: https://github.com/micro-zoe/micro-app/issues/908
*/
app.setAppState(appStates.BEFORE_MOUNT)
// exec mount async, simulate the first render scene
defer(() => this.mount(app))
}

Expand Down
11 changes: 5 additions & 6 deletions src/sandbox/iframe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import {
resetDataCenterSnapshot,
} from '../../interact'
import {
patchRoute,
} from './route'
patchRouter,
} from './router'
import {
router,
initRouteStateWithURL,
Expand Down Expand Up @@ -83,13 +83,12 @@ export default class IframeSandbox {
this.microAppWindow = this.iframe!.contentWindow

this.patchIframe(this.microAppWindow, (resolve: CallableFunction) => {
// TODO: 优化代码
// create new html to iframe
this.createIframeTemplate(this.microAppWindow)
// get escapeProperties from plugins
this.getSpecialProperties(appName)
// patch location & history of child app
this.proxyLocation = patchRoute(appName, url, this.microAppWindow, browserHost)
this.proxyLocation = patchRouter(appName, url, this.microAppWindow, browserHost)
// patch window of child app
this.windowEffect = patchWindow(appName, this.microAppWindow, this)
// patch document of child app
Expand All @@ -100,7 +99,7 @@ export default class IframeSandbox {
* create static properties
* NOTE:
* 1. execute as early as possible
* 2. run after patchRoute & createProxyWindow
* 2. run after patchRouter & createProxyWindow
*/
this.initStaticGlobalKeys(appName, url)
resolve()
Expand Down Expand Up @@ -241,7 +240,7 @@ export default class IframeSandbox {
* create static properties
* NOTE:
* 1. execute as early as possible
* 2. run after patchRoute & createProxyWindow
* 2. run after patchRouter & createProxyWindow
*/
private initStaticGlobalKeys (appName: string, url: string): void {
this.microAppWindow.__MICRO_APP_ENVIRONMENT__ = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
assign,
} from '../../libs/utils'

export function patchRoute (
export function patchRouter (
appName: string,
url: string,
microAppWindow: microAppWindowType,
Expand Down
67 changes: 43 additions & 24 deletions src/sandbox/router/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
AccurateGuard,
SetDefaultPageOptions,
AttachAllToURLParam,
AppInterface,
} from '@micro-app/types'
import {
encodeMicroPath,
Expand Down Expand Up @@ -88,6 +89,46 @@ function createRouterApi (): RouterApi {
// clear element scope after navigate
removeDomScope()
}

/**
* navigation handler
* @param appName app.name
* @param app app instance
* @param to router target options
* @param replace use router.replace?
*/
function handleNavigate (
appName: string,
app: AppInterface,
to: RouterTarget,
replace: boolean,
): void {
const microLocation = app.sandBox!.proxyWindow.location as MicroLocation
const targetLocation = createURL(to.path, microLocation.href)
// Only get path data, even if the origin is different from microApp
const currentFullPath = microLocation.pathname + microLocation.search + microLocation.hash
const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash
if (currentFullPath !== targetFullPath || getMicroPathFromURL(appName) !== targetFullPath) {
const methodName = (replace && to.replace !== false) || to.replace === true ? 'replaceState' : 'pushState'
navigateWithRawHistory(appName, methodName, targetLocation, to.state)
/**
* TODO:
* 1. 关闭虚拟路由的跳转地址不同:baseRoute + 子应用地址,文档中要说明
* 2. 关闭虚拟路由时跳转方式不同:1、基座跳转但不发送popstate事件 2、控制子应用更新location,内部发送popstate事件。
* 核心思路:减小对基座的影响(子应用跳转不向基座发送popstate事件,其他操作一致),但这是必要的吗,只是多了一个触发popstate的操作
* 路由优化方案有两种:
* 1、减少对基座的影响,主要是解决vue循环刷新的问题
* 2、全局发送popstate事件,解决主、子都是vue3的冲突问题
* 两者选一个吧,如果选2,则下面这两行代码可以去掉
* NOTE1: history和search模式采用2,这样可以解决vue3的问题,custom采用1,避免vue循环刷新的问题,这样在用户出现问题时各有解决方案。但反过来说,每种方案又分别导致另外的问题,不统一,导致复杂度增高
* NOTE2: 关闭虚拟路由,同时发送popstate事件还是无法解决vue3的问题(毕竟history.state理论上还是会冲突),那么就没必要发送popstate事件了。
*/
if (isRouterModeCustom(appName)) {
updateMicroLocationWithEvent(appName, targetFullPath)
}
}
}

/**
* create method of router.push/replace
* NOTE:
Expand All @@ -114,30 +155,8 @@ function createRouterApi (): RouterApi {
*/
if (getActiveApps({ excludeHiddenApp: true, excludePreRender: true }).includes(appName)) {
const app = appInstanceMap.get(appName)!
const microLocation = app.sandBox!.proxyWindow.location as MicroLocation
const targetLocation = createURL(to.path, microLocation.href)
// Only get path data, even if the origin is different from microApp
const currentFullPath = microLocation.pathname + microLocation.search + microLocation.hash
const targetFullPath = targetLocation.pathname + targetLocation.search + targetLocation.hash
if (currentFullPath !== targetFullPath || getMicroPathFromURL(appName) !== targetFullPath) {
const methodName = (replace && to.replace !== false) || to.replace === true ? 'replaceState' : 'pushState'
navigateWithRawHistory(appName, methodName, targetLocation, to.state)
/**
* TODO:
* 1. 关闭虚拟路由的跳转地址不同:baseRoute + 子应用地址,文档中要说明
* 2. 关闭虚拟路由时跳转方式不同:1、基座跳转但不发送popstate事件 2、控制子应用更新location,内部发送popstate事件。
* 核心思路:减小对基座的影响(子应用跳转不向基座发送popstate事件,其他操作一致),但这是必要的吗,只是多了一个触发popstate的操作
* 路由优化方案有两种:
* 1、减少对基座的影响,主要是解决vue循环刷新的问题
* 2、全局发送popstate事件,解决主、子都是vue3的冲突问题
* 两者选一个吧,如果选2,则下面这两行代码可以去掉
* NOTE1: history和search模式采用2,这样可以解决vue3的问题,custom采用1,避免vue循环刷新的问题,这样在用户出现问题时各有解决方案。但反过来说,每种方案又分别导致另外的问题,不统一,导致复杂度增高
* NOTE2: 关闭虚拟路由,同时发送popstate事件还是无法解决vue3的问题(毕竟history.state理论上还是会冲突),那么就没必要发送popstate事件了。
*/
if (isRouterModeCustom(appName)) {
updateMicroLocationWithEvent(appName, targetFullPath)
}
}
const navigateAction = () => handleNavigate(appName, app, to, replace)
app.iframe ? app.sandBox.sandboxReady.then(navigateAction) : navigateAction()
} else {
logWarn('navigation failed, app does not exist or is inactive')
}
Expand Down
2 changes: 1 addition & 1 deletion src/sandbox/router/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export function isEffectiveApp (appName: string): boolean {
const app = appInstanceMap.get(appName)
/**
* !!(app && !app.isPrefetch && !app.isHidden())
* TODO: 隐藏的keep-alive应用暂时不作为无效应用,原因如下
* NOTE: 隐藏的keep-alive应用暂时不作为无效应用,原因如下
* 1、隐藏后才执行去除浏览器上的微应用的路由信息的操作,导致微应用的路由信息无法去除
* 2、如果保持隐藏应用内部正常跳转,阻止同步路由信息到浏览器,这样理论上是好的,但是对于location跳转改如何处理?location跳转是基于修改浏览器地址后发送popstate事件实现的,所以应该是在隐藏后不支持通过location进行跳转
*/
Expand Down
14 changes: 3 additions & 11 deletions src/sandbox/with/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import {
getEffectivePath,
isArray,
isPlainObject,
isUndefined,
removeDomScope,
throttleDeferForSetAppName,
rawDefineProperty,
Expand Down Expand Up @@ -113,6 +112,8 @@ export default class WithSandBox implements WithSandBoxInterface {
this.adapter = new Adapter()
// get scopeProperties and escapeProperties from plugins
this.getSpecialProperties(appName)
// create location, history for child app
this.patchRouter(appName, url, this.microAppWindow)
// patch window of child app
this.windowEffect = patchWindow(appName, this.microAppWindow, this)
// patch document of child app
Expand All @@ -138,15 +139,6 @@ export default class WithSandBox implements WithSandBoxInterface {
this.active = true

/* --- memory router part --- start */
// create location, history for child app
if (isUndefined(this.microAppWindow.location)) {
this.setMicroAppRouter(
this.microAppWindow.__MICRO_APP_NAME__,
this.microAppWindow.__MICRO_APP_URL__,
this.microAppWindow,
)
}

// update microLocation, attach route info to browser url
this.initRouteState(defaultPage)

Expand Down Expand Up @@ -542,7 +534,7 @@ export default class WithSandBox implements WithSandBoxInterface {
}

// set location & history for memory router
private setMicroAppRouter (appName: string, url: string, microAppWindow: microAppWindowType): void {
private patchRouter (appName: string, url: string, microAppWindow: microAppWindowType): void {
const { microLocation, microHistory } = createMicroRouter(appName, url)
rawDefineProperties(microAppWindow, {
location: {
Expand Down
3 changes: 3 additions & 0 deletions typings/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ declare module '@micro-app/types' {
// app rendering error
onerror (e: Error): void

// set app state
setAppState (state: string): void

// get app state
getAppState (): string

Expand Down

0 comments on commit 4f2526b

Please sign in to comment.