From 5444448718d3a317d0ebc37a23614c391ee74b64 Mon Sep 17 00:00:00 2001 From: ZxBing0066 Date: Tue, 10 Mar 2020 08:04:35 +0800 Subject: [PATCH] add iframeStatusChange hook --- README.md | 4 +- examples/advance/index.ts | 11 ++++ src/plugins/iframe.ts | 125 +++++++++++++++++++++++++++++++++----- 3 files changed, 122 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 50d6505..28c7d0e 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ RAPIOP 出现的初心就是,让任意技术栈的前端项目(目前只支 - 参考`examples/basic/index.ts`,创建一个新的实例。 ```ts - import RAPIOP from "@rapiop/rapiop"; + import rapiop from "@rapiop/rapiop"; import { createBrowserHistory } from "history"; // 路由可自定义,或通过其它方式实现,非必要 const history = createBrowserHistory(); - const app = RAPIOP({ + const app = rapiop({ // 产品配置,支持函数、异步函数 config: { demo: { diff --git a/examples/advance/index.ts b/examples/advance/index.ts index 77a111d..05e1da9 100644 --- a/examples/advance/index.ts +++ b/examples/advance/index.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import { loadStyle } from '@rapiop/rapiop/lib/lib/load'; +import { IFRAME_STATUS } from '@rapiop/rapiop/lib/plugins/iframe'; import './style.css'; import { init as initFrame } from './frame'; @@ -58,6 +59,16 @@ if (!isInIframe) { app.hooks.error.tap('loading', () => { hideLoading(); }); + app.hooks.iframeStatusChange.tap('loading', (projectKey: string, status: string) => { + switch (status) { + case IFRAME_STATUS.create: + showLoading(); + break; + case IFRAME_STATUS.afterMount: + hideLoading(); + break; + } + }); const anotherApp = initAnotherApp(); anotherApp.hooks.mountDOM.call(document.getElementById('another-mount-dom')); diff --git a/src/plugins/iframe.ts b/src/plugins/iframe.ts index bb5f8bf..f89babd 100644 --- a/src/plugins/iframe.ts +++ b/src/plugins/iframe.ts @@ -1,9 +1,16 @@ +import { SyncHook } from 'tapable'; + import Hooks from '../Hooks'; import getType from '../lib/getType'; -const isInIframe = window.self !== window.top; +const isInIframe = window.self !== window.parent; -type Options = {}; +type Options = { + // 缓存的 iframe 项目上限 + cachedLimit?: number; + // 不清空历史 iframe 的内存上限 + memoryLimit?: number; +}; const createMountDOM = () => { const mountDOM = document.createElement('div'); @@ -14,45 +21,96 @@ const createMountDOM = () => { return mountDOM; }; -const createIframeDOM = () => { +const createIframeDOM = (projectKey: string) => { const iframeDOM = document.createElement('iframe'); iframeDOM.frameBorder = '0'; iframeDOM.width = '100%'; iframeDOM.height = '100%'; iframeDOM.style.display = 'block'; iframeDOM.src = location.href; + iframeDOM.setAttribute(`data-rapiop-project-${projectKey}`, ''); return iframeDOM; }; const RAPIOP_ROUTE_SYNC_EVENT = 'RAPIOP_ROUTE_SYNC'; +const RAPIOP_PROJECT_AFTER_MOUNT_EVENT = 'RAPIOP_PROJECT_AFTER_MOUNT'; +const RAPIOP_PROJECT_BACKGROUND_EVENT = 'RAPIOP_PROJECT_BACKGROUND'; +const RAPIOP_PROJECT_FOREGROUND_EVENT = 'RAPIOP_PROJECT_FOREGROUND'; + +const RAPIOP_IFRAME_STATUS_CREATA = 'RAPIOP_IFRAME_STATUS_CREATE'; +const RAPIOP_IFRAME_STATUS_AFTER_MOUNT = 'RAPIOP_IFRAME_STATUS_AFTER_MOUNT'; +const RAPIOP_IFRAME_STATUS_BACKGROUND = 'RAPIOP_IFRAME_STATUS_BACKGROUND'; +const RAPIOP_IFRAME_STATUS_FOREGROUND = 'RAPIOP_IFRAME_STATUS_FOREGROUND'; + +export const IFRAME_STATUS = { + create: RAPIOP_IFRAME_STATUS_CREATA, + afterMount: RAPIOP_IFRAME_STATUS_AFTER_MOUNT, + background: RAPIOP_IFRAME_STATUS_BACKGROUND, + foreground: RAPIOP_IFRAME_STATUS_FOREGROUND +}; export default class Iframe { options: Options; - constructor(options: Options) { + constructor(options: Options = {}) { this.options = options; } call({ hooks }: { hooks: Hooks }) { - let iframeMountDOM: HTMLElement = null; if (isInIframe) { + // Sync route info to parent window + const syncRoute = () => + window.parent.postMessage( + { + type: RAPIOP_ROUTE_SYNC_EVENT, + href: location.href + }, + location.origin + ); + // amend syncRoute function to instance hooks.amendInstance.tap('amend syncRoute', (instance, amendInstance) => { amendInstance({ - syncRoute: () => - window.top.postMessage( - { - type: RAPIOP_ROUTE_SYNC_EVENT, - href: location.href - }, - location.origin - ) + syncRoute }); }); + hooks.afterMount.tap('mount end', (projectKey: string) => { + window.parent.postMessage( + { + type: RAPIOP_PROJECT_AFTER_MOUNT_EVENT, + projectKey + }, + location.origin + ); + }); const mountDOM = createMountDOM(); hooks.mountDOM.call(mountDOM); + window.addEventListener('message', event => { + if (event.origin === location.origin && event.data) { + switch (event.data.type) { + case RAPIOP_PROJECT_FOREGROUND_EVENT: + syncRoute(); + break; + } + } + }); } else { + const iframeStatusChange = new SyncHook(['projectKey', 'status']); + let iframeMountDOM: HTMLElement = null; + const supportMemoryOptimization = (window.performance as any)?.memory?.usedJSHeapSize; + const { cachedLimit = 20, memoryLimit = 1024 * 1024 * 1024 } = this.options; + let cachedQueue: { + key: string; + destroy: () => void; + }[] = []; window.addEventListener('message', event => { - if (event.origin === location.origin && event.data && event.data.type === RAPIOP_ROUTE_SYNC_EVENT) { - history.replaceState(null, null, event.data.href); + if (event.origin === location.origin && event.data) { + switch (event.data.type) { + case RAPIOP_ROUTE_SYNC_EVENT: + history.replaceState(null, null, event.data.href); + break; + case RAPIOP_PROJECT_AFTER_MOUNT_EVENT: + iframeStatusChange.call(event.data.projectKey, IFRAME_STATUS.afterMount); + break; + } } }); hooks.amendInstance.tap('amend registerIframeMountDOM', (instance, amendInstance) => { @@ -60,6 +118,23 @@ export default class Iframe { registerIframeMountDOM: (dom: HTMLElement) => (iframeMountDOM = dom) }); }); + hooks.amendHooks.tap('amend iframe status change hook', (hooks, amendHooks) => { + amendHooks({ + iframeStatusChange + }); + }); + hooks.beforeMount.tap('clean iframe', (projectKey: string) => { + cachedQueue = cachedQueue.filter(({ key }) => key !== projectKey); + if (cachedQueue.length > cachedLimit) { + cachedQueue.shift().destroy(); + } + if (supportMemoryOptimization) { + const usedJSHeapSize = (window.performance as any).memory.usedJSHeapSize; + if (usedJSHeapSize > memoryLimit) { + cachedQueue.shift().destroy(); + } + } + }); hooks.afterConfig.tap('init iframe', (config, rapiop) => { const keys = Object.keys(config); keys.forEach(projectKey => { @@ -85,9 +160,15 @@ export default class Iframe { } if (iframeMountDOM && cachedIframe) { cachedIframe.style.display = 'block'; + cachedIframe.contentWindow.postMessage( + { type: RAPIOP_PROJECT_FOREGROUND_EVENT }, + location.origin + ); + iframeStatusChange.call(projectKey, IFRAME_STATUS.foreground); } else { - iframeDOM = createIframeDOM(); + iframeDOM = createIframeDOM(projectKey); (iframeMountDOM || mountDOM).appendChild(iframeDOM); + setTimeout(() => iframeStatusChange.call(projectKey, IFRAME_STATUS.create)); } }, (mountDOM: HTMLElement) => { @@ -98,6 +179,18 @@ export default class Iframe { if (isIframeModeCache && iframeMountDOM) { iframeDOM.style.display = 'none'; cachedIframe = iframeDOM; + cachedQueue.push({ + key: projectKey, + destroy: () => { + (iframeMountDOM || mountDOM).removeChild(iframeDOM); + iframeDOM = cachedIframe = null; + } + }); + cachedIframe.contentWindow.postMessage( + { type: RAPIOP_PROJECT_BACKGROUND_EVENT }, + location.origin + ); + iframeStatusChange.call(projectKey, IFRAME_STATUS.background); } else { (iframeMountDOM || mountDOM).removeChild(iframeDOM); iframeDOM = null;