<!doctype html>
<html>
  <head>
    <style>
      html.dark {
        color-scheme: dark;
      }

      body {
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
      }
    </style>
    <!-- PREVIEW-OPTIONS-HEAD-HTML -->
    <script>
      ;(() => {
        let scriptEls = []

        window.process = { env: {} }
        window.__modules__ = {}

        window.__export__ = (mod, key, get) => {
          Object.defineProperty(mod, key, {
            enumerable: true,
            configurable: true,
            get,
          })
        }

        window.__dynamic_import__ = (key) => {
          return Promise.resolve(window.__modules__[key])
        }

        async function handle_message(ev) {
          let { action, cmd_id } = ev.data
          // 通知父级
          const send_message = (payload) =>
            parent.postMessage({ ...payload }, ev.origin)
          const send_reply = (payload) => send_message({ ...payload, cmd_id })
          // 通知父级 ok 表示编译完成
          const send_ok = () => send_reply({ action: 'cmd_ok' })
          const send_error = (message, stack) =>
            send_reply({ action: 'cmd_error', message, stack })
          // 编译eval
          if (action === 'eval') {
          
            try {
              if (scriptEls.length) {
                scriptEls.forEach((el) => {
                  document.head.removeChild(el)
                })
                scriptEls.length = 0
              }
              //拿到 js 加载执行
              let { script: scripts } = ev.data.args
              if (typeof scripts === 'string') scripts = [scripts]

              for (const script of scripts) {
                const scriptEl = document.createElement('script')
                scriptEl.setAttribute('type', 'module')
                // send ok in the module script to ensure sequential evaluation
                // of multiple proxy.eval() calls
                const done = new Promise((resolve) => {
                  window.__next__ = resolve
                })
                scriptEl.innerHTML = script + `\nwindow.__next__()`
                // 挂在 js
                document.head.appendChild(scriptEl)
                scriptEl.onerror = (err) => send_error(err.message, err.stack)
                scriptEls.push(scriptEl)
                await done
              }
              // 成功以后，通知事件
              send_ok()
            } catch (e) {
              send_error(e.message, e.stack)
            }
          }

          if (action === 'catch_clicks') {
            try {
              const top_origin = ev.origin
              document.body.addEventListener('click', (event) => {
                if (event.which !== 1) return
                if (event.metaKey || event.ctrlKey || event.shiftKey) return
                if (event.defaultPrevented) return

                // ensure target is a link
                let el = event.target
                while (el && el.nodeName !== 'A') el = el.parentNode
                if (!el || el.nodeName !== 'A') return

                if (
                  el.hasAttribute('download') ||
                  el.getAttribute('rel') === 'external' ||
                  el.target ||
                  el.href.startsWith('javascript:') ||
                  !el.href
                )
                  return

                event.preventDefault()
                // 处理a 标签等一些情况
                if (el.href.startsWith(top_origin)) {
                  const url = new URL(el.href)
                  if (url.hash[0] === '#') {
                    window.location.hash = url.hash
                    return
                  }
                }

                window.open(el.href, '_blank')
              })
              send_ok()
            } catch (e) {
              send_error(e.message, e.stack)
            }
          }
        }
        // 监听父级事件
        window.addEventListener('message', handle_message, false)
        // 监听错误通知父级
        window.onerror = function (msg, url, lineNo, columnNo, error) {
          // ignore errors from import map polyfill - these are necessary for
          // it to detect browser support
          if (msg.includes('module specifier “vue”')) {
            // firefox only error, ignore
            return false
          }
          if (msg.includes("Module specifier, 'vue")) {
            // Safari only
            return false
          }
          try {
            parent.postMessage({ action: 'error', value: error }, '*')
          } catch (e) {
            parent.postMessage({ action: 'error', value: msg }, '*')
          }
        }
        // promise 触发报错
        window.addEventListener('unhandledrejection', (event) => {
          if (
            event.reason.message &&
            event.reason.message.includes('Cross-origin')
          ) {
            event.preventDefault()
            return
          }
          try {
            parent.postMessage(
              { action: 'unhandledrejection', value: event.reason },
              '*',
            )
          } catch (e) {
            parent.postMessage(
              { action: 'unhandledrejection', value: event.reason.message },
              '*',
            )
          }
        })

        let previous = { level: null, args: null }
        // console 上报
        ;['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach(
          (level) => {
            const original = console[level]
            // 重写 console
            console[level] = (...args) => {
              const msg = args[0]
              if (typeof msg === 'string') {
                if (
                  msg.includes('You are running a development build of Vue') ||
                  msg.includes('You are running the esm-bundler build of Vue')
                ) {
                  return
                }
              }

              original(...args)

              const stringifiedArgs = stringify(args)
              if (
                previous.level === level &&
                previous.args &&
                previous.args === stringifiedArgs
              ) {
                parent.postMessage(
                  { action: 'console', level, duplicate: true },
                  '*',
                )
              } else {
                previous = { level, args: stringifiedArgs }

                try {
                  parent.postMessage({ action: 'console', level, args }, '*')
                } catch (err) {
                  parent.postMessage(
                    { action: 'console', level, args: args.map(toString) },
                    '*',
                  )
                }
              }
            }
          },
        )
        // 特殊 console 暂且不管
        ;[
          { method: 'group', action: 'console_group' },
          { method: 'groupEnd', action: 'console_group_end' },
          { method: 'groupCollapsed', action: 'console_group_collapsed' },
        ].forEach((group_action) => {
          const original = console[group_action.method]
          console[group_action.method] = (label) => {
            parent.postMessage({ action: group_action.action, label }, '*')

            original(label)
          }
        })

        const timers = new Map()

        const original_time = console.time
        const original_timelog = console.timeLog
        const original_timeend = console.timeEnd

        console.time = (label = 'default') => {
          original_time(label)
          timers.set(label, performance.now())
        }
        console.timeLog = (label = 'default') => {
          original_timelog(label)
          const now = performance.now()
          if (timers.has(label)) {
            parent.postMessage(
              {
                action: 'console',
                level: 'system-log',
                args: [`${label}: ${now - timers.get(label)}ms`],
              },
              '*',
            )
          } else {
            parent.postMessage(
              {
                action: 'console',
                level: 'system-warn',
                args: [`Timer '${label}' does not exist`],
              },
              '*',
            )
          }
        }
        console.timeEnd = (label = 'default') => {
          original_timeend(label)
          const now = performance.now()
          if (timers.has(label)) {
            parent.postMessage(
              {
                action: 'console',
                level: 'system-log',
                args: [`${label}: ${now - timers.get(label)}ms`],
              },
              '*',
            )
          } else {
            parent.postMessage(
              {
                action: 'console',
                level: 'system-warn',
                args: [`Timer '${label}' does not exist`],
              },
              '*',
            )
          }
          timers.delete(label)
        }

        const original_assert = console.assert
        console.assert = (condition, ...args) => {
          if (condition) {
            const stack = new Error().stack
            parent.postMessage(
              { action: 'console', level: 'assert', args, stack },
              '*',
            )
          }
          original_assert(condition, ...args)
        }

        const counter = new Map()
        const original_count = console.count
        const original_countreset = console.countReset

        console.count = (label = 'default') => {
          counter.set(label, (counter.get(label) || 0) + 1)
          parent.postMessage(
            {
              action: 'console',
              level: 'system-log',
              args: `${label}: ${counter.get(label)}`,
            },
            '*',
          )
          original_count(label)
        }

        console.countReset = (label = 'default') => {
          if (counter.has(label)) {
            counter.set(label, 0)
          } else {
            parent.postMessage(
              {
                action: 'console',
                level: 'system-warn',
                args: `Count for '${label}' does not exist`,
              },
              '*',
            )
          }
          original_countreset(label)
        }

        const original_trace = console.trace

        console.trace = (...args) => {
          const stack = new Error().stack
          parent.postMessage(
            { action: 'console', level: 'trace', args, stack },
            '*',
          )
          original_trace(...args)
        }

        function toString(value) {
          if (value instanceof Error) {
            return value.message
          }
          for (const fn of [
            String,
            (v) => Object.prototype.toString.call(v),
            (v) => typeof v,
          ]) {
            try {
              return fn(value)
            } catch (err) {}
          }
        }

        function isComponentProxy(value) {
          return (
            value &&
            typeof value === 'object' &&
            value.__v_skip === true &&
            typeof value.$nextTick === 'function' &&
            value.$ &&
            value._
          )
        }

        function stringify(args) {
          try {
            return JSON.stringify(args, (key, value) => {
              return isComponentProxy(value) ? '{component proxy}' : value
            })
          } catch (error) {
            return null
          }
        }
      })()
    </script>
    <!-- 引入了  ES Module-->
    <!-- ES Module Shims: Import maps polyfill for modules browsers without import maps support (all except Chrome 89+) -->
    <!-- <script
      async
      src="https://cdn.jsdelivr.net/npm/es-module-shims@1.5.18/dist/es-module-shims.wasm.js"
    ></script> -->
    <script type="importmap">
      <!--IMPORT_MAP-->
    </script>
    <script type="module">
      import { ref } from 'vue'
      console.log(ref)
    </script>
  </head>

  <body>
    <!--PREVIEW-OPTIONS-PLACEHOLDER-HTML-->
  </body>
</html>
