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,
});
相关推荐
xw54 分钟前
uni-app项目process is not defined
前端·uni-app
独立开阀者_FwtCoder25 分钟前
三行CSS代码把网页像素化
前端·javascript·github
用户377434860022036 分钟前
从渲染集合到模块学习
前端
安心不心安37 分钟前
React状态管理——redux-saga异步操作
前端·javascript·react.js
猩猩程序员40 分钟前
bzip2 crate 从 C 切换为 100% Rust 实现
前端
500佰41 分钟前
总结前端三年 理想滚烫与现实的冰冷碰撞
前端
全栈小542 分钟前
【前端】Vue3+elementui+ts,给标签设置样式属性style时,提示type check failed for prop,再次请出DeepSeek来解答
前端·elementui
哪 吒42 分钟前
突破亚马逊壁垒,Web Unlocker API 助您轻松获取数据
前端·网络·python·网络安全
岸边的风44 分钟前
JavaScript篇:【前端必备】数组方法大全:从‘会写’到‘玩出花’,你掌握几个?
开发语言·前端·javascript
三天不学习44 分钟前
一文了解Blob文件格式,前端必备技能之一
前端·blob