diff --git a/examples/angular12/.browserslistrc b/examples/angular12/.browserslistrc index 427441dc9..ff704229d 100644 --- a/examples/angular12/.browserslistrc +++ b/examples/angular12/.browserslistrc @@ -14,4 +14,4 @@ last 2 Edge major versions last 2 Safari major versions last 2 iOS major versions Firefox ESR -not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. +IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/examples/angular12/tsconfig.json b/examples/angular12/tsconfig.json index 6df828326..a2e58cc10 100644 --- a/examples/angular12/tsconfig.json +++ b/examples/angular12/tsconfig.json @@ -14,7 +14,7 @@ "experimentalDecorators": true, "moduleResolution": "node", "importHelpers": true, - "target": "es2017", + "target": "es5", "module": "es2020", "lib": [ "es2018", @@ -27,4 +27,4 @@ "strictInputAccessModifiers": true, "strictTemplates": true } -} +} \ No newline at end of file diff --git a/examples/main-react/package.json b/examples/main-react/package.json index a3ae556f7..8ee4aaf06 100644 --- a/examples/main-react/package.json +++ b/examples/main-react/package.json @@ -86,14 +86,14 @@ }, "browserslist": { "production": [ - ">0.2%", - "not dead", - "not op_mini all" + ">1%", + "last 2 versions", + "ie >= 11" ], "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" + ">1%", + "last 2 versions", + "ie >= 11" ] }, "jest": { @@ -152,4 +152,4 @@ "react-app" ] } -} +} \ No newline at end of file diff --git a/examples/main-react/public/favicon.ico b/examples/main-react/public/favicon.ico index a11777cc4..5c125de5d 100644 Binary files a/examples/main-react/public/favicon.ico and b/examples/main-react/public/favicon.ico differ diff --git a/examples/main-react/public/index.html b/examples/main-react/public/index.html index afb711a8e..e235d16dc 100644 --- a/examples/main-react/public/index.html +++ b/examples/main-react/public/index.html @@ -1,22 +1,19 @@ - - - - - - - - - - - - 无界react-demo展示 - - - -
- - - + + + \ No newline at end of file diff --git a/examples/main-react/src/App.js b/examples/main-react/src/App.js index 5d58504a7..1ef7c9303 100644 --- a/examples/main-react/src/App.js +++ b/examples/main-react/src/App.js @@ -30,6 +30,7 @@ function Nav() { const [vue2Flag, setVue2Flag] = useState(location.pathname.includes("vue2-sub")); const [vue3Flag, setVue3Flag] = useState(location.pathname.includes("vue3-sub")); const [viteFlag, setViteFlag] = useState(location.pathname.includes("vite-sub")); + const degrade = window.Proxy // 在 xxx-sub 路由下子应用将激活路由同步给主应用,主应用跳转对应路由高亮菜单栏 bus.$on("sub-route-change", (name, path) => { @@ -107,10 +108,10 @@ function Nav() { ))} - (isActive ? "active" : "inactive")}> + {degrade && (isActive ? "active" : "inactive")}> vue3保活 handleFlag("vue3")} /> - + }
{subMap.vue3.map((item) => ( (isActive ? "active" : "inactive")}> @@ -118,10 +119,10 @@ function Nav() { ))}
- (isActive ? "active" : "inactive")}> + {degrade && (isActive ? "active" : "inactive")}> vite handleFlag("vite")} /> - + }
{subMap.vite.map((item) => ( (isActive ? "active" : "inactive")}> diff --git a/examples/main-react/src/index.css b/examples/main-react/src/index.css index f12dae6ec..541f4c64e 100644 --- a/examples/main-react/src/index.css +++ b/examples/main-react/src/index.css @@ -38,7 +38,7 @@ body { .nav .menu-icon { display: none; border: none; - background: var(--theme) !important; + background: rgb(241, 107, 95) !important; } .main-icon { @@ -107,18 +107,18 @@ h3 { } .nav a:hover { - color: var(--theme); + color: rgb(241, 107, 95); } .nav a.active { - color: var(--theme); + color: rgb(241, 107, 95); background: rgba(0, 0, 0, 0.05); } .alive { display: inline-block; white-space: nowrap; - background-color: var(--theme); + background-color: rgb(241, 107, 95); border-radius: 8px; margin-left: 4px; font-size: 10px; @@ -152,7 +152,7 @@ h3 { .title { font-size: 30px; - color: var(--theme); + color: rgb(241, 107, 95); text-align: center; } @@ -171,6 +171,12 @@ h3 { height: 40px; } +.ant-switch-disabled { + height: 40px; + padding: 5px; + margin-top: -5px; +} + .switch.ant-switch::after { top: 6px; width: 25px; @@ -190,8 +196,8 @@ h3 { transition: all 0.1s linear; } .docs:hover { - color: var(--theme); - border: 1px solid var(--theme) + color: rgb(241, 107, 95); + border: 1px solid rgb(241, 107, 95) } @@ -213,7 +219,7 @@ h3 { .home .ant-switch-checked { border: 1px solid rgb(217, 217, 217); box-shadow: 0 2px #00000004; - background: var(--theme); + background: rgb(241, 107, 95); color: rgb(44, 62, 80); } diff --git a/examples/main-react/src/index.js b/examples/main-react/src/index.js index f8904587a..454d510c3 100644 --- a/examples/main-react/src/index.js +++ b/examples/main-react/src/index.js @@ -1,3 +1,6 @@ +import "react-app-polyfill/stable"; +import "react-app-polyfill/ie11"; + import React from "react"; import ReactDOM from "react-dom"; import WujieReact from "wujie-react"; @@ -104,15 +107,17 @@ if (window.localStorage.getItem("preload") !== "false") { preloadApp({ name: "vue2", }); - preloadApp({ - name: "vue3", - }); preloadApp({ name: "angular12", }); - preloadApp({ - name: "vite", - }); + if (window.Proxy) { + preloadApp({ + name: "vue3", + }); + preloadApp({ + name: "vite", + }); + } } ReactDOM.render(, document.getElementById("root")); diff --git a/examples/main-react/src/pages/All.js b/examples/main-react/src/pages/All.js index c73b7e345..a02bd2e88 100644 --- a/examples/main-react/src/pages/All.js +++ b/examples/main-react/src/pages/All.js @@ -11,6 +11,7 @@ export default function React16() { const vue3Url = hostMap("//localhost:7300/"); const vite = hostMap("//localhost:7500/"); const angular12Url = hostMap("//localhost:7400/"); + const degrade = window.Proxy // 修正iframe的url,防止github pages csp报错 const props = { jump: (name) => { @@ -50,7 +51,7 @@ export default function React16() { props={props} >
-
+ {degrade &&
-
-
+
} + {degrade &&
-
+
}
1% last 2 versions -not dead +ie >= 11 diff --git a/examples/main-vue/package.json b/examples/main-vue/package.json index 768ea8ef5..9162858e8 100644 --- a/examples/main-vue/package.json +++ b/examples/main-vue/package.json @@ -10,8 +10,10 @@ "dependencies": { "ant-design-vue": "^1.7.8", "core-js": "^3.6.5", + "custom-event-polyfill": "^1.0.7", "vue": "^2.6.11", "vue-router": "^3.2.0", + "whatwg-fetch": "^3.6.2", "wujie": "workspace:^1.0.0-rc.25", "wujie-vue2": "workspace:^1.0.0-rc.25" }, @@ -28,4 +30,4 @@ "prettier": "^2.2.1", "vue-template-compiler": "^2.6.11" } -} +} \ No newline at end of file diff --git a/examples/main-vue/public/index.html b/examples/main-vue/public/index.html index 0189d9abf..ba04fb84e 100644 --- a/examples/main-vue/public/index.html +++ b/examples/main-vue/public/index.html @@ -1,18 +1,20 @@ - - - - - - 无界vue-demo展示 - - - - -
- - - + + + + + + + 无界vue-demo展示 + + + + +
+ + + + \ No newline at end of file diff --git a/examples/main-vue/src/App.vue b/examples/main-vue/src/App.vue index 7e7e7fe6b..7e4d83983 100644 --- a/examples/main-vue/src/App.vue +++ b/examples/main-vue/src/App.vue @@ -40,7 +40,7 @@ communication
- + vue3 保活 @@ -52,7 +52,7 @@ contact state - vite + :root { + --host-color: #f16b5f; + } + \ No newline at end of file diff --git a/examples/vue2/src/views/Location.vue b/examples/vue2/src/views/Location.vue index 42190f391..ac8329779 100644 --- a/examples/vue2/src/views/Location.vue +++ b/examples/vue2/src/views/Location.vue @@ -43,7 +43,7 @@ export default { methods: { handleClick() { if (window.__WUJIE?.degrade || !window.Proxy || !window.CustomElementRegistry) { - window.$wujie.location.href = "https://wujicode.cn/xy/app/prod/official/index"; + window.$wujie.location.href = "https://v2.vuejs.org/"; } else window.location.href = "https://wujicode.cn/xy/app/prod/official/index"; }, }, diff --git a/examples/vue2/vue.config.js b/examples/vue2/vue.config.js index 4cc4e2434..33056f7fe 100644 --- a/examples/vue2/vue.config.js +++ b/examples/vue2/vue.config.js @@ -13,4 +13,7 @@ module.exports = { }, port: "7200", }, + transpileDependencies: [ + "sockjs-client", + ], }; diff --git a/packages/wujie-core/.eslintrc.js b/packages/wujie-core/.eslintrc.js index 0488a3036..4bc6f9ebd 100644 --- a/packages/wujie-core/.eslintrc.js +++ b/packages/wujie-core/.eslintrc.js @@ -24,6 +24,9 @@ module.exports = { "@typescript-eslint/no-unused-vars": ["warn"], "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", "no-prototype-builtins": "off", + "no-self-assign": "off", + "no-empty": ["error", { allowEmptyCatch: true }], + "prettier/prettier": ["error", { endOfLine: "auto" }], }, ignorePatterns: ["esm/*.js", "__test__/apps/*/*.html"], }; diff --git a/packages/wujie-core/src/common.ts b/packages/wujie-core/src/common.ts index 2a16ce0bd..d1a6b1f46 100644 --- a/packages/wujie-core/src/common.ts +++ b/packages/wujie-core/src/common.ts @@ -190,8 +190,10 @@ export const rawElementAppendChild = HTMLElement.prototype.appendChild; export const rawElementRemoveChild = HTMLElement.prototype.removeChild; export const rawHeadInsertBefore = HTMLHeadElement.prototype.insertBefore; export const rawBodyInsertBefore = HTMLBodyElement.prototype.insertBefore; -export const rawAddEventListener = EventTarget.prototype.addEventListener; -export const rawRemoveEventListener = EventTarget.prototype.removeEventListener; +export const rawAddEventListener = Node.prototype.addEventListener; +export const rawRemoveEventListener = Node.prototype.removeEventListener; +export const rawWindowAddEventListener = window.addEventListener; +export const rawWindowRemoveEventListener = window.removeEventListener; export const rawAppendChild = Node.prototype.appendChild; export const rawDocumentQuerySelector = window.__POWERED_BY_WUJIE__ ? window.__WUJIE_RAW_DOCUMENT_QUERY_SELECTOR__ diff --git a/packages/wujie-core/src/effect.ts b/packages/wujie-core/src/effect.ts index 14ba5b173..7a25f6b7a 100644 --- a/packages/wujie-core/src/effect.ts +++ b/packages/wujie-core/src/effect.ts @@ -84,21 +84,27 @@ function patchStylesheetElement( function patchSheetInsertRule() { if (!RawInsertRule) return; stylesheetElement.sheet.insertRule = (rule: string, index?: number): number => { - stylesheetElement.innerHTML += rule; + innerHTMLDesc ? (stylesheetElement.innerHTML += rule) : (stylesheetElement.innerText += rule); return RawInsertRule.call(stylesheetElement.sheet, rule, index); }; } patchSheetInsertRule(); - Object.defineProperties(stylesheetElement, { - innerHTML: { - get: function () { - return innerHTMLDesc.get.call(stylesheetElement); - }, - set: function (code: string) { - innerHTMLDesc.set.call(stylesheetElement, cssLoader(code, "", curUrl)); - nextTick(() => handleStylesheetElementPatch(this, sandbox)); + + if (innerHTMLDesc) { + Object.defineProperties(stylesheetElement, { + innerHTML: { + get: function () { + return innerHTMLDesc.get.call(stylesheetElement); + }, + set: function (code: string) { + innerHTMLDesc.set.call(stylesheetElement, cssLoader(code, "", curUrl)); + nextTick(() => handleStylesheetElementPatch(this, sandbox)); + }, }, - }, + }); + } + + Object.defineProperties(stylesheetElement, { innerText: { get: function () { return innerTextDesc.get.call(stylesheetElement); @@ -292,7 +298,7 @@ function rewriteAppendOrInsertChild(opts: { try { // 降级的dom-iframe无需处理 if (!element.getAttribute(WUJIE_DATA_ID)) { - const patchScript = (element as HTMLIFrameElement).contentWindow.document.createElement("script"); + const patchScript = (element as HTMLIFrameElement).contentDocument.createElement("script"); patchScript.type = "text/javascript"; patchScript.innerHTML = `Array.prototype.slice.call(window.parent.frames).some(function(iframe){if(iframe.name === '${wujieId}'){window.parent = iframe;return true};return false})`; element.contentDocument.head.insertBefore(patchScript, element.contentDocument.head.firstChild); diff --git a/packages/wujie-core/src/iframe.ts b/packages/wujie-core/src/iframe.ts index c1ac5aeb3..e12b607ab 100644 --- a/packages/wujie-core/src/iframe.ts +++ b/packages/wujie-core/src/iframe.ts @@ -1,6 +1,6 @@ import WuJie from "./sandbox"; import { ScriptObject } from "./template"; -import { renderElementToContainer, clearChild } from "./shadow"; +import { renderElementToContainer } from "./shadow"; import { syncUrlToWindow } from "./sync"; import { fixElementCtrSrcOrHref, @@ -26,6 +26,8 @@ import { appWindowOnEvent, windowProxyProperties, windowRegWhiteList, + rawWindowAddEventListener, + rawWindowRemoveEventListener, } from "./common"; import { getJsLoader } from "./plugin"; import { WUJIE_TIPS_SCRIPT_ERROR_REQUESTED, WUJIE_DATA_FLAG } from "./constant"; @@ -79,6 +81,14 @@ declare global { interface HTMLBodyElement { _cacheListeners: Map; } + interface Document { + createTreeWalker( + root: Node, + whatToShow?: number, + filter?: NodeFilter | null, + entityReferenceExpansion?: boolean + ): TreeWalker; + } } /** @@ -94,10 +104,10 @@ function patchIframeEvents(iframeWindow: Window) { execHooks(iframeWindow.__WUJIE.plugins, "windowAddEventListenerHook", iframeWindow, type, listener, options); if (appWindowAddEventListenerEvents.includes(type)) { - return rawAddEventListener.call(iframeWindow, type, listener, options); + return rawWindowAddEventListener.call(iframeWindow, type, listener, options); } // 在子应用嵌套场景使用window.window获取真实window - rawAddEventListener.call(window.__WUJIE_RAW_WINDOW__ || window, type, listener, options); + rawWindowAddEventListener.call(window.__WUJIE_RAW_WINDOW__ || window, type, listener, options); }; iframeWindow.removeEventListener = function removeEventListener( @@ -109,15 +119,16 @@ function patchIframeEvents(iframeWindow: Window) { execHooks(iframeWindow.__WUJIE.plugins, "windowRemoveEventListenerHook", iframeWindow, type, listener, options); if (appWindowAddEventListenerEvents.includes(type)) { - return rawRemoveEventListener.call(iframeWindow, type, listener, options); + return rawWindowRemoveEventListener.call(iframeWindow, type, listener, options); } - rawRemoveEventListener.call(window.__WUJIE_RAW_WINDOW__ || window, type, listener, options); + rawWindowRemoveEventListener.call(window.__WUJIE_RAW_WINDOW__ || window, type, listener, options); }; } -function patchIframeVariable(iframeWindow: Window, appHostPath: string): void { +function patchIframeVariable(iframeWindow: Window, wujie: WuJie, appHostPath: string): void { + iframeWindow.__WUJIE = wujie; iframeWindow.__WUJIE_PUBLIC_PATH__ = appHostPath + "/"; - iframeWindow.$wujie = iframeWindow.__WUJIE.provide; + iframeWindow.$wujie = wujie.provide; iframeWindow.__WUJIE_RAW_WINDOW__ = iframeWindow; iframeWindow.__WUJIE_RAW_DOCUMENT_QUERY_SELECTOR__ = iframeWindow.Document.prototype.querySelector; iframeWindow.__WUJIE_RAW_DOCUMENT_QUERY_SELECTOR_ALL__ = iframeWindow.Document.prototype.querySelectorAll; @@ -250,7 +261,7 @@ function patchWindowEffect(iframeWindow: Window): void { */ function recordEventListeners(iframeWindow: Window) { const sandbox = iframeWindow.__WUJIE; - iframeWindow.EventTarget.prototype.addEventListener = function ( + iframeWindow.Node.prototype.addEventListener = function ( type: string, handler: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions @@ -265,7 +276,7 @@ function recordEventListeners(iframeWindow: Window) { return rawAddEventListener.call(this, type, handler, options); }; - iframeWindow.EventTarget.prototype.removeEventListener = function ( + iframeWindow.Node.prototype.removeEventListener = function ( type: string, handler: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions @@ -286,13 +297,13 @@ function recordEventListeners(iframeWindow: Window) { /** * 恢复节点的监听事件 */ -export function recoverEventListeners(rootElement: Element, iframeWindow: Window) { +export function recoverEventListeners(rootElement: Element | ChildNode, iframeWindow: Window) { const sandbox = iframeWindow.__WUJIE; const elementEventCacheMap: WeakMap< Node, Array<{ type: string; handler: EventListenerOrEventListenerObject; options: any }> > = new WeakMap(); - const ElementIterator = document.createTreeWalker(rootElement); + const ElementIterator = document.createTreeWalker(rootElement, NodeFilter.SHOW_ELEMENT, null, false); let nextElement = ElementIterator.currentNode; while (nextElement) { const elementListenerList = sandbox.elementEventCacheMap.get(nextElement); @@ -310,7 +321,11 @@ export function recoverEventListeners(rootElement: Element, iframeWindow: Window /** * 恢复根节点的监听事件 */ -export function recoverDocumentListeners(oldRootElement: Element, newRootElement: Element, iframeWindow: Window) { +export function recoverDocumentListeners( + oldRootElement: Element | ChildNode, + newRootElement: Element | ChildNode, + iframeWindow: Window +) { const sandbox = iframeWindow.__WUJIE; const elementEventCacheMap: WeakMap< Node, @@ -378,8 +393,8 @@ function patchDocumentEffect(iframeWindow: Window): void { if (appDocumentAddEventListenerEvents.includes(type)) { return rawAddEventListener.call(this, type, callback, options); } - // 降级统一走document.firstElementChild - if (sandbox.degrade) return sandbox.document.firstElementChild.addEventListener(type, callback, options); + // 降级统一走 sandbox.document + if (sandbox.degrade) return sandbox.document.addEventListener(type, callback, options); if (mainDocumentAddEventListenerEvents.includes(type)) return window.document.addEventListener(type, callback, options); if (mainAndAppAddEventListenerEvents.includes(type)) { @@ -410,7 +425,7 @@ function patchDocumentEffect(iframeWindow: Window): void { if (appDocumentAddEventListenerEvents.includes(type)) { return rawRemoveEventListener.call(this, type, callback, options); } - if (sandbox.degrade) return sandbox.document.firstElementChild.removeEventListener(type, callback, options); + if (sandbox.degrade) return sandbox.document.removeEventListener(type, callback, options); if (mainDocumentAddEventListenerEvents.includes(type)) { return window.document.removeEventListener(type, callback, options); } @@ -438,14 +453,12 @@ function patchDocumentEffect(iframeWindow: Window): void { Object.defineProperty(iframeWindow.Document.prototype, e, { enumerable: descriptor.enumerable, configurable: true, - get: () => (sandbox.degrade ? sandbox.document : sandbox.shadowRoot).firstElementChild[e], + get: () => (sandbox.degrade ? sandbox.document[e] : sandbox.shadowRoot.firstElementChild[e]), set: descriptor.writable || descriptor.set ? (handler) => { const val = typeof handler === "function" ? handler.bind(iframeWindow.document) : handler; - sandbox.degrade - ? (sandbox.document.firstElementChild[e] = val) - : (sandbox.shadowRoot.firstElementChild[e] = val); + sandbox.degrade ? (sandbox.document[e] = val) : (sandbox.shadowRoot.firstElementChild[e] = val); } : undefined, }); @@ -525,7 +538,7 @@ function patchDocumentEffect(iframeWindow: Window): void { function patchNodeEffect(iframeWindow: Window): void { const rawGetRootNode = iframeWindow.Node.prototype.getRootNode; const rawAppendChild = iframeWindow.Node.prototype.appendChild; - const RawInsertRule = iframeWindow.Node.prototype.insertBefore; + const rawInsertRule = iframeWindow.Node.prototype.insertBefore; iframeWindow.Node.prototype.getRootNode = function (options?: GetRootNodeOptions): Node { const rootNode = rawGetRootNode.call(this, options); if (rootNode === iframeWindow.__WUJIE.shadowRoot) return iframeWindow.document; @@ -537,7 +550,7 @@ function patchNodeEffect(iframeWindow: Window): void { return res; }; iframeWindow.Node.prototype.insertBefore = function (node: T, child: Node | null): T { - const res = RawInsertRule.call(this, node, child); + const res = rawInsertRule.call(this, node, child); patchElementEffect(node, iframeWindow); return res; }; @@ -571,14 +584,20 @@ export function initBase(iframeWindow: Window, url: string): void { * 初始化iframe的dom结构 * @param iframeWindow */ -function initIframeDom(iframeWindow: Window): void { +function initIframeDom(iframeWindow: Window, wujie: WuJie, mainHostPath: string, appHostPath: string): void { const iframeDocument = iframeWindow.document; - clearChild(iframeDocument); - const html = iframeDocument.createElement("html"); - html.innerHTML = ""; - iframeDocument.appendChild(html); + const newDoc = window.document.implementation.createHTMLDocument(""); + const newDocumentElement = iframeDocument.importNode(newDoc.documentElement, true); + iframeDocument.documentElement + ? iframeDocument.replaceChild(newDocumentElement, iframeDocument.documentElement) + : iframeDocument.appendChild(newDocumentElement); + + initBase(iframeWindow, wujie.url); + patchIframeHistory(iframeWindow, appHostPath, mainHostPath); + patchIframeEvents(iframeWindow); + if (wujie.degrade) recordEventListeners(iframeWindow); + syncIframeUrlToWindow(iframeWindow); - initBase(iframeWindow, iframeWindow.__WUJIE.url); patchWindowEffect(iframeWindow); patchDocumentEffect(iframeWindow); patchNodeEffect(iframeWindow); @@ -589,25 +608,26 @@ function initIframeDom(iframeWindow: Window): void { * 防止运行主应用的js代码,给子应用带来很多副作用 */ // TODO 更加准确抓取停止时机 -function stopIframeLoading(iframeWindow: Window, url: string) { - iframeWindow.__WUJIE.iframeReady = new Promise((resolve) => { +function stopIframeLoading(iframeWindow: Window, mainHostPath: string, appHostPath: string, appRoutePath: string) { + const sandbox = iframeWindow.__WUJIE; + sandbox.iframeReady = new Promise((resolve) => { function loop() { setTimeout(() => { // location ready if (iframeWindow.location.href === "about:blank") { loop(); } else { - iframeWindow.stop(); - initIframeDom(iframeWindow); + iframeWindow.stop ? iframeWindow.stop() : iframeWindow.document.execCommand("Stop"); + initIframeDom(iframeWindow, sandbox, mainHostPath, appHostPath); /** * 如果有同步优先同步,非同步从url读取 */ if (!isMatchSyncQueryById(iframeWindow.__WUJIE.id)) { - iframeWindow.history.replaceState(null, "", url); + iframeWindow.history.replaceState(null, "", mainHostPath + appRoutePath); } resolve(); } - }, 0); + }, 1); } loop(); }); @@ -734,17 +754,13 @@ export function iframeGenerator( appRoutePath: string ): HTMLIFrameElement { const iframe = window.document.createElement("iframe"); - const url = mainHostPath + appRoutePath; const attrsMerge = { src: mainHostPath, ...attrs, style: "display: none", name: sandbox.id, [WUJIE_DATA_FLAG]: "" }; Object.keys(attrsMerge).forEach((key) => iframe.setAttribute(key, attrsMerge[key])); window.document.body.appendChild(iframe); + const iframeWindow = iframe.contentWindow; - iframeWindow.__WUJIE = sandbox; - patchIframeVariable(iframeWindow, appHostPath); - stopIframeLoading(iframeWindow, url); - patchIframeHistory(iframeWindow, appHostPath, mainHostPath); - patchIframeEvents(iframeWindow); - if (iframeWindow.__WUJIE.degrade) recordEventListeners(iframeWindow); - syncIframeUrlToWindow(iframeWindow); + // 变量需要提前注入,在入口函数通过变量防止死循环 + patchIframeVariable(iframeWindow, sandbox, appHostPath); + stopIframeLoading(iframeWindow, mainHostPath, appHostPath, appRoutePath); return iframe; } diff --git a/packages/wujie-core/src/proxy.ts b/packages/wujie-core/src/proxy.ts index de5066bc8..4eb6cd7fc 100644 --- a/packages/wujie-core/src/proxy.ts +++ b/packages/wujie-core/src/proxy.ts @@ -26,7 +26,7 @@ function locationHrefSet(iframe: HTMLIFrameElement, value: string, appHostPath: iframe.contentWindow.__WUJIE.hrefFlag = true; if (degrade) { const iframeBody = rawDocumentQuerySelector.call(iframe.contentDocument, "body"); - renderElementToContainer(document.firstElementChild, iframeBody); + renderElementToContainer(document.documentElement, iframeBody); renderIframeReplaceApp(window.decodeURIComponent(url), getDegradeIframe(id).parentElement); } else renderIframeReplaceApp(url, shadowRoot.host.parentElement); pushUrlToWindow(id, url); @@ -265,8 +265,10 @@ export function localGenerator( .concat(ownerProperties, shadowProperties, shadowMethods, documentProperties, documentMethods) .forEach((key) => { Object.defineProperty(proxyDocument, key, { - get: () => - isCallable(sandbox.document[key]) ? sandbox.document[key].bind(sandbox.document) : sandbox.document[key], + get: () => { + const value = sandbox.document?.[key]; + return isCallable(value) ? value.bind(sandbox.document) : value; + }, }); }); diff --git a/packages/wujie-core/src/sandbox.ts b/packages/wujie-core/src/sandbox.ts index b24bce40b..8b2b1473f 100644 --- a/packages/wujie-core/src/sandbox.ts +++ b/packages/wujie-core/src/sandbox.ts @@ -197,17 +197,13 @@ export default class Wujie { }; if (this.document) { if (this.alive) { - iframe.contentDocument.appendChild(this.document.firstElementChild); + iframe.contentDocument.appendChild(this.document.documentElement); // 保活场景需要事件全部恢复 - recoverEventListeners(iframe.contentDocument.firstElementChild, iframeWindow); + recoverEventListeners(iframe.contentDocument.documentElement, iframeWindow); } else { await renderTemplateToIframe(iframe.contentDocument, this.iframe.contentWindow, this.template); // 非保活场景需要恢复根节点的事件,防止react16监听事件丢失 - recoverDocumentListeners( - this.document.firstElementChild, - iframe.contentDocument.firstElementChild, - iframeWindow - ); + recoverDocumentListeners(this.document.documentElement, iframe.contentDocument.documentElement, iframeWindow); } } else { await renderTemplateToIframe(iframe.contentDocument, this.iframe.contentWindow, this.template); @@ -494,16 +490,15 @@ export default class Wujie { const { urlElement, appHostPath, appRoutePath } = appRouteParse(url); const { mainHostPath } = this.inject; // 创建iframe - const iframe = iframeGenerator(this, attrs, mainHostPath, appHostPath, appRoutePath); - this.iframe = iframe; + this.iframe = iframeGenerator(this, attrs, mainHostPath, appHostPath, appRoutePath); if (this.degrade) { - const { proxyDocument, proxyLocation } = localGenerator(iframe, urlElement, mainHostPath, appHostPath); + const { proxyDocument, proxyLocation } = localGenerator(this.iframe, urlElement, mainHostPath, appHostPath); this.proxyDocument = proxyDocument; this.proxyLocation = proxyLocation; } else { const { proxyWindow, proxyDocument, proxyLocation } = proxyGenerator( - iframe, + this.iframe, urlElement, mainHostPath, appHostPath diff --git a/packages/wujie-core/src/shadow.ts b/packages/wujie-core/src/shadow.ts index abfbd4df0..f4a68a7d4 100644 --- a/packages/wujie-core/src/shadow.ts +++ b/packages/wujie-core/src/shadow.ts @@ -33,30 +33,27 @@ declare global { } } -/** - * 制作webComponent沙箱 - */ -class WujieApp extends HTMLElement { - connectedCallback(): void { - if (this.shadowRoot) return; - const shadowRoot = this.attachShadow({ mode: "open" }); - const sandbox = getWujieById(this.getAttribute(WUJIE_DATA_ID)); - patchElementEffect(shadowRoot, sandbox.iframe.contentWindow); - sandbox.shadowRoot = shadowRoot; - } - - disconnectedCallback(): void { - const sandbox = getWujieById(this.getAttribute(WUJIE_DATA_ID)); - sandbox?.unmount(); - } -} - /** * 定义 wujie webComponent,将shadow包裹并获得dom装载和卸载的生命周期 */ export function defineWujieWebComponent() { - if (!customElements.get("wujie-app")) { - customElements.define("wujie-app", WujieApp); + const customElements = window.customElements; + if (customElements && !customElements?.get("wujie-app")) { + class WujieApp extends HTMLElement { + connectedCallback(): void { + if (this.shadowRoot) return; + const shadowRoot = this.attachShadow({ mode: "open" }); + const sandbox = getWujieById(this.getAttribute(WUJIE_DATA_ID)); + patchElementEffect(shadowRoot, sandbox.iframe.contentWindow); + sandbox.shadowRoot = shadowRoot; + } + + disconnectedCallback(): void { + const sandbox = getWujieById(this.getAttribute(WUJIE_DATA_ID)); + sandbox?.unmount(); + } + } + customElements?.define("wujie-app", WujieApp); } } @@ -70,7 +67,10 @@ export function createWujieWebComponent(id: string): HTMLElement { /** * 将准备好的内容插入容器 */ -export function renderElementToContainer(element: Element, selectorOrElement: string | HTMLElement): HTMLElement { +export function renderElementToContainer( + element: Element | ChildNode, + selectorOrElement: string | HTMLElement +): HTMLElement { const container = getContainer(selectorOrElement); if (container && !container.contains(element)) { // 有 loading 无需清理,已经清理过了 @@ -169,7 +169,7 @@ function renderTemplateToHtml(iframeWindow: Window, template: string): HTMLHtmlE sandbox.head = html.querySelector("head"); sandbox.body = html.querySelector("body"); } - const ElementIterator = document.createTreeWalker(html, NodeFilter.SHOW_ELEMENT); + const ElementIterator = document.createTreeWalker(html, NodeFilter.SHOW_ELEMENT, null, false); let nextElement = ElementIterator.currentNode as HTMLElement; while (nextElement) { patchElementEffect(nextElement, iframeWindow); @@ -242,7 +242,7 @@ export async function renderTemplateToIframe( renderDocument.appendChild(processedHtml); // 修复 html parentNode - Object.defineProperty(renderDocument.firstElementChild, "parentNode", { + Object.defineProperty(renderDocument.documentElement, "parentNode", { enumerable: true, configurable: true, get: () => iframeWindow.document, diff --git a/packages/wujie-core/src/sync.ts b/packages/wujie-core/src/sync.ts index e48f8d293..36c0e8e3a 100644 --- a/packages/wujie-core/src/sync.ts +++ b/packages/wujie-core/src/sync.ts @@ -77,7 +77,7 @@ export function clearInactiveAppUrl(): void { if (!sandbox) return; // 子应用执行过并且已经卸载才需要清除 const clearFlag = sandbox.degrade - ? !window.document.contains(getDegradeIframe(sandbox.id)) + ? !window.document.body.contains(getDegradeIframe(sandbox.id)) : !window.document.contains(sandbox?.shadowRoot?.host); if (sandbox.execFlag && sandbox.sync && !sandbox.hrefFlag && clearFlag) { delete queryMap[id]; @@ -129,7 +129,7 @@ export function processAppForHrefJump(): void { // 前进href if (/http/.test(url)) { if (sandbox.degrade) { - renderElementToContainer(sandbox.document.firstElementChild, iframeBody); + renderElementToContainer(sandbox.document.documentElement, iframeBody); renderIframeReplaceApp(window.decodeURIComponent(url), getDegradeIframe(sandbox.id).parentElement); } else renderIframeReplaceApp(window.decodeURIComponent(url), sandbox.shadowRoot.host.parentElement); sandbox.hrefFlag = true; diff --git a/packages/wujie-core/src/utils.ts b/packages/wujie-core/src/utils.ts index 2c7c545cb..096036183 100644 --- a/packages/wujie-core/src/utils.ts +++ b/packages/wujie-core/src/utils.ts @@ -118,13 +118,15 @@ export function appRouteParse(url: string): { } const urlElement = anchorElementGenerator(url); const appHostPath = urlElement.protocol + "//" + urlElement.host; - const appRoutePath = urlElement.pathname + urlElement.search + urlElement.hash; + let appRoutePath = urlElement.pathname + urlElement.search + urlElement.hash; + if (!appRoutePath.startsWith("/")) appRoutePath = "/" + appRoutePath; // fuck ie return { urlElement, appHostPath, appRoutePath }; } export function anchorElementGenerator(url: string): HTMLAnchorElement { const element = window.document.createElement("a"); element.href = url; + element.href = element.href; // fuck ie return element; } diff --git a/packages/wujie-core/tsconfig.json b/packages/wujie-core/tsconfig.json index 5167a7745..2117e658a 100644 --- a/packages/wujie-core/tsconfig.json +++ b/packages/wujie-core/tsconfig.json @@ -1,10 +1,13 @@ { - "include": ["./src/*.ts"], - "exclude": ["node_modules"], + "include": [ + "./src" + ], + "exclude": [ + "node_modules" + ], "compilerOptions": { "outDir": "./esm", "rootDir": "./src", - /* 额外的检查 */ "noUnusedLocals": true, "noUnusedParameters": true, @@ -12,10 +15,9 @@ "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "skipDefaultLibCheck": true, - "downlevelIteration": true, "declaration": true, "emitDeclarationOnly": true, "isolatedModules": true } -} +} \ No newline at end of file