元素隔离

micro app中采用的是对doucment对象的一些方法重写,每次获取dom时从自身容器顶层dom节点来获取,以此达到元素的隔离

样式隔离

  1. 使用前缀
    • postcss
    • 加载子应用时在在选择器上手动添加一个关于子应用的前缀
    • css modules
  2. shadow dom

micro app 中对于样式的处理时对每个样式规则,添加自身容器对名字添加前缀比如:micro-app[name=app-root] .child{} 达到样式的隔离

Js的处理

micro app 中对于js采用的是沙箱机制,对于顶层对象采用的代理的形式
对于全局对象的修改会被代理到代理对象中,记录修改的key值, 在停止时会把记录的key在原始window、和代理对象的window中删除掉,然后清空所有的key值

  1. function executeCode(code) {
  2. const rawWindow = window;
  3. const fakeWindow = {count: 1000};
  4. let _code = `(function (window, self) {
  5. with(window) {
  6. ${code}
  7. }
  8. }).call(fakeWindow, fakeWindow)`;
  9. eval(_code);
  10. }
  11. executeCode(`
  12. console.log(window)
  13. `);
  14. // log -> {count: 1000}

数据通信

micro app 中采用的是发布订阅的形式
源代码:

import { CallableFunctionForInteract } from '@micro-app/types'
import { logError, isFunction, isPlainObject } from '../libs/utils'

export default class EventCenter {
  eventList = new Map<string, {
    data: Record<PropertyKey, unknown>,
    callbacks: Set<CallableFunctionForInteract>,
  }>()

  // whether the name is legal
  isLegalName (name: string): boolean {
    if (!name) {
      logError('event-center: Invalid name')
      return false
    }

    return true
  }

  /**
   * add listener
   * @param name event name
   * @param f listener
   * @param autoTrigger If there is cached data when first bind listener, whether it needs to trigger, default is false
   */
  on (name: string, f: CallableFunctionForInteract, autoTrigger = false): void {

    if (this.isLegalName(name)) {
      if (!isFunction(f)) {
        return logError('event-center: Invalid callback function')
      }

      let eventInfo = this.eventList.get(name)

      if (!eventInfo) {
        eventInfo = {
          data: {},
          callbacks: new Set(),
        }
        this.eventList.set(name, eventInfo)
      } else if (autoTrigger && Object.getOwnPropertyNames(eventInfo.data).length) {
        // auto trigger when data not null
        f(eventInfo.data)
      }

      eventInfo.callbacks.add(f)
    }
  }

  // remove listener, but the data is not cleared
  off (name: string, f?: CallableFunctionForInteract): void {
    if (this.isLegalName(name)) {
      const eventInfo = this.eventList.get(name)
      if (eventInfo) {
        if (isFunction(f)) {
          eventInfo.callbacks.delete(f!)
        } else {
          eventInfo.callbacks.clear()
        }
      }
    }
  }

  // eventInfo中保存的事最近一次发布的data
  // 所以才获取时获取到的就是最新的
  // dispatch data
  dispatch (name: string, data: Record<PropertyKey, unknown>): void {
    if (this.isLegalName(name)) {
      if (!isPlainObject(data)) {
        return logError('event-center: data must be object')
      }

      let eventInfo = this.eventList.get(name)
      if (eventInfo) {
        // Update when the data is not equal
        if (eventInfo.data !== data) {
          eventInfo.data = data
          // 通知所有监听函数
          for (const f of eventInfo.callbacks) {
            f(data)
          }

        }
      } else {
        eventInfo = {
          data: data,
          callbacks: new Set(),
        }
        this.eventList.set(name, eventInfo)
      }
    }
  }

  // get data
  getData (name: string): Record<PropertyKey, unknown> | null {
    const eventInfo = this.eventList.get(name)
    return eventInfo?.data ?? null
  }
}

实例中使用的getData() 就是继承了baseAppEventCenter的实例,实例时传入appName, 在之上封装了一层,所以可以直接获取data ,而不用传入当前的appname


// 实例化sandBox时创建eventcenter
// 因为在with块中,所以访问microApp就可以拿到
private initMicroAppWindow (microAppWindow: microAppWindowType, appName: string, url: string): void {
     // ...code
    microAppWindow.microApp = Object.assign(new EventCenterForMicroApp(appName), {
      removeDomScope,
      pureCreateElement,
    })
    // ...code
}

// 应用中


function App() {


console.log(window. microApp);
  return (
    <div className="child" >
     <h1>App子应用</h1>
     <p>react 18 子应用</p>
     <p>设置路由</p>
    </div>
  );
}

EventCenterForMicroApp 
  appName: "app-root"
  pureCreateElement: ƒ pureCreateElement(tagName, options)
  removeDomScope: ƒ removeDomScope()
  [[Prototype]]: EventCenterForGlobal
  addDataListener: ƒ addDataListener(cb, autoTrigger)
  clearDataListener: ƒ clearDataListener()
  constructor: class EventCenterForMicroApp
  dispatch: ƒ dispatch(data)
  // getData 就是上面的eventCenter
  getData: ƒ getData()
  removeDataListener: ƒ removeDataListener(cb)

预加载

基于requestIdleCallback(命名冲突~~~)

export const requestIdleCallback = globalThis.requestIdleCallback ||
  function (fn: CallableFunction) {
    const lastTime = Date.now()
    return setTimeout(function () {
      fn({
        didTimeout: false,
        timeRemaining () {
          return Math.max(0, 50 - (Date.now() - lastTime))
        },
      })
    }, 50)
  }



function preFetchInSerial (prefetchApp: prefetchParam): Promise<void> {
  return new Promise((resolve) => {
    requestIdleCallback(() => {
      if (isPlainObject(prefetchApp) && navigator.onLine) {

        prefetchApp.name = formatAppName(prefetchApp.name)
        prefetchApp.url = formatAppURL(prefetchApp.url, prefetchApp.name)

        if (
            prefetchApp.name && 
            prefetchApp.url &&
            !appInstanceMap.has(prefetchApp.name)
         ) {
          // 实例化组件类
          const app = new CreateApp({
            name: prefetchApp.name,
            url: prefetchApp.url,
            scopecss: !(prefetchApp.disableScopecss ?? microApp.disableScopecss),
            useSandbox: !(prefetchApp.disableSandbox ?? microApp.disableSandbox),
          })

          app.isPrefetch = true
          app.prefetchResolve = resolve
          // 保存整个应用中所有的子应用实例
           // 设置 实例
          appInstanceMap.set(prefetchApp.name, app)
        } else {
          resolve()
        }
      } else {
        resolve()
      }
    })
  })
}

// 实例化以后就是加载应用
// 请求html,links,script,一切加载完毕以后
// 调用app的onLoad方法, 在里面执行prefetchResolve
// 预加载完成