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,
});
相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic7 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy7 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜10 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
江华森11 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http
IT_陈寒12 小时前
Redis的KEYS命令把我搞崩溃了,改用SCAN才活过来
前端·人工智能·后端