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 这个插件实现的数据信息收集

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

实现一个 wujie 插件

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

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

  1. 在无界子应用的 jsBeforeLoaders 钩子中将pageSpy插件注入
  2. 自定义 harboronDownload 方法,即:使用主应用的统一下载回调
  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,
});
相关推荐
kyriewen12 分钟前
Claude自己跑出去hack了3家公司——我为什么还在用它写代码
前端·ai编程·claude
IT_陈寒19 分钟前
SpringBoot自动配置的坑,这次真踩疼我了
前端·人工智能·后端
子兮曰42 分钟前
AI 浏览器 Agent 的安全困局:当自动化工具可以偷走你的登录态
前端·人工智能·后端
程序员黑豆1 小时前
鸿蒙应用开发之@Styles 装饰器:定义可复用的组件样式
前端·harmonyos
程序员码歌1 小时前
我全程用 AI开发了一款微信小游戏,上线了
android·前端·游戏开发
咩咩啃树皮1 小时前
第52篇:前端工程化完整体系精讲——Vite构建、模块化、打包原理、规范体系、面试终极通关
前端·面试·职场和发展
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十八):菜单规则管理实现
前端·后端·ai编程
FogLetter1 小时前
当JavaScript学会“分身术”:异步编程的进化简史
前端·javascript·面试
hunterandroid1 小时前
[鸿蒙从零到一] HarmonyOS 分布式能力与设备协同实战:从发现设备到任务闭环
前端
lichenyang4531 小时前
从图片创作到本地可复现的 AIGC 全栈闭环:AIGC Creative Studio 实践复盘
前端·后端