写点什么

微前端无界机制浅析 | 京东物流技术团队

  • 2023-11-06
    北京
  • 本文字数:12949 字

    阅读完需:约 42 分钟

微前端无界机制浅析 | 京东物流技术团队

简介

随着项目的发展,前端 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)

// wujieclass 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[p] = 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[url] = 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[url] || (embedHTMLCache[url] = 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

// createWujieWebComponentexport 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()?.(); }); }); }
复制代码


// getExternalScriptsexport 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(); }) }));
// insertScriptToIframeexport 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 转载请注明来源

发布于: 刚刚阅读数: 2
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
微前端无界机制浅析 | 京东物流技术团队_前端_京东科技开发者_InfoQ写作社区