Skip to content

Commit

Permalink
fix: 修复了渲染缓存micro-app元素时导致的micro-app-head, micro-app-body重复的问题
Browse files Browse the repository at this point in the history
  • Loading branch information
bailicangdu committed Sep 13, 2021
1 parent 3c99d79 commit 9b1b661
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 54 deletions.
9 changes: 9 additions & 0 deletions docs/zh-cn/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@

---

### 0.3.3

`2021-09-13`

- **Bug Fix**

- 🐞 修复了data属性赋值后插入文档时,初始化data值无法通过setAttribute拦截的问题
- 🐞 修复了渲染缓存micro-app元素时导致的micro-app-head, micro-app-body重复的问题

### 0.3.2

`2021-09-10`
Expand Down
4 changes: 2 additions & 2 deletions docs/zh-cn/configure.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ microApp.start({
<style exclude></style>
```

### global(资源共享)
### global
当多个子应用使用相同的js或css资源,在link、script设置`global`属性会将文件提取为公共文件,共享给其它应用。

设置`global`属性后文件第一次加载会放入公共缓存,其它子应用加载相同的资源时直接从缓存中读取内容并渲染,从而提升性能、节省流量。
Expand All @@ -170,7 +170,7 @@ microApp.start({
<script src="xx.js" global></script>
```

### globalAssets(资源共享)
### globalAssets
globalAssets用于设置全局共享资源,它和预加载的思路相同,在浏览器空闲时加载资源并放入缓存,提高渲染效率。

当子应用加载相同地址的js或css资源时,会直接从缓存中提取数据。
Expand Down
2 changes: 1 addition & 1 deletion docs/zh-cn/data.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<micro-app
name='my-app'
url='xx'
:data='data' // data只接受对象类型,数据变化时会重新发送
:data='dataxx' // data只接受对象类型,数据变化时会重新发送
/>
```
<!-- tabs:end -->
Expand Down
71 changes: 63 additions & 8 deletions docs/zh-cn/route.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

micro-app不是iframe,不会重开一个window窗口,基座应用和子应用本质是在同一个页面渲染,所以影响到子应用路由的是浏览器地址。micro-app的url属性只是html的地址,它只是用来获取html。

**举个栗子🌰 :**
**举个栗子 🌰 :**

浏览器地址为:`http://localhost:3000/page1/`,此时路由地址为`page1`

Expand All @@ -21,7 +21,7 @@ micro-app不是iframe,不会重开一个window窗口,基座应用和子应

同理,页面参数和hash也是以浏览器为准。

**再举个栗子🌰 :**
**栗子2 🌰 :**

子应用是hash路由,我们要渲染子应用的page1页面,那么下面的hash值是无效的,`#/page1`应该添加到浏览器地址上。
```html
Expand All @@ -32,7 +32,7 @@ micro-app不是iframe,不会重开一个window窗口,基座应用和子应
<micro-app url='http://www.xxx.com/'></micro-app>
```

**再再举个栗子🌰 :**
**栗子3 🌰 :**

基座应用是history路由,子应用是hash路由,我们要跳转基座应用的`my-app`页面,页面中嵌入子应用,我们要展现子应用的`page1`页面。

Expand All @@ -46,8 +46,7 @@ micro-app配置如下:
<micro-app url='http://www.xxx.com/'></micro-app>
```


**再再再举个栗子🌰 :**
**栗子4 🌰 :**

基座应用是history路由,子应用也是history路由,我们要跳转基座应用的`my-app`页面,`my-app`页面中嵌入子应用,我们要展现子应用的`page1`页面。

Expand Down Expand Up @@ -239,7 +238,63 @@ window.dispatchEvent(new PopStateEvent('popstate', { state: null }))
> [!NOTE]
> 1、popstate事件是全局发送的,所有正在运行的应用(包括发送popstate事件的应用)都会接受到popstate事件并进行路由匹配,此时要注意和兜底路由的冲突。
>
> 2、一些框架(比如angular)对popstate支持性不好,此时建议使用下面的方式2进行跳转。
> 2、popstate常出现一些预料不到的问题,此时建议使用下面的方式2进行跳转。
### 2、基座路由控制

例如:

**基座下发跳转方法:**
<!-- tabs:start -->

#### ** React **
```js
import { useEffect } from 'react'
import microApp from '@micro-zoe/micro-app'

export default (props) => {
function pushState (path) {
props.history.push(path)
}

useEffect(() => {
// 👇 基座向子应用下发一个名为pushState的方法
microApp.setData(子应用名称, { pushState })
}, [])

return (
<div>
<micro-app name='子应用名称' url='...'></micro-app>
</div>
)
}
```

#### ** Vue **

```html
<template
<micro-app name='子应用名称' url='...'></micro-app>
</template>

<script>
import microApp from '@micro-zoe/micro-app'
export default {
name: 'page',
created () {
// 👇 基座向子应用下发一个名为pushState的方法
microApp.setData(子应用名称, {
pushState: (path) => {
this.$router.push(path)
}
})
}
}
</script>
```
<!-- tabs:end -->

子应用通过 `window.microApp.getData().pushState(path)` 进行跳转。

### 2、数据通信进行控制
如基座下发指令控制子应用进行跳转,或者子应用向基座应用上传一个可以控制自身路由的函数。
这种方式更加规范,出错的可能性更小。
4 changes: 2 additions & 2 deletions docs/zh-cn/static-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import './public-path'
当多个子应用拥有相同的js或css资源,可以指定这些资源在多个子应用之间共享,在子应用加载时直接从缓存中提取数据,从而提高渲染效率和性能。

设置资源共享的方式有两种:
#### 1、设置 globalAssets
#### 方式一、设置 globalAssets
globalAssets用于设置全局共享资源,它和预加载的思路相同,在浏览器空闲时加载资源并放入缓存。

当子应用加载相同地址的js或css资源时,会直接从缓存中提取数据。
Expand All @@ -59,7 +59,7 @@ microApp.start({
})
```

#### 2、设置 global 属性
#### 方式一、设置 global 属性
在link、script设置`global`属性会将文件提取为公共文件,共享给其它应用。

设置`global`属性后文件第一次加载会放入公共缓存,其它子应用加载相同的资源时直接从缓存中读取内容。
Expand Down
2 changes: 2 additions & 0 deletions examples/main-vue2/src/pages/multiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
url='http://localhost:3001/micro-app/react16/'
baseRoute='/multiple'
:data='data'
library='micro-app-react16'
></micro-app>
<!-- destory inline scopecss='false' -->
<micro-app
class='multiple-micro-app'
name='vue22'
url='http://localhost:4001/micro-app/vue2/'
:data='data'
library='micro-app-vue2'
>
</micro-app>
</div>
Expand Down
2 changes: 2 additions & 0 deletions examples/main-vue3-vite/src/pages/multiple.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
url='http://localhost:3001/micro-app/react16/'
baseRoute='/multiple'
:data='data'
library='micro-app-react16'
></micro-app>
<!-- destory inline scopecss='false' -->
<micro-app
class='multiple-micro-app'
name='vue22'
url='http://localhost:4001/micro-app/vue2/'
:data='data'
library='micro-app-vue2'
>
</micro-app>
</div>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@micro-zoe/micro-app",
"version": "0.3.2",
"version": "0.3.3",
"description": "A minimalist solution for building micro front-end applications",
"private": false,
"main": "lib/index.min.js",
Expand Down
37 changes: 15 additions & 22 deletions src/__tests__/micro_app_element.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* eslint-disable promise/param-names */
import { commonStartEffect, releaseAllEffect, ports } from './common'
import { appInstanceMap } from '../create_app'
import MicroAppElement from '../micro_app_element'
import microApp from '..'
import { defer } from '../libs/utils'

Expand Down Expand Up @@ -163,27 +162,6 @@ describe('micro_app_element', () => {
appCon.removeChild(microappElement8)
})

// microAppCount为0后依然卸载应用,此时无法执行卸载
test('unmount app when microAppCount less than 1', async () => {
// 清空所有app
appCon.innerHTML = ''
const microappElement9 = document.createElement('micro-app')
microappElement9.setAttribute('name', 'test-app9')
microappElement9.setAttribute('url', `http://127.0.0.1:${ports.micro_app_element}/common/`)

appCon.appendChild(microappElement9)
await new Promise((reslove) => {
microappElement9.addEventListener('mounted', () => {
// @ts-ignore
microappElement9.disconnectedCallback()
expect(MicroAppElement.microAppCount).toBe(0)
reslove(true)
}, false)
})

appCon.removeChild(microappElement9)
})

// 重新渲染带有shadowDom和baseurl属性应用 -- 分支覆盖
test('coverage branch of remount app with shadowDom & baseurl', async () => {
const microappElement10 = document.createElement('micro-app')
Expand Down Expand Up @@ -253,4 +231,19 @@ describe('micro_app_element', () => {
})
})
})

// getBaseRouteCompatible 分支覆盖
test('coverage branch of getBaseRouteCompatible', async () => {
const microappElement14 = document.createElement('micro-app')
microappElement14.setAttribute('name', 'test-app14')
microappElement14.setAttribute('url', `http://127.0.0.1:${ports.micro_app_element}/common/`)
microappElement14.setAttribute('baseroute', '/path')

appCon.appendChild(microappElement14)
await new Promise((reslove) => {
microappElement14.addEventListener('mounted', () => {
reslove(true)
}, false)
})
})
})
5 changes: 2 additions & 3 deletions src/create_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export default class CreateApp implements AppInterface {

this.status = appStatus.MOUNTING

cloneNode(this.source.html!, this.container!)
cloneNode(this.source.html!, this.container! as Element)

this.sandBox?.start(this.baseroute)
if (!this.umdHookMount) {
Expand All @@ -142,11 +142,10 @@ export default class CreateApp implements AppInterface {
this.umdHookMount = mount as Func
this.umdHookunMount = unmount as Func
this.sandBox?.recordUmdSnapshot()
this.source.html!.innerHTML = ''
/**
* TODO: Some UI frameworks insert and record container elements to micro-app-body, such as modal and notification. The DOM remounted is a cloned element, so the cached elements of UI frameworks are invalid, this may cause bug when remount app
*/
cloneNode(this.container!, this.source.html!)
cloneNode(this.container! as Element, this.source.html!)
formatHTMLStyleAfterUmdInit(this.source.html!, this.name)
this.umdHookMount()
}
Expand Down
7 changes: 4 additions & 3 deletions src/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,11 @@ export function pureCreateElement<K extends keyof HTMLElementTagNameMap> (tagNam
* @param origin Cloned element
* @param target Accept cloned elements
*/
export function cloneNode <T extends Node, Q extends Node> (origin: T, target: Q): void {
const clonedOrigin = origin.cloneNode(true)
export function cloneNode <T extends Element, Q extends Element> (origin: T, target: Q): void {
target.innerHTML = ''
const clonedNode = origin.cloneNode(true)
const fragment = document.createDocumentFragment()
Array.from(clonedOrigin.childNodes).forEach((node: Node) => {
Array.from(clonedNode.childNodes).forEach((node: Node) => {
fragment.appendChild(node)
})
target.appendChild(fragment)
Expand Down
39 changes: 27 additions & 12 deletions src/micro_app_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,28 @@ import microApp from './micro_app'
import dispatchLifecyclesEvent from './interact/lifecycles_event'
import { listenUmountAppInline, replaseUnmountAppInline } from './libs/additional'

// record all micro-app elements
const elementInstanceMap = new Map<Element, boolean>()
export default class MicroAppElement extends HTMLElement implements MicroAppElementType {
static microAppCount = 0
static get observedAttributes (): string[] {
return ['name', 'url']
}

constructor () {
super()
// cloned node of umd container also trigger constructor, we should skip
if (!this.querySelector('micro-app-head')) {
this.performWhenFirstCreated()
}
}

appName = ''
appUrl = ''
version = version
isWating = false
cacheData: Record<PropertyKey, unknown> | null = null

// 👇Configuration
// 👇 Configuration
// shadowDom: use shadowDOM, default is false
// destory: whether delete cache resources when unmount, default is false
// inline: whether js runs in inline script mode, default is false
Expand All @@ -33,10 +42,8 @@ export default class MicroAppElement extends HTMLElement implements MicroAppElem
// baseRoute: route prefix, default is ''

connectedCallback (): void {
if (++MicroAppElement.microAppCount === 1) {
patchElementPrototypeMethods()
rejectMicroAppStyle()
listenUmountAppInline()
if (!elementInstanceMap.has(this)) {
this.performWhenFirstCreated()
}

defer(() => dispatchLifecyclesEvent(
Expand Down Expand Up @@ -71,12 +78,11 @@ export default class MicroAppElement extends HTMLElement implements MicroAppElem
}

disconnectedCallback (): void {
if (MicroAppElement.microAppCount > 0) {
this.handleUnmount(this.getDisposeResult('destory'))
if (--MicroAppElement.microAppCount === 0) {
releasePatches()
replaseUnmountAppInline()
}
elementInstanceMap.delete(this)
this.handleUnmount(this.getDisposeResult('destory'))
if (elementInstanceMap.size === 0) {
releasePatches()
replaseUnmountAppInline()
}
}

Expand Down Expand Up @@ -104,6 +110,15 @@ export default class MicroAppElement extends HTMLElement implements MicroAppElem
}
}

// Perform global initialization when the element count is 1
performWhenFirstCreated (): void {
if (elementInstanceMap.set(this, true).size === 1) {
patchElementPrototypeMethods()
rejectMicroAppStyle()
listenUmountAppInline()
}
}

/**
* handle for change of name an url after element inited
*/
Expand Down

0 comments on commit 9b1b661

Please sign in to comment.