如果面试里让我回答这个问题,我一般会从架构分层开始讲。

我设计前端监控 SDK,核心思路是参考类似 Sentry 的做法,把它拆成核心包和环境适配包。核心包只放跟运行环境无关的能力,比如统一的初始化流程、插件机制、标准化的上报接口,还有对外暴露的监控实例。像这个项目里我就是拆成 corebrowserbrowser-utils 三层。core 不直接依赖 windowdocument 这些浏览器对象,所以它天然更容易做到跨框架、跨环境复用。

然后 browser 这一层,主要负责浏览器环境下的能力接入。比如我会在这里实现 BrowserTransport,基于 fetchsendBeacon 去做数据上报;同时接入浏览器里的错误监控和性能监控。再往下一层,browser-utils 主要放浏览器公共能力和性能采集工具,比如获取 userAgentplatformreferrerpath,还有 Web Vitals,像 FCPLCPCLSTTFBINP 这些指标。

这样拆分的好处有两个。第一是关注点分离,核心层不关心你在什么框架里运行,React、Vue、Vanilla 只是接入方式不同。第二是产物比较轻,浏览器只引浏览器适配包,不会把别的环境代码打进去。

如果要做到跨框架兼容,我觉得关键不是在核心里直接写 React 或 Vue 的逻辑,而是先把接口边界抽象好。比如 React 可以单独做一个 React 适配包,去接 ErrorBoundary;Vue 可以接 app.config.errorHandler;Vanilla 就直接监听全局错误。但不管是哪种方式,最终都统一走核心层的标准数据模型和上报链路。这样新增一个框架时,只需要加一个适配包,不需要改核心代码。

从模块设计上,我一般会拆成四块。第一块是异常监控,主要负责 JS 运行时错误、Promise 未捕获异常,后面还可以继续扩展资源加载错误和接口异常。第二块是性能监控,核心是浏览器 Performance API 和 Web Vitals。第三块是数据上报模块,负责批量、重试、限流、离线缓存这些能力。第四块是公共上下文模块,负责给每条数据补充环境信息,比如页面路径、浏览器信息、应用版本、用户标识。

我觉得这里最关键的一个设计点,就是插件化。因为监控 SDK 的需求一定会不断扩展,今天可能只有错误和性能,后面可能还要加用户行为、路由切换、接口耗时、白屏检测。如果前期没有插件机制,后面会越来越难维护。所以我会把错误监控、性能监控、行为采集都设计成可插拔模块,SDK 初始化时按顺序注册。这样扩展新能力时,不需要改核心流程,只要新增插件就可以。

另一个很关键的点是上报链路的稳定性。监控 SDK 不能只考虑“能发出去”,还要考虑不能影响业务。比如正常场景可以用 fetch,页面关闭前优先用 sendBeacon;同时要支持按数量或者按时间间隔批量上报,减少请求数。弱网或者离线场景下,还要考虑本地缓存和失败重试。再进一步,生产上通常还会加采样、限流、去重,避免异常风暴把服务端打挂。

性能监控这一块,我不会只看一个 load 时间,因为那样太粗了。更合理的做法是结合 Web Vitals 去看真实用户体验。比如 FCP 看首次内容绘制,LCP 看首屏关键内容出现时间,CLS 看页面抖动,INP 看交互延迟,TTFB 看服务端首字节时间。这样采回来的数据,才真的能支撑性能分析和定位。

还有一个容易被忽略的点,是数据模型要统一。我通常会约定统一的事件结构,比如 event_typetypemessagestackpathtimestampbrowserInfo。这样不管是错误事件、性能事件,还是业务自定义事件,最终都能走同一套存储、查询和告警逻辑,后面接分析平台也会更顺。

实现层面上,我会优先用 TypeScript。因为 SDK 本身就是给别人用的,类型系统其实就是 API 的一部分。像 MonitoringOptionsTransportIntegration 这些核心接口,用 TS 约束清楚以后,后续做扩展和重构时会更稳,也能减少很多运行时问题。

如果让我最后总结一句,我会说,这类高扩展性、跨框架兼容的前端监控 SDK,核心原则就是四个词:核心抽象、环境适配、功能插件化、上报稳定化。只要这四件事做好,整个 SDK 就会比较容易演进,也更适合在真实业务里落地。

面试时可以补的一句

如果面试官继续追问,我一般还会补一句:这个项目里我已经把浏览器错误采集、Web Vitals 采集、统一 Transport 上报链路跑通了。后续如果要继续演进,我会再补路由监控、用户行为采集、离线重试、采样限流,以及 React 和 Vue 的专门适配层。