PageSpy 对无界子应用内容录制的实现,即 iframe实现思路

前言

本文只记录了 PageSpy 离线日志 的实现方法,在线调试未做记录! PageSpy官网

无法录制的原因

pageSpy是使用的 rrweb 进行的页面 dom 信息录制,rrweb 需要开启 recordIframe

解决页面录制问题

开启 rrweb 的 recordIframe 属性即可

js 复制代码
window.$rrweb = new window.RRWebPlugin({
    recordIframe: true, // 启用 iframe 录制
    checkoutEveryNms: 2 * 1000, // 每2秒创建检查点,确保 iframe 状态被记录
});
window.PageSpy.registerPlugin(window.$rrweb);

页面信息有了,但是数据没有被记录

pageSpy是使用的 data-harbor 这个插件实现的数据信息收集

所以我们需要在无界子应用内部将pageSpy和data-harbor 重新初始化一遍

实现一个 wujie 插件

这里子应用只做了一层嵌套,即: 主应用 > 子应用;

不考虑多层嵌套的情况,即:主应用 > 子应用1 > 子应用2
iframe 的录制思路相同,在 iframe 内部页面加载之前进行脚本注入即可

  1. 在无界子应用的 jsBeforeLoaders 钩子中将pageSpy插件注入
  2. 自定义 harbor 的 onDownload 方法,即:使用主应用的统一下载回调
  3. 将子应用的 window 实例挂载到主应用中
js 复制代码
export function injectPageSpy() {
  // 这里根据 url 中是否携带 DEBUGGER 参数决定是否开启 pageSpy
  const isOpenDebugger = location.href.includes('DEBUGGER');
  if (!isOpenDebugger) return {};
  return {
    jsBeforeLoaders: [
      { src: 'https://pagespy.jikejishu.com/page-spy/index.min.js' },
      { src: 'https://pagespy.jikejishu.com/plugin/data-harbor/index.min.js' },
      {
        callback: (appWindow) => {
          appWindow.$harbor = new appWindow.DataHarborPlugin({
            onDownload: appWindow.parent.__PageSpy_Log_Download__,
          });
          appWindow.PageSpy.registerPlugin(appWindow.$harbor);

          appWindow.$pageSpy = new appWindow.PageSpy({
            title: 'Bug 调试',
            project: `PageSpy-Wujie-${appWindow.name}`,
            autoRender: false,
            serializeData: true,
            offline: true,
          });
          
          appWindow.parent.__WUJIE_CHILD_APP__.push(appWindow);
        },
      },
    ],
  };
}

主应用中 data-harbor 插件改造

js 复制代码
// 所有日志信息收集
const PAGE_SPY_USER_DATA_LOG = [];
// wujie 子应用 window 实例集合
window.__WUJIE_CHILD_APP__ = []; 
// 全局统一日志下载回调方法
window.__PageSpy_Log_Download__ = (data) => { 
    PAGE_SPY_USER_DATA_LOG.push(...data); 
}; 
window.$harbor = new window.DataHarborPlugin({ 
    onDownload: async (data) => {
        const list = window.__WUJIE_CHILD_APP__.map((instance) => instance.$harbor.download()); 
        try {
            await Promise.all(list);
            PAGE_SPY_USER_DATA_LOG.push(...data);
            // TODO: 下载 PAGE_SPY_USER_DATA_LOG 日志数据
            // Dowload PAGE_SPY_USER_DATA_LOG
        } catch (err) {
            console.log(err);
        } 
    }, 
});
window.PageSpy.registerPlugin(window.$harbor);

主应用完整代码实现

js 复制代码
      function useScript(src) {
        return new Promise((resolve, reject) => {
          const script = document.createElement('script');
          script.async = true;
          script.defer = true;
          script.src = src;
          script.onload = resolve;
          script.onerror = reject;
          document.head.appendChild(script);
        });
      }

      function downloadPageSpyData(data) {
        const jsonData = data.sort((a, b) => a.timestamp - b.timestamp);

        const blob = new Blob([JSON.stringify(jsonData)], { type: 'application/json', encoding: 'utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `debug-data-${Date.now()}.json`;
        a.click();
        URL.revokeObjectURL(url);
      }

      async function openDebugger() {
        try {
          await useScript('https://pagespy.jikejishu.com/page-spy/index.min.js');
          await useScript('https://pagespy.jikejishu.com/plugin/data-harbor/index.min.js');
          await useScript('https://pagespy.jikejishu.com/plugin/rrweb/index.min.js');

          const PAGE_SPY_USER_DATA_LOG = [];
          window.__WUJIE_CHILD_APP__ = [];
          window.__PageSpy_Log_Download__ = (data) => {
            PAGE_SPY_USER_DATA_LOG.push(...data);
          };

          window.$harbor = new window.DataHarborPlugin({
            onDownload: async (data) => {
              const list = window.__WUJIE_CHILD_APP__.map((instance) => instance.$harbor.download());
              try {
                await Promise.all(list);
                PAGE_SPY_USER_DATA_LOG.push(...data);
                downloadPageSpyData(PAGE_SPY_USER_DATA_LOG);
              } catch (err) {
                console.log(err);
              }
            },
          });
          window.PageSpy.registerPlugin(window.$harbor);

          window.$rrweb = new window.RRWebPlugin({
            recordIframe: true, // 启用 iframe 录制
            checkoutEveryNms: 2 * 1000, // 每3秒创建检查点,确保 iframe 状态被记录
          });
          window.PageSpy.registerPlugin(window.$rrweb);

          window.$pageSpy = new window.PageSpy({
            title: 'Bug 调试',
            project: 'YIYE',
            autoRender: true,
            serializeData: true,
            offline: true,
            modal: {
              logo: '',
              title: 'YIYE调试器',
            },
          });
        } catch (err) {
          console.error('PageSpy init failed:');
          console.error(err);
        }
      }

      const isOpenDebugger = location.href.includes('OPEN_DEBUGGER');
      if (isOpenDebugger) {
        const style = document.createElement('style');
        style.innerHTML = `#upload-periods,#page-spy-copy-link { display: none; }`;
        document.head.appendChild(style);
        openDebugger();
      }

无界插件注册

js 复制代码
import { startApp as rawStartApp } from 'wujie';

const localPlugins = [
    /* ...more Plugin */
    injectPageSpy()
]

await rawStartApp({
    /* ...more wujie config */
    plugins: localPlugins,
});
相关推荐
葡萄城技术团队6 小时前
1000 人协同压测的环境瓶颈排查:CPU 争用与 Wi\-Fi
前端
excel7 小时前
Marked 与 Markdown 有什么区别?该怎么选?
前端
Da Da 泓8 小时前
五子棋 Web 端测试用例报告
前端·网络·websocket·网络协议·测试用例·项目
zxcvb1539 小时前
远程查看本地文档:一条轻量 Web 文件服务的工程化链路
前端·远程查看文档
niucloud-admin9 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php
北岛贰9 小时前
啵啵音乐迎来全新升级!
前端
hkNaruto11 小时前
【AI】基于大模型的AI游戏地图生成、控制方案:AI Native Game Harness 迁移操作手册
前端·人工智能·游戏
IT_陈寒11 小时前
Java序列化踩坑记:对象循环引用让我加班两小时
前端·人工智能·后端
Acro11 小时前
快给macOS 上的Grok Bot换一套皮肤
前端
flash俊杰12 小时前
双引擎 OCR 编排:逐页判定、视觉优先、partial 语义与三层超时
前端