微前端-wujie源码解析-js隔离&样式隔离

859 阅读3分钟

基础介绍

作为微前端中基于iframe来做隔离的框架wenjie算是创立先河了,结合源码来看看内部实现;

1、wujie也是基于mono-repo + pnpm来做包管理;

2、核心包是wujie-core, 框架层面做了一些插件化的扩展来包装了wujie-core, 于是就有了wujie-react; wujie-vue2; wujie-vue3等包来支持不同框架接入;

基本使用

主应用

import { bus, setupApp, preloadApp, startApp, destroyApp } from "wujie";

// 设置子应用
setupApp({ name: "唯一id", url: "子应用地址", exec: true, el: "容器", sync: true })

// 启动子应用
startApp({ name: "唯一id" });

基本原理

微前端是可以实现多个子应用同时存在,且不相互影响,相互独立,那么就会涉及js运行的独立, css样式隔离的独立;来看看js和css隔离的实现

js沙箱

  • iframe有天然的隔离,但是也有他的一些问题存在,比如:
      1. 路由状态丢失,刷新一下,iframe的url状态就丢失了;
      1. dom割裂严重,弹窗只能在iframe内部展示,无法覆盖到全局;
    • ....

结合源码来看看wujie是怎么解决这些问题的, wujie基于proxy + iframe创建js sandbox

// 1. 生成iframeGenerator 创建了隐藏的iframe;
function iframeGenerator(...args) {
  const iframe = window.document.createElement('iframe');
  const attrsMerge = {
    style: "display: none",
    ...args.attrs,
    src,
    name,
  }
  setAttrsToElement(iframe, attrsMerge);
  window.document.body.appendChild(iframe);
  return iframe;
}

// 2. proxy 各个对象proxyWindow, proxyDocument, proxyLocation 增强了一些功能
function proxyGenerator(...args){
  const proxyWindow = new Proxy(iframe.contentWindow, {
    get:(tagrt, p) => {
      return getTargetValue(tagrt, p)
    }
  })

  const proxyDocument = new Proxy({}, {
    get:(_fakeDocument, propKey) => {
      const document = window.document;
    }
  })
  const proxyLocation = new Proxy({}, {
    get: function(location, key){
      const location = iframe.contentWindow.location;
      if(key === 'href') {
        return location[key].replace(mainHostPath, appHostPath);
      }
    }
  })
  return { proxyWindow, proxyDocument, proxyLocation }
}
  • wujie是怎么处理路由状态丢失的实现的;
// 重写了iframe的history的pushState, relaceState,实现了将主应用的路由和子应用同步;
function patchIframeHistory = (iframeWindow) => {
  const hostory = iframeWindow.history;
  history.pushState = function(){
        syncUrlToWindow(iframeWindow);
  },
  history.replaceState = function() {}
    syncUrlToWindow(iframeWindow);
}
  • iframe内部进行history.pushState,浏览器会自动的在joint session history中添加iframesession-history,浏览器的前进、后退在不做任何处理的情况就可以直接作用于子应用
  • wujie通过劫持iframehistory.pushStatehistory.replaceState,就可以将子应用的url同步到主应用的query参数上,当刷新浏览器初始化iframe时,读回子应用的url并使用iframehistory.replaceState进行同步

css沙箱

  • 基于web component的sandow dom来实现样式的隔离;
  • wujie是子应用的样式挂载到shadow dom上;
  • document的查询类接口:getElementsByTagName、getElementsByClassName、getElementsByName、getElementById、querySelector、querySelectorAll、head、body全部代理到webcomponent,这样instancewebcomponent就精准的链接起来。

// 定义web component的容器
function defineWujieWebComponent() {
  const custtomElements = window.customElements;
  class WujieApp extends HTMLElement {
    connectedCallback() {
      // attachShdow是可以将shadow DOM挂载到宿主this上;
      const shadowRoot = this.attachShadow({ mode: "open" });
      const sanbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
      patchElementEffect(shadowRoot, shandbox.iframe.contentWindow);
      sandbox.shadowRoot = shadowRoot;
    },

    disconnectedCallback() {
      const sandbox = getWujieById(this.getAttribute(WUJIE_APP_ID));
      sanbox?.unmount();
    }
  }
  customElements?.define("wujie-app", WujieApp)
}

附加知识点

web components

  1. web components web component

浏览器原生的组件规范,主要由custom elements, template, shadow DOM, HTML Import组成;

  • Custom Element 用户自定义网页元素,

    • custom elment内部有一个shadow root,用来接入外部DOM的根元素;
  • template组件内的html模版代码;

  • shadow DOM, 浏览器将模版,样式,属性,js代码等封装成一个独立的DOM元素,比如这类;

  • HTML import 可以用来加载外部网页;

参考

wujie doc