无界

71 阅读2分钟

无界的主要逻辑就是把 js 代码脚本放到 iframe 标签中来进行运行,因为 iframe 标签自带沙箱属性,不会影响其他的应用,把 html 和 css 放到 ShadowDOM 中来进行渲染,这样可以实现样式隔离,但是 js 脚本在 iframe 中执行,js 的 window 操作不了 ShadowDOM 的标签,所以通过代理和重写的方式来进行操作 ShadowDOM 的。

<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
 
<body>
  <div>基座的代码,不被子应用的样式锁污染</div>
 
  <!-- 加载子应用的div -->
  <div id="container"></div>
  <script>
    // 子应用的 html 和 css
    const strTemWithCSS = `
		<!DOCTYPE html>
			<html lang="en">
 
			<head>
				<meta charset="UTF-8">
				<meta name="viewport" content="width=device-width, initial-scale=1.0">
				<title>Document</title>
			</head>
 
			<body>
				<div id="inner">子应用1 hello</div>
				<style>
				div{ color: red;background: yellow;}	
				</style>
			</body>
 
			</html>
		`
 
    // 子应用的 js 脚本
    const strScript = `
		window.a = 100 ; // 子应用里面的属性,不会影响到父应用
		console.log(window.a,'子应用属性'); 
		const ele = document.querySelector('#inner');
		console.log(ele,'子应用元素');
		`
 
    // 创建 iframe 标签
    function createIframe() {
      const iframe = document.createElement('iframe');
      iframe.src = 'about:blank'
      document.body.appendChild(iframe)
      return iframe
    }
    // 创建沙箱,包括 iframe 和 shadowRoot
    function createSandbox() {
      const sandbox = {
        iframe: createIframe(),  // 创建一个 iframe 沙箱,用于运行 js
        shadowRoot: null
      }
      return sandbox
    }
 
    // 将 html、css 放入到 shadowDOM
    function injectTemplate(sandbox, strTemWithCSS) {
      const wrapper = document.createElement('div')
      wrapper.innerHTML = strTemWithCSS
      sandbox.shadowRoot.appendChild(wrapper)
    }
 
    function runScriptInSandbox(sandbox, strScript) {
      const iframeWindow = sandbox.iframe.contentWindow
      // 创建 script 标签,用于执行脚本
      const scriptElement = document.createElement('script')
 
      // 将 script 标签插入到 iframe 的 head 标签 中
      const headElement = iframeWindow.document.querySelector('head')
      // 希望在 iframe 中执行脚本内容 是操作 shadowDom 的
      // 因为脚本要获取或者操作dom,而且我们是通过 shadowDom 进行渲染的,此时的 iframe 的 window 是无法操作 shadowDom 的
      Object.defineProperty(iframeWindow.Document.prototype, 'querySelector', {
        get() {
          console.log('get');
          return new Proxy(sandbox.shadowRoot['querySelector'], {
            apply(target, thisArg, args) {
              return thisArg.querySelector.apply(sandbox.shadowRoot, args)
            }
          })
        }
      })
      //  将脚本内容放入 script 标签中
      scriptElement.textContent = strScript
      headElement.appendChild(scriptElement)
    }
    function createCustomElement() {
      class WujieApp extends HTMLElement {
        // 当自定义元素插入到 DOM 中时,会调用这个回调函数
        connectedCallback() {
          console.log("标签渲染完毕");
          // 1,创建沙箱
          const sandbox = createSandbox()
          // 2,创建 shadowDOM
          sandbox.shadowRoot = this.attachShadow({ mode: 'open' })
          // 3,将 html、css 放入到 shadowDOM
          injectTemplate(sandbox, strTemWithCSS)
          // 4,将 js 放入到沙箱中执行
          runScriptInSandbox(sandbox, strScript)
        }
      }
      window.customElements.define('wujie-app', WujieApp);
 
      container.appendChild(document.createElement('wujie-app'))
    }
    // 定义一个组件来使用
    createCustomElement()
 
  </script>
</body>
 
</html>