@rrweb/record 实战指南:rrweb 2.x 独立录制包的安装、事件采集与隐私配置
【免费下载链接】rrwebrecord and replay the web项目地址: https://gitcode.com/gh_mirrors/rr/rrweb
@rrweb/record是 rrweb 在 2.x 时代拆出的独立录制包,专门承载网页录制(record)相关代码,面向需要在前端应用或网页中采集用户操作事件(DOM 变化、滚动、交互、Canvas 等)的生产场景。本文围绕 packages/record/README.md 展开,完整覆盖其三种安装方式、record函数的核心用法、全量参数表、隐私脱敏与 Checkout 快照机制,并结合仓库源码与测试用例给出可复现的实操结论。读完本文,你将能独立完成@rrweb/record的接入、事件上报、按需全量快照与敏感内容屏蔽。
包定位与设计动机
在 rrweb 2.x 的包拆分方案中,录制与回放被拆成两个独立包:@rrweb/record负责录制,@rrweb/replay负责回放,另有@rrweb/all提供一键合并导入。官方 guide.md 明确指出:在大多数生产场景中,录制端与回放端部署在不同页面/应用中,因此录制页使用@rrweb/record、回放页使用@rrweb/replay(或rrweb-player)是推荐组合,而rrweb主包已被标记为 deprecated,新项目应直接使用拆分后的包。
@rrweb/record的源码目前非常精简——packages/record/src/index.ts 只有三行,本质是从rrweb主包 re-exportrecord函数:
import { record } from 'rrweb'; export { record };文档 Notes 部分也明确说明:当前该包实质上只是主rrweb包中record函数的包装器,未来所有 record 相关代码都会迁移至此。因此,本包的全部能力(选项解析、快照、观察器等)实际实现位于 packages/rrweb/src/record/index.ts,阅读该文件即可理解record的底层原理。
此外,rrweb 官方的云端录制浏览器客户端browser-client也是基于本包构建的,且接受与record完全相同的 options——仓库中对应的实现在 packages/browser-client。如果你需要基于 WebSocket 对接 rrweb Cloud API,可在该包基础上二次开发。
安装与引入的三种方式
方式一:Bundler / npm(推荐)
npm install @rrweb/recordimport { record } from '@rrweb/record';从 packages/record/package.json 可以看到,该包以 ESM 为主路径:exports字段同时提供了import(./dist/record.js)与require(./dist/record.cjs)两个入口并各自配套类型声明(.d.ts/.d.cts),因此 CommonJS 的require('@rrweb/record')依然可用,但 2.x 官方推荐 ESM 导入。
方式二:浏览器直引 ESM(无构建)
在不使用打包器的场景下,直接通过 CDN 加载浏览器 ESM 产物:
<script type="module"> import { record } from 'https://cdn.rrweb.com/record/current/dist/record.js'; </script>current指向最新稳定版本,适合快速体验;- 生产环境建议固定精确版本号以保证 URL 不可变,例如
https://cdn.rrweb.com/record/2.0.0/dist/record.js。
方式三:传统<script>直引(UMD 回退)
仅用于不支持 ES Module 的旧环境兼容:
<script src="https://cdn.rrweb.com/record/current/dist/record.umd.cjs"></script>加载后全局变量名为rrwebRecord。该全局名也直接体现在打包配置中——packages/record/vite.config.ts 在调用公共构建配置时传入'rrwebRecord'作为库名,与 vite.config.default.ts 中基于 esbuildumdWrapper生成 UMD 与压缩产物的逻辑相衔接。
快速上手:启动一次录制
record的用法非常直接——传入一个包含emit回调的 options 对象即可:
import { record } from '@rrweb/record'; record({ emit(event) { // 将 event 发送到你的服务端 }, });录制期间,只要页面发生任何事件(DOM 变更、滚动、输入、鼠标交互等),录制器就会通过emit回调吐出事件对象。源码 packages/rrweb/src/record/index.ts#L122-L125 中有对应的运行时校验:在主录制帧内(inEmittingFrame且未跨域转发)必须提供emit,否则直接抛出'emit function is required'。
record方法返回一个停止函数,调用后不再产生新事件:
let stopFn = record({ emit(event) { if (events.length > 100) { stopFn(); // 采集满 100 个事件后停止 } }, });注意:record的返回类型是listenerHandler | undefined,在某些场景(例如跨域 iframe 子帧中,父帧已负责采集时)会返回空操作函数,因此调用停止函数前建议判空。
一个真实的上报示例
将采集到的事件批量 POST 到后端(以 rrweb Cloud API 为例):
const publicApiKey = 'your-public-api-key-here'; const recordingId = crypto.randomUUID(); let events = []; record({ emit(event) { events.push(event); }, }); // 将事件发送到后端并重置数组 function save() { const body = JSON.stringify({ events }); events = []; fetch(`https://api.rrweb.com/recordings/${recordingId}/events`, { method: 'POST', headers: { Authorization: `Bearer ${publicApiKey}`, 'Content-Type': 'application/json', }, body, }); } // 每 10 秒保存一次 setInterval(save, 10 * 1000);record 核心选项总览
record函数接受一个 options 对象,完整参数表(与 guide.md 一致)如下:
| key | 默认值 | 说明 |
|---|---|---|
| emit | 必填 | 接收录制事件的回调函数 |
| checkoutEveryNth | - | 每 N 个事件后输出一次全量快照,见下文 Checkout 章节 |
| checkoutEveryNms | - | 每 N 毫秒后输出一次全量快照,见下文 Checkout 章节 |
| blockClass | 'rr-block' | 字符串或 RegExp,命中元素不录制,回放时以同尺寸占位块显示 |
| blockSelector | null | 字符串选择器,命中元素不录制 |
| ignoreClass | 'rr-ignore' | 字符串或 RegExp,命中元素不录制其 input 事件 |
| ignoreSelector | null | 字符串选择器,命中元素不录制其 input 事件 |
| ignoreCSSAttributes | null | 需要忽略的 CSS 属性数组 |
| maskTextClass | 'rr-mask' | 字符串或 RegExp,命中元素及其子元素的文本被脱敏 |
| maskTextSelector | null | 字符串选择器,命中元素及其子元素的文本被脱敏 |
| maskAllInputs | false | 将所有 input 内容统一掩码为* |
| maskInputOptions | { password: true } | 按输入类型掩码,见下文 |
| maskInputFn | - | 自定义 input 内容录制逻辑 |
| maskTextFn | - | 自定义文本内容录制逻辑 |
| slimDOMOptions | {} | 移除 DOM 中的冗余部分,见下文 |
| dataURLOptions | {} | Canvas 图片格式与质量,会传给OffscreenCanvas.convertToBlob(),可有效减小录制数据体积 |
| inlineStylesheet | true | 2.0.0 起弃用,仍受支持,未来将被captureAssets取代 |
| hooks | {} | 事件级钩子 |
| packFn | - | 事件压缩函数,见 存储优化指南 |
| sampling | - | 事件采样配置,见 存储优化指南 |
| recordCanvas | false | 是否录制 canvas 元素(false/true) |
| recordCrossOriginIframes | false | 是否录制跨域 iframe(需在每个子 iframe 中注入 rrweb) |
| recordAfter | 'load' | 文档未就绪时,在指定事件后开始录制(DOMContentLoaded/load) |
| inlineImages | false | 2.0.0 起弃用,仍受支持,未来将被captureAssets取代 |
| collectFonts | false | 是否采集页面字体 |
| userTriggeredOnInput | false | 是否为 input 事件附加userTriggered标记(区分是否由用户直接触发) |
| plugins | [] | 加载扩展录制功能的插件,见 插件 API |
| errorHandler | - | rrweb 内部抛出错误时的回调,接收 error 参数 |
从源码 packages/rrweb/src/record/index.ts#L68-L102 可以确认这些选项的解构与默认值逻辑,其中几个细节值得注意:
recordAfter只接受'DOMContentLoaded',其他任何值(包括未传)统一按'load'处理;ignoreCSSAttributes在源码中被转换为Set使用;- 若同时传了已弃用的
mousemoveWait与新的sampling.mousemove,源码会优先保留sampling.mousemove(mousemoveWait仅在sampling.mousemove未定义时被迁移过去)。
maskInputOptions 与 slimDOMOptions 的完整取值
这两个选项的类型定义位于 packages/rrweb-snapshot/src/types.ts:
MaskInputOptions支持按输入类型逐项掩码:color、date、datetime-local、email、month、number、range、search、tel、text、time、url、textarea、select、password;SlimDOMOptions支持裁剪:script、comment、headFavicon、headWhitespace、headMetaDescKeywords、headMetaSocial、headMetaRobots、headMetaHttpEquiv、headMetaAuthorship、headMetaVerification等,以及用于屏蔽 title 标签"动画"类高频变更的选项。
源码 packages/rrweb/src/record/index.ts#L139-L163 揭示了maskInputOptions与maskAllInputs的合并逻辑:当maskAllInputs === true时,所有上述输入类型(含password)一律掩码;否则使用用户显式传入的maskInputOptions,未传时默认{ password: true }。slimDOMOptions则统一经过slimDOMDefaults处理后再参与快照。
隐私保护:四种脱敏手段
录制即涉及用户数据,@rrweb/record提供了多级隐私方案(详见 guide.md):
.rr-block:命中元素完全不录制,回放时渲染为同尺寸占位块(默认blockClass);.rr-ignore:命中元素不录制其 input 事件(默认ignoreClass);.rr-mask:命中元素及其所有子元素的文本被掩码(默认maskTextClass);input[type="password"]:默认即被掩码(对应maskInputOptions的默认值)。
<!-- 示例:对敏感区域打标记 --> <div class="rr-block">卡片号码区域,不录制</div> <div class="rr-mask">用户真实姓名会被掩码</div> <input type="password" /> <!-- 默认掩码 -->若默认类名与业务样式冲突,可通过blockSelector、ignoreSelector、maskTextSelector传入精确的 CSS 选择器;需要更细粒度的控制时,可用maskInputFn/maskTextFn自定义掩码逻辑,用ignoreCSSAttributes跳过特定 CSS 属性的录制。
Checkout:按事件数或时间输出全量快照
rrweb 的事件流遵循"初始全量快照(FullSnapshot)+ 后续增量快照(IncrementalSnapshot)"的链式结构,要回放完整会话必须持有链上全部事件。checkoutEveryNth/checkoutEveryNms允许你周期性强制输出新的全量快照,从而把事件流切成多个自洽的片段,便于只保留错误发生前的最后若干段。
先看源码层面的触发逻辑(packages/rrweb/src/record/index.ts#L213-L234):
- 每当发出
FullSnapshot事件时,记录lastFullSnapshotEvent并重置增量计数; - 每当发出
IncrementalSnapshot事件时计数加一,当checkoutEveryNth(按条数)或checkoutEveryNms(按与上次全量快照的时间差)任一条件满足时,调用takeFullSnapshot(true)强制输出新的全量快照; emit回调的第二个参数isCheckout即为该全量快照是否由 checkout 触发(而非会话开头那次)。
按事件数:保留出错前的最近 200~400 条
const publicApiKey = 'your-public-api-key-here'; const recordingId = crypto.randomUUID(); // 用二维数组存放多个事件片段 const eventsMatrix = [[]]; record({ emit(event, isCheckout) { if (isCheckout) { eventsMatrix.push([]); } const lastEvents = eventsMatrix[eventsMatrix.length - 1]; lastEvents.push(event); }, checkoutEveryNth: 200, // 每 200 个事件输出一次全量快照 }); // 出错时把最近两个片段发给后端 window.onerror = function () { const len = eventsMatrix.length; const events = eventsMatrix[len - 2].concat(eventsMatrix[len - 1]); const body = JSON.stringify({ events }); fetch(`https://api.rrweb.com/recordings/${recordingId}/events`, { method: 'POST', headers: { Authorization: `Bearer ${publicApiKey}`, 'Content-Type': 'application/json', }, body, }); };按时间:保留出错前的最近 5~10 分钟
record({ emit(event, isCheckout) { if (isCheckout) { eventsMatrix.push([]); } const lastEvents = eventsMatrix[eventsMatrix.length - 1]; lastEvents.push(event); }, checkoutEveryNms: 5 * 60 * 1000, // 每 5 分钟输出一次全量快照 });边界说明
指南 guide.md 特别提醒:由于增量快照链的机制限制,无法精确截取"最后 N 条"事件。checkoutEveryNth: 200实际会得到最后 200~400 条事件(checkout 触发前可能已累积近一个周期的增量);checkoutEveryNms: 5 * 60 * 1000对应最近 5~10 分钟的事件。大多数情况下你不需要配置 checkout,它只适合"错误发生后仅回传最近一段会话"这类场景。
工程化细节:产物形态与体积约束
@rrweb/record的构建产物由 packages/record/vite.config.ts 与根目录 vite.config.default.ts 共同决定:
- 输出
es(dist/record.js)与cjs(dist/record.cjs)两种库格式,并额外生成 UMD 与压缩版本; - UMD 产物除
dist/外还会同步到umd/目录(package.json中unpkg/jsdelivr字段指向umd/record.js),这是为兼容 jsDelivr MIME 类型问题而做的同步副本; - 打包时会将
rrweb、rrweb-snapshot、rrdom三个依赖解析到仓库内的本地源码入口(见 packages/record/vite.config.ts 中的sourceEntryByPackageName),保证按最新源码出包; - 全局库名
rrwebRecord服务于 UMD 直引场景。
值得注意的是,packages/record/test/record.test.ts 通过测试对产物体积做了硬约束:注释记录了 tree-shaking 修复前 ESM bundle 为 397373 字节、修复后为 161287 字节,并断言产物中不得包含回放专用的 postcss 代码,且dist/record.js体积必须比修复前基线至少小 200 KiB——这从测试侧印证了"录制包只打包录制代码、不携带回放代码"的设计目标,是录制端体积优化的可验证依据。
延伸阅读
- guide.md:官方入门指南,包含 Replayer、rrweb-player 与 REPL 工具等完整内容;
- 存储优化:
packFn、sampling的实战用法; - 插件 API:为
record扩展录制能力的插件体系; - 源码核心实现:
record函数的完整实现(观察器初始化、事件处理器、checkout 调度); - 录制相关测试:跨域 iframe、mutation、错误处理等录制行为的测试用例,可作为行为规范参考。
【免费下载链接】rrwebrecord and replay the web项目地址: https://gitcode.com/gh_mirrors/rr/rrweb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考