From 9b1b661f1f704508cf7eae336fd033a03b5d1433 Mon Sep 17 00:00:00 2001 From: bailicangdu <1264889788@qq.com> Date: Mon, 13 Sep 2021 19:10:17 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=B8=B2?= =?UTF-8?q?=E6=9F=93=E7=BC=93=E5=AD=98micro-app=E5=85=83=E7=B4=A0=E6=97=B6?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84micro-app-head,=20micro-app-body?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/zh-cn/changelog.md | 9 +++ docs/zh-cn/configure.md | 4 +- docs/zh-cn/data.md | 2 +- docs/zh-cn/route.md | 71 ++++++++++++++++--- docs/zh-cn/static-source.md | 4 +- examples/main-vue2/src/pages/multiple.vue | 2 + .../main-vue3-vite/src/pages/multiple.vue | 2 + package.json | 2 +- src/__tests__/micro_app_element.test.ts | 37 ++++------ src/create_app.ts | 5 +- src/libs/utils.ts | 7 +- src/micro_app_element.ts | 39 ++++++---- 12 files changed, 130 insertions(+), 54 deletions(-) diff --git a/docs/zh-cn/changelog.md b/docs/zh-cn/changelog.md index 2b8f694cc..9b018be50 100644 --- a/docs/zh-cn/changelog.md +++ b/docs/zh-cn/changelog.md @@ -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` diff --git a/docs/zh-cn/configure.md b/docs/zh-cn/configure.md index 96d7ad235..80c26a80b 100644 --- a/docs/zh-cn/configure.md +++ b/docs/zh-cn/configure.md @@ -159,7 +159,7 @@ microApp.start({ ``` -### global(资源共享) +### global 当多个子应用使用相同的js或css资源,在link、script设置`global`属性会将文件提取为公共文件,共享给其它应用。 设置`global`属性后文件第一次加载会放入公共缓存,其它子应用加载相同的资源时直接从缓存中读取内容并渲染,从而提升性能、节省流量。 @@ -170,7 +170,7 @@ microApp.start({ ``` -### globalAssets(资源共享) +### globalAssets globalAssets用于设置全局共享资源,它和预加载的思路相同,在浏览器空闲时加载资源并放入缓存,提高渲染效率。 当子应用加载相同地址的js或css资源时,会直接从缓存中提取数据。 diff --git a/docs/zh-cn/data.md b/docs/zh-cn/data.md index 465df7b60..edb92f5f5 100644 --- a/docs/zh-cn/data.md +++ b/docs/zh-cn/data.md @@ -41,7 +41,7 @@ ``` diff --git a/docs/zh-cn/route.md b/docs/zh-cn/route.md index f24bbbbf4..6e61a32d5 100644 --- a/docs/zh-cn/route.md +++ b/docs/zh-cn/route.md @@ -5,7 +5,7 @@ micro-app不是iframe,不会重开一个window窗口,基座应用和子应用本质是在同一个页面渲染,所以影响到子应用路由的是浏览器地址。micro-app的url属性只是html的地址,它只是用来获取html。 -**举个栗子🌰 :** +**举个栗子 🌰 :** 浏览器地址为:`http://localhost:3000/page1/`,此时路由地址为`page1`。 @@ -21,7 +21,7 @@ micro-app不是iframe,不会重开一个window窗口,基座应用和子应 同理,页面参数和hash也是以浏览器为准。 -**再举个栗子🌰 :** +**栗子2 🌰 :** 子应用是hash路由,我们要渲染子应用的page1页面,那么下面的hash值是无效的,`#/page1`应该添加到浏览器地址上。 ```html @@ -32,7 +32,7 @@ micro-app不是iframe,不会重开一个window窗口,基座应用和子应 ``` -**再再举个栗子🌰 :** +**栗子3 🌰 :** 基座应用是history路由,子应用是hash路由,我们要跳转基座应用的`my-app`页面,页面中嵌入子应用,我们要展现子应用的`page1`页面。 @@ -46,8 +46,7 @@ micro-app配置如下: ``` - -**再再再举个栗子🌰 :** +**栗子4 🌰 :** 基座应用是history路由,子应用也是history路由,我们要跳转基座应用的`my-app`页面,`my-app`页面中嵌入子应用,我们要展现子应用的`page1`页面。 @@ -239,7 +238,63 @@ window.dispatchEvent(new PopStateEvent('popstate', { state: null })) > [!NOTE] > 1、popstate事件是全局发送的,所有正在运行的应用(包括发送popstate事件的应用)都会接受到popstate事件并进行路由匹配,此时要注意和兜底路由的冲突。 > -> 2、一些框架(比如angular)对popstate支持性不好,此时建议使用下面的方式2进行跳转。 +> 2、popstate常出现一些预料不到的问题,此时建议使用下面的方式2进行跳转。 + +### 2、基座路由控制 + +例如: + +**基座下发跳转方法:** + + +#### ** 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 ( +
+ +
+ ) +} +``` + +#### ** Vue ** + +```html + + + +``` + + +子应用通过 `window.microApp.getData().pushState(path)` 进行跳转。 -### 2、数据通信进行控制 -如基座下发指令控制子应用进行跳转,或者子应用向基座应用上传一个可以控制自身路由的函数。 +这种方式更加规范,出错的可能性更小。 diff --git a/docs/zh-cn/static-source.md b/docs/zh-cn/static-source.md index 0466c6597..590d9e19e 100644 --- a/docs/zh-cn/static-source.md +++ b/docs/zh-cn/static-source.md @@ -41,7 +41,7 @@ import './public-path' 当多个子应用拥有相同的js或css资源,可以指定这些资源在多个子应用之间共享,在子应用加载时直接从缓存中提取数据,从而提高渲染效率和性能。 设置资源共享的方式有两种: -#### 1、设置 globalAssets +#### 方式一、设置 globalAssets globalAssets用于设置全局共享资源,它和预加载的思路相同,在浏览器空闲时加载资源并放入缓存。 当子应用加载相同地址的js或css资源时,会直接从缓存中提取数据。 @@ -59,7 +59,7 @@ microApp.start({ }) ``` -#### 2、设置 global 属性 +#### 方式一、设置 global 属性 在link、script设置`global`属性会将文件提取为公共文件,共享给其它应用。 设置`global`属性后文件第一次加载会放入公共缓存,其它子应用加载相同的资源时直接从缓存中读取内容。 diff --git a/examples/main-vue2/src/pages/multiple.vue b/examples/main-vue2/src/pages/multiple.vue index 3768e2889..1fb75c7b4 100644 --- a/examples/main-vue2/src/pages/multiple.vue +++ b/examples/main-vue2/src/pages/multiple.vue @@ -8,6 +8,7 @@ url='http://localhost:3001/micro-app/react16/' baseRoute='/multiple' :data='data' + library='micro-app-react16' >
diff --git a/examples/main-vue3-vite/src/pages/multiple.vue b/examples/main-vue3-vite/src/pages/multiple.vue index c4ca3ad6d..bbb0b5ab2 100644 --- a/examples/main-vue3-vite/src/pages/multiple.vue +++ b/examples/main-vue3-vite/src/pages/multiple.vue @@ -8,6 +8,7 @@ url='http://localhost:3001/micro-app/react16/' baseRoute='/multiple' :data='data' + library='micro-app-react16' > diff --git a/package.json b/package.json index f61e66165..90f6d3f63 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/__tests__/micro_app_element.test.ts b/src/__tests__/micro_app_element.test.ts index 71efa9e46..9eea9b50d 100644 --- a/src/__tests__/micro_app_element.test.ts +++ b/src/__tests__/micro_app_element.test.ts @@ -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' @@ -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') @@ -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) + }) + }) }) diff --git a/src/create_app.ts b/src/create_app.ts index ba4824178..1ef61fc14 100644 --- a/src/create_app.ts +++ b/src/create_app.ts @@ -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) { @@ -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() } diff --git a/src/libs/utils.ts b/src/libs/utils.ts index fe0f5bc98..b2958d65a 100644 --- a/src/libs/utils.ts +++ b/src/libs/utils.ts @@ -244,10 +244,11 @@ export function pureCreateElement (tagNam * @param origin Cloned element * @param target Accept cloned elements */ -export function cloneNode (origin: T, target: Q): void { - const clonedOrigin = origin.cloneNode(true) +export function cloneNode (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) diff --git a/src/micro_app_element.ts b/src/micro_app_element.ts index c34b9b299..dc8fa0d39 100644 --- a/src/micro_app_element.ts +++ b/src/micro_app_element.ts @@ -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() 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 | 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 @@ -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( @@ -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() } } @@ -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 */