找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2999

积分

0

好友

416

主题
发表于 昨天 21:49 | 查看: 0| 回复: 0

你是不是也好奇:不依赖第三方库,仅靠浏览器自带的API,如何自己动手实现一个性能监控SDK?这篇文章将带你跳出“调包侠”的舒适区,不再满足于简单的工具使用,而是深入黑盒内部,探究数据背后的真相:

  • 抓数据:搞懂 FP、FCP、LCP、CLS、INP 这些指标的真实含义,并利用 PerformanceObserver API 将它们从浏览器底层逐一捕获。
  • 找原因:性能指标变慢时,不仅要给出数值,更要定位到具体的瓶颈——是哪个DOM元素、哪条资源或哪段脚本导致了问题。
  • 搞定 SPA:即便是在单页应用中通过前端路由切换,也能实现分页面、分时段的性能统计,确保数据清晰、不混淆。

虽然 Google 官方的 web-vitals 库能便捷地采集核心 Web 指标,但本文旨在引导你从零开始构建一个可控、可归因、可扩展的SDK。这样做的好处是数据来源透明、算法可调、上报机制完全可定制。

你将收获什么?

亲手编写 SDK 不仅是为了“造轮子”,更是为了将黑盒变成白盒,你将深入掌握:

  1. 拒绝盲猜:告别只闻其名不见其实!我们将带你直接使用原生 PerformanceObserver API 抓取数据,彻底搞懂性能指标的产生机制。
  2. 全身体检:不止关注页面加载快慢(LCP),更要监控交互响应是否顺畅(INP)、视觉是否稳定(CLS),为你的应用进行一次全面的“CT扫描”。
  3. 工程架构:如何组织代码才能清晰有序?我们将教你如何进行模块化设计,把数据采集、处理、上报、配置等职责拆解清楚,便于未来扩展。
  4. 直击病灶:知道“慢”是远远不够的,必须找到“为什么慢”。我们将实现精准定位导致卡顿的 DOM 元素,并能够区分耗时任务是来自主线程还是第三方 iframe

预备知识:解读核心性能指标

别被一堆专业缩写吓到。对于前端性能,我们核心关注三个维度:加载速度(Loading)、交互响应(Interaction)、视觉稳定(Visual Stability)

1. Loading (加载):关注页面渲染的关键时刻

用户最怕面对一片空白。此阶段我们监控三个关键时间点:

指标 (全称) 解释 典型场景
FP (First Paint) 屏幕亮了:浏览器开始渲染任何内容(哪怕只是背景色)的时刻。 屏幕从纯白变为浅灰色,虽然还没有具体内容,但你知道页面“活着”。
FCP (First Contentful Paint) 看到内容了:浏览器渲染出第一个实质性内容(文字、图片、Logo)的时刻。 页面上终于出现了“Loading...”文字或者导航栏的Logo。
LCP (Largest Contentful Paint) 主角登场:视口内可见的最大图片或文本块渲染完成的时刻。这是 Google 最看重的加载指标。 商品详情页的大图终于加载出来,你终于能看清商品细节。

⏱️ 及格线:FCP < 1.8s,LCP < 2.5s

2. Interaction (交互):关注用户操作的流畅度

内容加载完成后,用户开始与页面交互,此时最怕出现卡顿。

指标 (全称) 解释 典型场景
FID (First Input Delay) 第一下没反应?:用户首次与页面交互(点击、输入)到浏览器开始处理该事件的时间差。 兴奋地点击“登录”按钮,结果页面没有立即响应,过了约1秒按钮才变色。
INP (Interaction to Next Paint) 越用越卡?:FID 的升级版。它监控用户在整个会话中所有交互的延迟,并取其中最慢的几次进行评估。 在输入框中打字,每输入一个字符,输入框都要卡顿一下才能显示出来,有种“粘滞感”。
Long Task (长任务) 谁在堵路?:任何执行时间超过 50ms 的 JavaScript 任务。 主线程如同单行道,一个复杂的计算任务(大卡车)长时间占用,导致后续的点击事件全部排队等待。

⏱️ 及格线:FID < 100ms,INP < 200ms,Long Task < 50ms

:长任务不仅出现在交互阶段,在加载时也常见;它会延长白屏时间,拉高 TBT(总阻塞时间)。我们将其归到“交互”部分讨论,是因为它最直接影响的是点击/输入的响应速度(FID、INP)。

3. Visual Stability (视觉稳定性):关注页面布局的稳定

这是最影响用户体验的问题之一。

指标 (全称) 解释 典型场景
CLS (Cumulative Layout Shift) 手滑点错了!:页面在加载过程中,布局发生意外移动的累积程度。 刚想点“取消”按钮,顶部广告突然加载并把页面内容向下挤,导致你误点了“支付”。CLS分数越低,页面越稳定。

⏱️ 及格线:CLS < 0.1

系统架构与功能设计

为了保证 SDK 的轻量性与可扩展性,我们采用分层架构设计。整个系统由核心采集层数据处理层数据上报层配置中心四大模块组成。

简单来说,就是将职责划分明确:采集模块只负责抓取数据,处理模块只负责清洗数据,上报模块只负责发送数据,配置模块则管理所有运行参数

前端性能监控SDK系统架构流程图

架构要点

  • 采集层:按模块划分,各司其职(Loading / Interaction / VisualStability / Network)
  • 处理层:负责数据清洗、格式化,以及关键指标的归因分析(定位导致问题的DOM或脚本)
  • 上报层:支持 sendBeaconfetch keepalive 双保险策略,确保页面卸载时数据不丢失
  • 配置中心:环境区分、采样率控制、调试日志开关等

1. 核心采集层 (Collectors) —— 基于用户体验的四步监控

这是 SDK 的核心。我们不按技术类型分类,而是依据用户的真实感受来划分模块,对应源码中的四个核心目录:

  • Step 1: Loading (页面加载)

    • 核心目标:紧盯白屏时间与关键内容的渲染。
    • 实现手段:利用 Paint TimingLargest Contentful Paint API,捕获 FP、FCP、LCP 及页面加载完成的时机。
  • Step 2: Interaction (用户交互)

    • 核心目标:监控用户操作的响应速度与流畅度。
    • 实现手段:通过 Event Timing 监听点击延迟(FID/INP),并用 Long Task API 找出阻塞主线程的元凶。
  • Step 3: Visual Stability (视觉稳定)

    • 核心目标:防止页面布局“乱跳”,提升视觉舒适度。
    • 实现手段:结合 Layout Shift API 计算布局偏移(CLS),并针对 SPA 应用实现特殊的会话窗口计算。
  • Step 4: Network (网络请求)

    • 核心目标:定位资源加载与接口响应的性能瓶颈。
    • 实现手段:利用 Resource Timing API,深度解析静态资源与 XHR/Fetch 请求的 DNS、TCP、TTFB 等关键阶段耗时。

2. 数据处理层 (Processor) —— 数据清洗与增强

  • 清洗数据:浏览器原生 API 返回的数据结构复杂,我们需要将其清洗、转换为统一、干净的 JSON 格式,便于后端存储。
  • 数据增强:仅知道“卡了”不够,还需定位“卡在哪里”。例如,当 LCP 过慢时,我们会自动附加导致慢的元素的选择器;长任务发生时,会尝试解析其来源。

3. 数据上报层 (Reporter) —— 可靠的数据快递员

  • 使命必达:用户关闭页面时,数据必须成功发出。我们首选兼容性好的 Navigator.sendBeacon;在现代浏览器中,也可使用 fetch(..., { keepalive: true }) 以支持自定义请求头。
  • 省流模式:非关键日志采用批量上报以节省流量,关键错误则实时上报,确保问题能被即时发现。

4. 配置中心 (Configurator) —— 全局遥控器

  • 灵活控制:通过初始化 options 参数进行控制。开发环境需要看详细日志?打开!生产环境只上报错误?关闭!采样率设置为多少?由你决定。

核心代码实现

项目结构

为了保持代码的模块化和可维护性,我们采用以下目录结构:

performance-monitor/
├── dist/                  # 打包产物
├── src/                   # 源码目录
│   ├── index.ts          # 入口文件
│   ├── loading/          # 加载与绘制采集(FP/FCP/LCP/Load)
│   ├── interaction/      # 交互采集(FID/INP/LongTask)
│   ├── visualStability/  # 视觉稳定性(CLS)
│   ├── network/          # 资源与请求(ResourceTiming / API 请求)
│   ├── report/           # 数据上报(sendBeacon / fetch keepalive)
│   └── util/             # 工具与路由监听(getSelector/onUrlChange)
├── test/                  # 测试靶场
│   ├── server.js         # 本地测试服务
│   ├── index.html        # 指标触发页面
│   └── case-*.js         # 专项示例(cls/interaction/longtask/network)
├── package.json          # 项目配置
├── rollup.config.js      # Rollup 打包配置
└── tsconfig.json         # TypeScript 配置
  • src 目录的核心职责是全面、准确地抓取性能数据。它按照 Loading、Interaction、VisualStability、Network 四大板块划分,模块间职责清晰,互不干扰。
  • 开箱即用:使用 rollup 进行打包,产物输出至 dist 目录;test 目录下提供了可交互的测试页面和本地服务,方便直接运行验证。
  • 拒绝黑盒:我们使用 TypeScript 编写,直接调用浏览器原生的 PerformanceObserverResourceTiming API,每一毫秒的数据都源于真实的浏览器性能记录。

浏览项目的完整代码及示例可以访问 GitHub 仓库

1. 主入口 (index.ts)

入口文件负责对外暴露初始化方法,并串联各个监控模块。

  • 职责明确init() 方法一键启动所有监控,按照用户体验的生命周期(加载 -> 交互 -> 网络)依次调用。
  • 配置中心:构造函数接收 options,实现默认配置与用户自定义配置的合并。
  • 模块解耦:不直接在入口文件中编写监控逻辑,而是通过 import 引入四大模块的 startXXX 函数,实现高内聚、低耦合。

示例代码

// src/index.ts
import { startFP, startFCP, startLCP, startLoad } from './loading';
import { startFID, startINP, startLongTask } from './interaction';
import { startCLS } from './visualStability';
import { startEntries, startRequest } from './network';

export default class PerformanceMonitor {
  constructor(options: any = {}) {
    this.options = { log: true, ...options };
  }

  init() {
    // 1. 页面加载与渲染 (Loading & Rendering)
    startFP();
    startFCP();
    startLCP();
    startLoad();

    // 2. 交互响应 (Interaction)
    startFID();
    startINP();
    startLongTask();

    // 3. 视觉稳定性 (Visual Stability)
    startCLS();

    // 4. 资源与网络 (Resource & Network)
    startEntries();
    startRequest();

    console.log('Performance Monitor Initialized');
  }
}

2. Loading 监控 (loading/index.ts)

这部分负责捕捉页面从白屏到内容完全呈现的关键时刻。我们将此过程拆解为三个关键动作:屏幕首次绘制 (FP) -> 首次内容绘制 (FCP) -> 最大内容绘制 (LCP)

  1. FP (First Paint):屏幕不再空白(例如背景色出现)。
  2. FCP (First Contentful Paint):页面上出现了第一个实质性的文字或图片。
  3. LCP (Largest Contentful Paint):视口中最大的内容元素(如图片、标题)渲染完成。
  4. Load:页面及所有依赖资源加载完成。

(1) FP & FCP:监控页面首次渲染

实现要点

  • FP 和 FCP 在浏览器性能记录中同属 paint 类型,代表用户“第一眼”的感知。
  • 使用 PerformanceObserver 进行监听。
  • 关键参数 buffered: true:SDK 的初始化往往晚于页面开始渲染。如果不设置此参数,你将无法获取到在 SDK 加载之前就已发生的 FP/FCP 事件。设置为 true 后,浏览器会将缓冲区中已有的性能条目补发给观察者。

代码实战

// src/loading/FP.ts (FCP逻辑类似,只需筛选 entry.name === 'first-contentful-paint')
export function startFP() {
  const entryHandler = (list) => {
    for (const entry of list.getEntries()) {
      // 筛选 'first-paint'
      if (entry.name === 'first-paint') {
        observer.disconnect(); // FP 只会发生一次,捕获后即可断开观察以节省资源

        const json = entry.toJSON();
        console.log('FP Captured:', json);

        // 上报数据结构
        const reportData = {
          ...json,
          type: 'performance',
          name: entry.name,
          pageUrl: window.location.href,
        };
        // ... 调用上报函数
      }
    }
  };

  // 1. 创建观测者
  const observer = new PerformanceObserver(entryHandler);

  // 2. 开始观察 ‘paint’ 类型,buffered: true 是关键
  observer.observe({ type: 'paint', buffered: true });

  // 3. 返回清理函数(便于在需要时停止监控)
  return () => observer.disconnect();
}

(2) LCP:监控最大内容绘制

实现要点

  • 动态变化:LCP 代表视口内最大内容元素的渲染时间。与 FP/FCP 一次性确定不同,随着图片加载、字体渲染或内容插入,更大的元素可能出现,LCP 值会不断更新。
  • 何时确定最终值? 浏览器会在用户首次交互(点击、按键)或页面隐藏时,停止产生新的 LCP 候选值。因此,我们需要监听这些“停止信号”,取最后一次候选值作为最终结果上报。
  • 元素归因:仅上报时间是不够的。我们需要利用条目的 element 属性计算出其 CSS 选择器,以精确定位是哪张图片哪段文本导致了 LCP 延迟。

代码实战

// src/loading/LCP.ts
import { getSelector } from '../util/index';

export function startLCP() {
  let lcpEntry: PerformanceEntry | undefined;
  let hasReported = false;

  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // 持续记录最新的 LCP 候选值
      lcpEntry = entry;
    }
  });
  observer.observe({ type: 'largest-contentful-paint', buffered: true });

  const report = () => {
    if (hasReported || !lcpEntry) return;
    hasReported = true;

    const json = (lcpEntry as any).toJSON();
    const reportData = {
      ...json,
      lcpTime: lcpEntry.startTime,
      elementSelector: getSelector((lcpEntry as any).element), // 关键:附加元素选择器
      type: 'performance',
      name: lcpEntry.name,
      pageUrl: window.location.href,
    };
    console.log('LCP Final Report:', reportData);
    // ... 调用上报函数
  };

  // 页面隐藏或用户首次交互时,上报最终 LCP
  const onHidden = () => {
    if (document.visibilityState === 'hidden') report();
  };

  // 监听页面显示状态变化
  document.addEventListener('visibilitychange', onHidden, { once: true });
  window.addEventListener('pagehide', report, { once: true });
  // 监听用户首次交互
  ['click', 'keydown', 'pointerdown'].forEach((type) => {
    window.addEventListener(type, report, { once: true, capture: true });
  });

  return () => {
    observer.disconnect();
    document.removeEventListener('visibilitychange', onHidden);
  };
}

3. Interaction 监控

交互性能直接决定用户对页面“顺不顺手”的感受。这里我们重点关注 FID/INP(响应速度)Long Task(主线程阻塞)

(1) FID & INP:监控交互响应延迟

概念辨析

  • FID首次输入延迟。关注“第一印象”,即用户进入页面后第一次交互(点击、输入)的响应延迟。它只衡量从交互发生到浏览器主线程开始处理该事件之间的排队时间
  • INP交互到下次绘制。关注“全程体验”,它会记录页面整个生命周期内用户的所有交互延迟(包含排队、处理、渲染全过程),并取其中最慢的几次作为最终评分。INP 更能反映用户的实际体验。

FID 代码实现

// src/interaction/FID.ts
export function startFID() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // 核心公式:处理开始时间 - 交互开始时间 = 延迟时间
      const delay = entry.processingStart - entry.startTime;
      console.log('FID:', delay, entry.target);
      observer.disconnect(); // FID 只监控第一次交互,捕获后即可断开
    }
  });
  observer.observe({ type: 'first-input', buffered: true });
}
  • processingStart - startTimestartTime是用户交互发生的时刻,processingStart是浏览器主线程开始执行对应事件处理函数的时刻。差值即是因主线程繁忙导致的等待时间。
  • buffered: true:确保即使 SDK 加载稍晚于用户首次交互,也能捕获到该事件。

INP 代码实现(基础监听)

// src/interaction/INP.ts
export function startINP() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // 持续收集所有交互事件
      // 实际实现中需要维护一个数组,存储最慢的几次交互耗时
      console.log('Interaction Latency:', entry.duration, entry.target);
    }
  });
  // 注意:INP 监听的是 ‘event’ 类型,并可以设置一个最小阈值忽略极短的交互
  observer.observe({ type: 'event', durationThreshold: 16, buffered: true });
}
  • type: 'event':监听所有事件类型的性能条目。
  • durationThreshold: 16:优化参数,忽略短于 16ms(约一帧)的交互,减少数据噪音。

注意事项

  • Google 已正式用 INP 取代 FID 作为核心 Web 指标。生产环境应优先监控 INP。
  • 交互延迟通常由执行时间过长的 JavaScript 任务(Long Task)阻塞主线程导致。

(2) Long Task:监控主线程阻塞

概念:任何连续执行时间超过 50ms 的 JavaScript 任务都被视为长任务。由于浏览器主线程是单线程的,长任务会阻塞渲染、事件处理等,导致页面“卡死”。

实现要点

  • Long Task API 可以帮助定位长任务的宏观来源,通常通过 entry.attribution 字段可以区分任务是来自当前主页面 (window) 还是第三方 iframe(如广告),但一般无法直接定位到具体的 .js 文件或函数。

代码实现

// src/interaction/longTask.ts
export function startLongTask() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      if (entry.duration > 50) {
        // attribution 包含任务来源信息,如 ‘self’ (当前页面) 或 iframe 的 src
        console.log('LongTask:', entry.duration, entry.attribution);
      }
    }
  });
  observer.observe({ type: 'longtask', buffered: true });
}

优化建议:对于不可避免的繁重计算,应进行任务拆分。常用手段包括:使用 setTimeoutrequestAnimationFrame 分片执行、将计算密集型任务移至 Web Worker

4. Visual Stability 监控:CLS

核心挑战

  1. 区分预期与非预期位移:用户主动交互(如点击按钮展开菜单)引起的布局变化不应计入 CLS。浏览器通过 hadRecentInput 标志(500ms 内有过用户输入)来帮助我们过滤。
  2. 累积计算:CLS 是一个“累积”分数。页面整个会话期间所有非预期的布局偏移分数都会被累加。
  3. 最终上报时机:必须在页面卸载(隐藏或关闭)时,才计算并上报最终的 CLS 总分。

代码实现

// src/visualStability/CLS.ts
export function startCLS() {
  let clsValue = 0;
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // 核心:剔除用户近期交互导致的预期偏移
      if (!entry.hadRecentInput) {
        clsValue += entry.value; // 累加每次非预期偏移的分数
      }
    }
  });
  observer.observe({ type: 'layout-shift', buffered: true });

  const report = () => console.log('CLS Final:', clsValue); // 最终上报

  // 双重保险:监听页面卸载的两种事件
  window.addEventListener('pagehide', report, { once: true });
  document.addEventListener(
    'visibilitychange',
    () => {
      if (document.visibilityState === 'hidden') report();
    },
    { once: true }
  );
}
  • 为什么不用 beforeunload/unload 这些事件在移动端或页面跳转时可能不触发,且会妨碍浏览器的往返缓存(BFCache)优化。visibilitychangepagehide 是现代推荐的做法。

5. Network 监控:资源与接口性能

浏览器提供了 Resource Timing API 来监控所有网络资源的加载性能,包括图片、脚本、样式以及 fetch / XHR 请求。

基础实现(监控所有资源)

export function startEntries() {
  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // entryType 为 ‘resource’ 代表网络资源
      if (entry.entryType === 'resource') {
        console.log(
          'Resource:',
          entry.name, // 资源URL
          entry.initiatorType, // 发起者类型 (img, script, fetch, xmlhttprequest等)
          entry.duration // 总耗时
        );
      }
    }
  });
  observer.observe({ type: 'resource', buffered: true });
}

进阶实现(专注 API 请求耗时分析)
我们可以过滤出 fetchxmlhttprequest 类型的请求,并解析其详细的网络阶段耗时。

export function startRequest() {
  const entryHandler = (list) => {
    const data = list.getEntries();
    for (const entry of data) {
      // 过滤出 API 请求
      if (
        entry.initiatorType === 'fetch' ||
        entry.initiatorType === 'xmlhttprequest'
      ) {
        const reportData = {
          name: entry.name, // 请求地址
          type: 'performance',
          subType: entry.entryType,
          sourceType: entry.initiatorType,
          duration: entry.duration, // 请求总耗时
          dns: entry.domainLookupEnd - entry.domainLookupStart, // DNS 解析耗时
          tcp: entry.connectEnd - entry.connectStart, // TCP 连接耗时
          ttfb: entry.responseStart - entry.requestStart, // 首字节时间 (TTFB)
          transferSize: entry.transferSize, // 响应体大小
          startTime: entry.startTime,
          pageUrl: window.location.href,
        };
        console.log('Network Request:', reportData);
      }
    }
  };
  const observer = new PerformanceObserver(entryHandler);
  observer.observe({ type: 'resource', buffered: true });
}

关键指标解读

  • TTFBresponseStart - requestStart。反映服务端处理请求的速度。此值高通常意味着后端性能瓶颈。
  • TCP & DNS:反映网络连接建立阶段的耗时。如果 DNS 时间过长,可能需要考虑 DNS 预解析或 CDN 优化。
  • TransferSize:响应体大小。过大的响应体是导致下载时间长的直接原因,应考虑数据分页或压缩。

注意事项:对于跨域资源,除非响应头包含 Timing-Allow-Origin,否则许多详细的计时信息(如 DNS、TCP)将返回 0,这是浏览器的安全策略。

6. 必备工具函数:DOM 元素定位

为了能准确定位导致性能问题的元素(如 LCP 元素),我们需要一个工具函数将 DOM 元素转换为 CSS 选择器。

// src/util/index.js
export function getElementSelector(element) {
  if (!element || element.nodeType !== 1) return '';

  // 如果有 id,直接返回 #id
  if (element.id) {
    return `#${element.id}`;
  }

  // 递归向上查找,构建选择器路径
  let path = [];
  while (element) {
    let name = element.localName;
    if (!name) break;

    // 如果在向上查找过程中遇到 id,拼接后停止
    if (element.id) {
      path.unshift(`#${element.id}`);
      break;
    }

    // 加上 class
    let className = element.getAttribute('class');
    if (className) {
      name += '.' + className.split(/\s+/).join('.');
    }

    path.unshift(name);
    element = element.parentElement;
  }

  return path.join(' > ');
}

7. 可靠的数据上报层

数据上报的核心痛点在于:用户关闭页面时,普通的异步请求(如 fetch)可能被浏览器中止,导致数据丢失。

解决方案:采用 sendBeacon API 为主,fetch + keepalive 为降级的组合策略,确保页面卸载期间的数据可靠发送。

代码实现

// src/report/index.ts
export const sendBehaviorData = (data: Record<string, any>, url: string) => {
  // 1. 包装数据
  const dataToSend = {
    ...data,
    userAgent: navigator.userAgent,
  };

  // 2. 优先使用 sendBeacon (最稳定,不阻塞页面卸载)
  if (navigator.sendBeacon) {
    const blob = new Blob([JSON.stringify(dataToSend)], {
      type: 'application/json',
    });
    // sendBeacon 返回 true 表示成功加入发送队列
    navigator.sendBeacon(url, blob);
    return;
  }

  // 3. 降级方案:使用 fetch + keepalive
  fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(dataToSend),
    keepalive: true, // 关键参数!允许请求在页面关闭后继续
  }).catch((err) => {
    console.error('上报失败:', err);
  });
};

工程化构建与发布

一个成熟的 SDK 需要便于分发和集成。我们选择使用 Rollup 进行打包,因为它更适合库(Library)的构建,能生成更简洁的代码。

1. 构建配置

rollup.config.js 配置:我们配置 Rollup 输出三种模块格式以兼容不同环境。

import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';

export default {
  input: 'src/index.ts',
  output: [
    { file: 'dist/index.cjs.js', format: 'cjs', sourcemap: true }, // CommonJS,用于 Node.js 或老构建工具
    { file: 'dist/index.esm.js', format: 'es', sourcemap: true },  // ESM,支持 Tree Shaking
    {
      file: 'dist/index.umd.js',
      format: 'umd',
      name: 'PerformanceSDK', // 挂载到全局的变量名
      sourcemap: true,
      plugins: [terser()], // 对 UMD 包进行压缩
    },
  ],
  plugins: [
    typescript({
      tsconfig: './tsconfig.json',
      declaration: true, // 生成 .d.ts 类型声明文件
      declarationDir: 'dist',
    }),
  ],
};

package.json 关键字段

{
  "name": "performance-sdk",
  "version": "1.0.0",
  "main": "dist/index.cjs.js",      // CommonJS 入口
  "module": "dist/index.esm.js",    // ESM 入口 (支持 Tree Shaking)
  "browser": "dist/index.umd.js",   // UMD 入口 (供<script>标签直接使用)
  "types": "dist/index.d.ts",       // TypeScript 类型声明入口
  "files": ["dist"],                // 发布到 npm 的目录
  "scripts": {
    "build": "rollup -c"
  }
}

2. 发布到 NPM

  1. 构建:运行 npm run build 生成最新的 dist 目录。
  2. 登录:在终端执行 npm login
  3. 发布:执行 npm publish --access public

发布成功后,开发者即可通过 npm install performance-sdk 来使用你的 SDK。

3. 如何使用

NPM 安装 + ES Modules (推荐):

npm install performance-sdk
import PerformanceMonitor from 'performance-sdk';
const monitor = new PerformanceMonitor({ /* 可选配置 */ });
monitor.init();

CDN 引入 (UMD):

<script src="https://unpkg.com/performance-sdk@1.0.0/dist/index.umd.js"></script>
<script>
  const monitor = new PerformanceSDK.PerformanceMonitor({ /* 可选配置 */ });
  monitor.init();
</script>

总结与展望

至此,你已经完成了一个功能完整、模块清晰、可工程化部署的前端性能监控 SDK。它涵盖了从数据采集(利用原生 Performance API)、数据处理与归因,到可靠上报的完整链路。

回顾四大监控支柱:

  1. Loading:通过 FP、FCP、LCP 监控“快不快”。
  2. Interaction:通过 FID、INP、Long Task 监控“顺不顺”。
  3. Visual Stability:通过 CLS 监控“稳不稳”。
  4. Network:通过 Resource Timing 监控“通不通”。

性能监控是前端监控体系的重要一环。要构建更全面的应用可观测性,你还可以进一步探索:

  • 错误监控:捕获 JavaScript 异常、Promise 拒绝、资源加载失败、接口请求异常等。
  • 用户行为监控:记录用户点击、页面浏览、功能使用等埋点数据,用于产品分析。

将这些监控能力结合起来,并配以可视化大屏(如 Grafana)和实时报警(如钉钉/飞书机器人),你就能打造出从前端到后端、从性能到稳定性的全方位应用健康保障体系。性能优化永无止境,希望这个手把手实现的 SDK 能成为你深入理解前端性能、构建强大监控能力的坚实起点。

欢迎在 云栈社区 分享你的实践经验和优化思路,与更多开发者共同探讨前端工程化与性能优化的最佳实践。




上一篇:Moltbot本地AI助手全解析:隐私优先的7x24小时智能体实战指南
下一篇:谷歌开源MedGemma 1.5医疗模型,专攻2D/3D医学影像,4B参数量普通显卡可部署
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-1 18:08 , Processed in 0.305902 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表