Skip to content
霞露小伙 — HfWang
On this page

qiankun 沙箱简单实现

快照沙箱

js
class SnapshotSandbox {
  windowSnapshot = {}
  modifiedMap = {}
  proxy = window

  constructor() {}

  active() {
    // 记录 window 旧的 key-value
    Object.entries(window).forEach(([key, value]) => {
      this.windowSnapshot[key] = value
    })

    // 恢复上一次的 key-value
    Object.keys(this.modifiedMap).forEach((key) => {
      window[key] = this.modifiedMap[key]
    })
  }

  inactive() {
    this.modifiedMap = {}

    Object.keys(window).forEach((key) => {
      // 如果有改动,则说明要恢复回来
      if (window[key] !== this.windowSnapshot[key]) {
        // 记录变更
        this.modifiedMap[key] = window[key]
        window[key] = this.windowSnapshot[key]
      }
    })
  }
}

module.exports = SnapshotSandbox

单例沙箱

js
class SingularProxySandbox {
  /** 沙箱期间新增的全局变量 */
  addedMap = new Map()
  /** 沙箱期间更新的全局变量 */
  originMap = new Map()
  /** 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻做 snapshot */
  updatedMap = new Map()

  constructor() {
    const fakeWindow = Object.create(null)
    const { addedMap, originMap, updatedMap } = this

    this.proxy = new Proxy(fakeWindow, {
      set(_, key, value) {
        // 记录以前的值
        const originValue = window[key]

        if (!window.hasOwnProperty(key)) {
          // 如果不存在,那么加入 addedMap(添加)
          addedMap.set(key, value)
        } else if (!originMap.has(key)) {
          // 如果当前 window 对象存在该属性,且 originMap 中未记录过,
          // 则记录该属性初始值(修改)
          originMap.set(key, originValue)
        }

        // 记录修改后的值
        updatedMap.set(key, value)

        // 修改值
        window[key] = value
      },
      get(_, key) {
        return window[key]
      },
    })
  }

  setWindowKeyValues(key, value, shouldDelete) {
    if (value === undefined || shouldDelete) {
      // 删除 key-value
      delete window[key]
    } else {
      window[key] = value
    }
  }

  active() {
    // 激活时,把上次微应用做更新/新增的 key-value 覆盖到 window 上
    this.updatedMap.forEach((value, key) => {
      this.setWindowKeyValues(key, value)
    })
  }

  inactive() {
    // 删除新增的 key-value
    this.addedMap.forEach((_, key) => {
      this.setWindowKeyValues(key, undefined, true)
    })
    // 覆盖上次全局变量的 key-value
    this.originMap.forEach((value, key) => {
      this.setWindowKeyValues(key, value)
    })
  }
}

module.exports = SingularProxySandbox

多例沙箱

js
let activeSandboxCount = 0

class MultipleProxySandbox {
  proxy = {}

  constructor(props) {
    const { fakeWindow, keysWithGetters } = this.createFakeWindow()

    this.proxy = new Proxy(fakeWindow, {
      set(target, key, value) {
        // 对于一些非原生的属性不做修改,因这些属性可能是用户自己改的
        // 这里只需要判断 target[key] 即可, 因为只有原生属性才会复制到 target 上
        if (!target[key] && window[key]) {
          return
        }
        target[key] = value
      },
      get(target, key) {
        // 判断从 window 取值还是从当前 fakeWindow 取值
        const actualTarget = keysWithGetters[key] ? window : key in target ? target : window
        return actualTarget[key]
      },
    })
  }

  createFakeWindow() {
    const fakeWindow = {}
    const keysWithGetters = {}

    Object.getOwnPropertyNames(window)
      .filter((key) => {
        // 只要不可配置的的属性,这些不可配置属性也可以理解为原生属性(一般情况下)
        const descriptor = Object.getOwnPropertyDescriptor(window, key)
        return !descriptor?.configurable
      })
      .forEach((key) => {
        // 复制 key-value
        fakeWindow[key] = window[key]
        // 同时记录这些 key
        keysWithGetters[key] = true
      })

    return { fakeWindow, keysWithGetters }
  }

  active() {
    activeSandboxCount += 1
  }

  inactive() {
    activeSandboxCount -= 1
  }
}

module.exports = MultipleProxySandbox

本站中引用到的其他资料,如有侵权,请联系本人删除