微前端无界机制浅析

  • 微前端无界机制浅析已关闭评论
  • 92 次浏览
  • A+
所属分类:Web前端
摘要

随着项目的发展,前端SPA应用的规模不断加大、业务代码耦合、编译慢,导致日常的维护难度日益增加。同时前端技术的发展迅猛,导致功能扩展吃力,重构成本高,稳定性低。


简介

随着项目的发展,前端SPA应用的规模不断加大、业务代码耦合、编译慢,导致日常的维护难度日益增加。同时前端技术的发展迅猛,导致功能扩展吃力,重构成本高,稳定性低。

为了能够将前端模块解耦,通过相关技术调研,最终选择了无界微前端框架作为物流客服系统解耦支持。为了更好的使用无界微前端框架,我们对其运行机制进行了相关了解,以下是对无界运行机制的一些认识。

基本用法

主应用配置

import WujieVue from 'wujie-vue2';  const { setupApp, preloadApp, bus } = WujieVue; /*设置缓存*/ setupApp({ }); /*预加载*/ preloadApp({   name: 'vue2' }) <WujieVue width="100%" height="100%" name="vue2" :url="vue2Url" :sync="true" :alive="true"></WujieVue   

具体实践详细介绍参考:

http://3.cn/1GyPHN-i/shendeng

https://wujie-micro.github.io/doc/guide/start.html

无界源码解析

1 源码包目录结构

packages 包里包含无界框架核心代码wujie-core和对应不同技术栈应用包

examples 使用案例,main-xxx对应该技术栈主应用的使用案例,其他代表子应用的使用案例

微前端无界机制浅析

2 wujie-vue2组件

该组件默认配置了相关参数,简化了无界使用时的一些配置项,作为一个全局组件被主引用使用

这里使用wujie-vue2示例,其他wujie-react,wujie-vue3大家可自行阅读,基本作用和wujie-vue2相同都是用来简化无界配置,方便快速使用

import Vue from "vue"; import { bus, preloadApp, startApp as rawStartApp, destroyApp, setupApp } from "wujie";  const wujieVueOptions = {   name: "WujieVue",   props: {        /*传入配置参数*/   },   data() {     return {       startAppQueue: Promise.resolve(),     };   },   mounted() {     bus.$onAll(this.handleEmit);     this.execStartApp();   },   methods: {     handleEmit(event, ...args) {       this.$emit(event, ...args);     },     async startApp() {       try {         // $props 是vue 2.2版本才有的属性,所以这里直接全部写一遍         await rawStartApp({           name: this.name,           url: this.url,           el: this.$refs.wujie,           loading: this.loading,           alive: this.alive,           fetch: this.fetch,           props: this.props,           attrs: this.attrs,           replace: this.replace,           sync: this.sync,           prefix: this.prefix,           fiber: this.fiber,           degrade: this.degrade,           plugins: this.plugins,           beforeLoad: this.beforeLoad,           beforeMount: this.beforeMount,           afterMount: this.afterMount,           beforeUnmount: this.beforeUnmount,           afterUnmount: this.afterUnmount,           activated: this.activated,           deactivated: this.deactivated,           loadError: this.loadError,         });       } catch (error) {         console.log(error);       }     },     execStartApp() {       this.startAppQueue = this.startAppQueue.then(this.startApp);     },     destroy() {       destroyApp(this.name);     },   },   beforeDestroy() {     bus.$offAll(this.handleEmit);   },   render(c) {     return c("div", {       style: {         width: this.width,         height: this.height,       },       ref: "wujie",     });   }, };  const WujieVue = Vue.extend(wujieVueOptions);  WujieVue.setupApp = setupApp; WujieVue.preloadApp = preloadApp; WujieVue.bus = bus; WujieVue.destroyApp = destroyApp; WujieVue.install = function (Vue) {   Vue.component("WujieVue", WujieVue); }; export default WujieVue;   

3 入口defineWujieWebComponent和StartApp

首先从入口文件index看起,defineWujieWebComponent

import { defineWujieWebComponent } from "./shadow"; // 定义webComponent容器 defineWujieWebComponent();  // 定义webComponent  存在shadow.ts 文件中 export function defineWujieWebComponent() {   class WujieApp extends HTMLElement {     connectedCallback(){       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() {       const sandbox = getWujieById(this.getAttribute(WUJIE_DATA_ID));       sandbox?.unmount();     }   }   customElements?.define("wujie-app", WujieApp); }   

startApp方法

startApp(options) {   const newSandbox = new WuJie({ name, url, attrs, degradeAttrs, fiber, degrade, plugins, lifecycles });   const { template, getExternalScripts, getExternalStyleSheets } = await importHTML({     url,     html,     opts: {       fetch: fetch || window.fetch,       plugins: newSandbox.plugins,       loadError: newSandbox.lifecycles.loadError,       fiber,     },   });   const processedHtml = await processCssLoader(newSandbox, template, getExternalStyleSheets);   await newSandbox.active({ url, sync, prefix, template: processedHtml, el, props, alive, fetch, replace });   await newSandbox.start(getExternalScripts);   return newSandbox.destroy;     

微前端无界机制浅析

4 实例化

4-1, wujie (sandbox.ts)

// wujie class wujie {   constructor(options) {     /** iframeGenerator在 iframe.ts中**/     this.iframe = iframeGenerator(this, attrs, mainHostPath, appHostPath, appRoutePath);      if (this.degrade) { // 降级模式       const { proxyDocument, proxyLocation } = localGenerator(this.iframe, urlElement, mainHostPath, appHostPath);       this.proxyDocument = proxyDocument;       this.proxyLocation = proxyLocation;     } else {           // 非降级模式       const { proxyWindow, proxyDocument, proxyLocation } = proxyGenerator();       this.proxy = proxyWindow;       this.proxyDocument = proxyDocument;       this.proxyLocation = proxyLocation;     }     this.provide.location = this.proxyLocation;     addSandboxCacheWithWujie(this.id, this);   } }   

4-2.非降级Proxygenerator

非降级模式window、document、location代理

window代理拦截,修改this指向

export function proxyGenerator(   iframe: HTMLIFrameElement,   urlElement: HTMLAnchorElement,   mainHostPath: string,   appHostPath: string ): {   proxyWindow: Window;   proxyDocument: Object;   proxyLocation: Object; } {   const proxyWindow = new Proxy(iframe.contentWindow, {     get: (target: Window, p: PropertyKey): any => {       // location进行劫持       /*xxx*/       // 修正this指针指向       return getTargetValue(target, p);     },     set: (target: Window, p: PropertyKey, value: any) => {       checkProxyFunction(value);       target

= value; return true; }, /**其他方法属性**/ }); // proxy document const proxyDocument = new Proxy( {}, { get: function (_fakeDocument, propKey) { const document = window.document; const { shadowRoot, proxyLocation } = iframe.contentWindow.__WUJIE; const rawCreateElement = iframe.contentWindow.__WUJIE_RAW_DOCUMENT_CREATE_ELEMENT__; const rawCreateTextNode = iframe.contentWindow.__WUJIE_RAW_DOCUMENT_CREATE_TEXT_NODE__; // need fix /* 包括元素创建,元素选择操作等 createElement,createTextNode, documentURI,URL,querySelector,querySelectorAll documentElement,scrollingElement ,forms,images,links等等 */ // from shadowRoot if (propKey === "getElementById") { return new Proxy(shadowRoot.querySelector, { // case document.querySelector.call apply(target, ctx, args) { if (ctx !== iframe.contentDocument) { return ctx[propKey]?.apply(ctx, args); } return target.call(shadowRoot, `[id="${args[0]}"]`); }, }); } }, } ); // proxy location const proxyLocation = new Proxy( {}, { get: function (_fakeLocation, propKey) { const location = iframe.contentWindow.location; if ( propKey === "host" || propKey === "hostname" || propKey === "protocol" || propKey === "port" || propKey === "origin" ) { return urlElement[propKey]; } /** 拦截相关propKey, 返回对应lication内容 propKey =="href","reload","replace" **/ return getTargetValue(location, propKey); }, set: function (_fakeLocation, propKey, value) { // 如果是跳转链接的话重开一个iframe if (propKey === "href") { return locationHrefSet(iframe, value, appHostPath); } iframe.contentWindow.location[propKey] = value; return true; } } ); return { proxyWindow, proxyDocument, proxyLocation }; }

4-3,降级模式localGenerator

export function localGenerator( ){   // 代理 document   Object.defineProperties(proxyDocument, {     createElement: {       get: () => {         return function (...args) {           const element = rawCreateElement.apply(iframe.contentDocument, args);           patchElementEffect(element, iframe.contentWindow);           return element;         };       },     },   });   // 普通处理   const {     modifyLocalProperties,     modifyProperties,     ownerProperties,     shadowProperties,     shadowMethods,     documentProperties,     documentMethods,   } = documentProxyProperties;   modifyProperties     .filter((key) => !modifyLocalProperties.includes(key))     .concat(ownerProperties, shadowProperties, shadowMethods, documentProperties, documentMethods)     .forEach((key) => {       Object.defineProperty(proxyDocument, key, {         get: () => {           const value = sandbox.document?.[key];           return isCallable(value) ? value.bind(sandbox.document) : value;         },       });     });    // 代理 location   const proxyLocation = {};   const location = iframe.contentWindow.location;   const locationKeys = Object.keys(location);   const constantKey = ["host", "hostname", "port", "protocol", "port"];   constantKey.forEach((key) => {     proxyLocation[key] = urlElement[key];   });   Object.defineProperties(proxyLocation, {     href: {       get: () => location.href.replace(mainHostPath, appHostPath),       set: (value) => {         locationHrefSet(iframe, value, appHostPath);       },     },     reload: {       get() {         warn(WUJIE_TIPS_RELOAD_DISABLED);         return () => null;       },     },   });   return { proxyDocument, proxyLocation }; }   

实例化化主要是建立起js运行时的沙箱iframe, 通过非降级模式下proxy和降级模式下对document,location,window等全局操作属性的拦截修改将其和对应的js沙箱操作关联起来

微前端无界机制浅析

5 importHTML入口文件解析

importHtml方法(entry.ts)

export default function importHTML(params: {   url: string;   html?: string;   opts: ImportEntryOpts; }): Promise<htmlParseResult> {   /*xxxx*/   const getHtmlParseResult = (url, html, htmlLoader) =>     (html       ? Promise.resolve(html)       : fetch(url).then( /** 使用fetch Api 加载子应用入口**/           (response) => response.text(),           (e) => {             embedHTMLCache
= null; loadError?.(url, e); return Promise.reject(e); } ) ).then((html) => { const assetPublicPath = getPublicPath(url); const { template, scripts, styles } = processTpl(htmlLoader(html), assetPublicPath); return { template: template, assetPublicPath, getExternalScripts: () => getExternalScripts( scripts .filter((script) => !script.src || !isMatchUrl(script.src, jsExcludes)) .map((script) => ({ ...script, ignore: script.src && isMatchUrl(script.src, jsIgnores) })), fetch, loadError, fiber ), getExternalStyleSheets: () => getExternalStyleSheets( styles .filter((style) => !style.src || !isMatchUrl(style.src, cssExcludes)) .map((style) => ({ ...style, ignore: style.src && isMatchUrl(style.src, cssIgnores) })), fetch, loadError ), }; }); if (opts?.plugins.some((plugin) => plugin.htmlLoader)) { return getHtmlParseResult(url, html, htmlLoader); // 没有html-loader可以做缓存 } else { return embedHTMLCache
|| (embedHTMLCache
= getHtmlParseResult(url, html, htmlLoader)); } }

importHTML结构如图:

微前端无界机制浅析

注意点: 通过Fetch url加载子应用资源,这里也是需要子应用支持跨域设置的原因

6 CssLoader和样式加载优化

export async function processCssLoader(   sandbox: Wujie,   template: string,   getExternalStyleSheets: () => StyleResultList ): Promise<string> {   const curUrl = getCurUrl(sandbox.proxyLocation);   /** css-loader */   const composeCssLoader = compose(sandbox.plugins.map((plugin) => plugin.cssLoader));   const processedCssList: StyleResultList = getExternalStyleSheets().map(({ src, ignore, contentPromise }) => ({     src,     ignore,     contentPromise: contentPromise.then((content) => composeCssLoader(content, src, curUrl)),   }));   const embedHTML = await getEmbedHTML(template, processedCssList);   return sandbox.replace ? sandbox.replace(embedHTML) : embedHTML; }   

微前端无界机制浅析

7 子应用active

active方法主要用于做 子应用激活, 同步路由,动态修改iframe的fetch, 准备shadow, 准备子应用注入

7-1, active方法(sandbox.ts)

public async active(options){     /**  options的检查  **/     // 处理子应用自定义fetch     // TODO fetch检验合法性     const iframeWindow = this.iframe.contentWindow;     iframeWindow.fetch = iframeFetch;     this.fetch = iframeFetch;          // 处理子应用路由同步     if (this.execFlag && this.alive) {       // 当保活模式下子应用重新激活时,只需要将子应用路径同步回主应用       syncUrlToWindow(iframeWindow);     } else {       // 先将url同步回iframe,然后再同步回浏览器url       syncUrlToIframe(iframeWindow);       syncUrlToWindow(iframeWindow);     }      // inject template     this.template = template ?? this.template;      /* 降级处理 */     if (this.degrade) {       return;     }      if (this.shadowRoot) {       this.el = renderElementToContainer(this.shadowRoot.host, el);       if (this.alive) return;     } else {       // 预执行无容器,暂时插入iframe内部触发Web Component的connect       // rawDocumentQuerySelector.call(iframeWindow.document, "body") 相当于Document.prototype.querySelector('body')       const iframeBody = rawDocumentQuerySelector.call(iframeWindow.document, "body") as HTMLElement;                this.el = renderElementToContainer(createWujieWebComponent(this.id), el ?? iframeBody);     }      await renderTemplateToShadowRoot(this.shadowRoot, iframeWindow, this.template);     this.patchCssRules();      // inject shadowRoot to app     this.provide.shadowRoot = this.shadowRoot;   }   

7-2,createWujieWebComponent, renderElementToContainer, renderTemplateToShadowRoot

// createWujieWebComponent export function createWujieWebComponent(id: string): HTMLElement {   const contentElement = window.document.createElement("wujie-app");   contentElement.setAttribute(WUJIE_DATA_ID, id);   contentElement.classList.add(WUJIE_IFRAME_CLASS);   return contentElement; }  /**  * 将准备好的内容插入容器  */ export function renderElementToContainer(   element: Element | ChildNode,   selectorOrElement: string | HTMLElement ): HTMLElement {   const container = getContainer(selectorOrElement);   if (container && !container.contains(element)) {     // 有 loading 无需清理,已经清理过了     if (!container.querySelector(`div[${LOADING_DATA_FLAG}]`)) {       // 清除内容       clearChild(container);     }     // 插入元素     if (element) {       //  rawElementAppendChild = HTMLElement.prototype.appendChild;       rawElementAppendChild.call(container, element);     }   }   return container; } /**  * 将template渲染到shadowRoot  */ export async function renderTemplateToShadowRoot(   shadowRoot: ShadowRoot,   iframeWindow: Window,   template: string ): Promise<void> {   const html = renderTemplateToHtml(iframeWindow, template);   // 处理 css-before-loader 和 css-after-loader   const processedHtml = await processCssLoaderForTemplate(iframeWindow.__WUJIE, html);   // change ownerDocument   shadowRoot.appendChild(processedHtml);   const shade = document.createElement("div");   shade.setAttribute("style", WUJIE_SHADE_STYLE);   processedHtml.insertBefore(shade, processedHtml.firstChild);   shadowRoot.head = shadowRoot.querySelector("head");   shadowRoot.body = shadowRoot.querySelector("body");    // 修复 html parentNode   Object.defineProperty(shadowRoot.firstChild, "parentNode", {     enumerable: true,     configurable: true,     get: () => iframeWindow.document,   });    patchRenderEffect(shadowRoot, iframeWindow.__WUJIE.id, false); } /**  * 将template渲染成html元素  */ function renderTemplateToHtml(iframeWindow: Window, template: string): HTMLHtmlElement {   const sandbox = iframeWindow.__WUJIE;   const { head, body, alive, execFlag } = sandbox;   const document = iframeWindow.document;   let html = document.createElement("html");   html.innerHTML = template;   // 组件多次渲染,head和body必须一直使用同一个来应对被缓存的场景   if (!alive && execFlag) {     html = replaceHeadAndBody(html, head, body);   } else {     sandbox.head = html.querySelector("head");     sandbox.body = html.querySelector("body");   }   const ElementIterator = document.createTreeWalker(html, NodeFilter.SHOW_ELEMENT, null, false);   let nextElement = ElementIterator.currentNode as HTMLElement;   while (nextElement) {     patchElementEffect(nextElement, iframeWindow);     const relativeAttr = relativeElementTagAttrMap[nextElement.tagName];     const url = nextElement[relativeAttr];     if (relativeAttr) nextElement.setAttribute(relativeAttr, getAbsolutePath(url, nextElement.baseURI || ""));     nextElement = ElementIterator.nextNode() as HTMLElement;   }   if (!html.querySelector("head")) {     const head = document.createElement("head");     html.appendChild(head);   }   if (!html.querySelector("body")) {     const body = document.createElement("body");     html.appendChild(body);   }   return html; } /* // 保存原型方法 // 子应用的Document.prototype已经被改写了 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 = 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__   : Document.prototype.querySelector; */    

微前端无界机制浅析

8 子应用启动执行start

start 开始执行子应用,运行js,执行无界js插件列表

public async start(getExternalScripts: () => ScriptResultList): Promise<void> {     this.execFlag = true;     // 执行脚本     const scriptResultList = await getExternalScripts();     const iframeWindow = this.iframe.contentWindow;     // 标志位,执行代码前设置     iframeWindow.__POWERED_BY_WUJIE__ = true;     // 用户自定义代码前      const beforeScriptResultList: ScriptObjectLoader[] = getPresetLoaders("jsBeforeLoaders", this.plugins);     // 用户自定义代码后     const afterScriptResultList: ScriptObjectLoader[] = getPresetLoaders("jsAfterLoaders", this.plugins);     // 同步代码     const syncScriptResultList: ScriptResultList = [];     // async代码无需保证顺序,所以不用放入执行队列     const asyncScriptResultList: ScriptResultList = [];     // defer代码需要保证顺序并且DOMContentLoaded前完成,这里统一放置同步脚本后执行     const deferScriptResultList: ScriptResultList = [];     scriptResultList.forEach((scriptResult) => {       if (scriptResult.defer) deferScriptResultList.push(scriptResult);       else if (scriptResult.async) asyncScriptResultList.push(scriptResult);       else syncScriptResultList.push(scriptResult);     });      // 插入代码前     beforeScriptResultList.forEach((beforeScriptResult) => {       this.execQueue.push(() =>         this.fiber           ? requestIdleCallback(() => insertScriptToIframe(beforeScriptResult, iframeWindow))           : insertScriptToIframe(beforeScriptResult, iframeWindow)       );     });     // 同步代码     syncScriptResultList.concat(deferScriptResultList).forEach((scriptResult) => {       /**xxxxx**/     });      // 异步代码     asyncScriptResultList.forEach((scriptResult) => {       scriptResult.contentPromise.then((content) => {         this.fiber           ? requestIdleCallback(() => insertScriptToIframe({ ...scriptResult, content }, iframeWindow))           : insertScriptToIframe({ ...scriptResult, content }, iframeWindow);       });     });      //框架主动调用mount方法     this.execQueue.push(this.fiber ? () => requestIdleCallback(() => this.mount()) : () => this.mount());      //触发 DOMContentLoaded 事件     const domContentLoadedTrigger = () => {       eventTrigger(iframeWindow.document, "DOMContentLoaded");       eventTrigger(iframeWindow, "DOMContentLoaded");       this.execQueue.shift()?.();     };     this.execQueue.push(this.fiber ? () => requestIdleCallback(domContentLoadedTrigger) : domContentLoadedTrigger);      // 插入代码后     afterScriptResultList.forEach((afterScriptResult) => {       /**xxxxx**/     });      //触发 loaded 事件     const domLoadedTrigger = () => {       eventTrigger(iframeWindow.document, "readystatechange");       eventTrigger(iframeWindow, "load");       this.execQueue.shift()?.();     };     this.execQueue.push(this.fiber ? () => requestIdleCallback(domLoadedTrigger) : domLoadedTrigger);     // 由于没有办法准确定位是哪个代码做了mount,保活、重建模式提前关闭loading     if (this.alive || !isFunction(this.iframe.contentWindow.__WUJIE_UNMOUNT)) removeLoading(this.el);     this.execQueue.shift()();      // 所有的execQueue队列执行完毕,start才算结束,保证串行的执行子应用     return new Promise((resolve) => {       this.execQueue.push(() => {         resolve();         this.execQueue.shift()?.();       });     });   }   
// getExternalScripts export function getExternalScripts(   scripts: ScriptObject[],   fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response> = defaultFetch,   loadError: loadErrorHandler,   fiber: boolean ): ScriptResultList {   // module should be requested in iframe   return scripts.map((script) => {     const { src, async, defer, module, ignore } = script;     let contentPromise = null;     // async     if ((async || defer) && src && !module) {       contentPromise = new Promise((resolve, reject) =>         fiber           ? requestIdleCallback(() => fetchAssets(src, scriptCache, fetch, false, loadError).then(resolve, reject))           : fetchAssets(src, scriptCache, fetch, false, loadError).then(resolve, reject)       );       // module || ignore     } else if ((module && src) || ignore) {       contentPromise = Promise.resolve("");       // inline     } else if (!src) {       contentPromise = Promise.resolve(script.content);       // outline     } else {       contentPromise = fetchAssets(src, scriptCache, fetch, false, loadError);     }     return { ...script, contentPromise };   }); }  // 加载assets资源  // 如果存在缓存则从缓存中获取 const fetchAssets = (   src: string,   cache: Object,   fetch: (input: RequestInfo, init?: RequestInit) => Promise<Response>,   cssFlag?: boolean,   loadError?: loadErrorHandler ) =>   cache[src] ||   (cache[src] = fetch(src)     .then((response) => {      /**status > 400按error处理**/       return response.text();     })  }));  // insertScriptToIframe export function insertScriptToIframe(   scriptResult: ScriptObject | ScriptObjectLoader,   iframeWindow: Window,   rawElement?: HTMLScriptElement ) {   const { src, module, content, crossorigin, crossoriginType, async, callback, onload } =     scriptResult as ScriptObjectLoader;   const scriptElement = iframeWindow.document.createElement("script");   const nextScriptElement = iframeWindow.document.createElement("script");   const { replace, plugins, proxyLocation } = iframeWindow.__WUJIE;   const jsLoader = getJsLoader({ plugins, replace });   let code = jsLoader(content, src, getCurUrl(proxyLocation));    // 内联脚本处理   if (content) {     // patch location     if (!iframeWindow.__WUJIE.degrade && !module) {       code = `(function(window, self, global, location) {       ${code} }).bind(window.__WUJIE.proxy)(   window.__WUJIE.proxy,   window.__WUJIE.proxy,   window.__WUJIE.proxy,   window.__WUJIE.proxyLocation, );`;     }   } else {     // 外联自动触发onload     onload && (scriptElement.onload = onload as (this: GlobalEventHandlers, ev: Event) => any);     src && scriptElement.setAttribute("src", src);     crossorigin && scriptElement.setAttribute("crossorigin", crossoriginType);   }   // esm 模块加载   module && scriptElement.setAttribute("type", "module");   scriptElement.textContent = code || "";   // 执行script队列检测   nextScriptElement.textContent =     "if(window.__WUJIE.execQueue && window.__WUJIE.execQueue.length){ window.__WUJIE.execQueue.shift()()}";    const container = rawDocumentQuerySelector.call(iframeWindow.document, "head");   if (/^<!DOCTYPE html/i.test(code)) {     error(WUJIE_TIPS_SCRIPT_ERROR_REQUESTED, scriptResult);     return !async && container.appendChild(nextScriptElement);   }   container.appendChild(scriptElement);    // 调用回调   callback?.(iframeWindow);   // 执行 hooks   execHooks(plugins, "appendOrInsertElementHook", scriptElement, iframeWindow, rawElement);   // 外联转内联调用手动触发onload   content && onload?.();   // async脚本不在执行队列,无需next操作   !async && container.appendChild(nextScriptElement); }    

微前端无界机制浅析

9 子应用销毁

/** 销毁子应用 */   public destroy() {     this.bus.$clear();     // thi.xxx = null;     // 清除 dom     if (this.el) {       clearChild(this.el);       this.el = null;     }     // 清除 iframe 沙箱     if (this.iframe) {       this.iframe.parentNode?.removeChild(this.iframe);     }     // 删除缓存     deleteWujieById(this.id);   }   

微前端无界机制浅析

主应用,无界,子应用之间的关系

主应用创建自定义元素和创建iframe元素

无界将子应用解析后的html,css加入到自定义元素,进行元素和样式隔离

同时建立iframe代理,将iframe和自定义元素shadowDom进行关联,

将子应用中的js放入iframe执行,iframe中js执行的结果被代理到修改shadowDom结构和数据

微前端无界机制浅析

作者:京东物流 张燕燕、刘海鼎

来源:京东云开发者社区 自猿其说Tech 转载请注明来源