【大前端】实现一个前端埋点SDK,并封装成NPM包

🚀来做一个支持 React 的前端埋点 SDK,并把它封装成 npm 包 的形式。整体分 3 部分:

  1. 核心 SDK(独立的采集 & 上报逻辑)
  2. React Hook / HOC 支持(自动埋点:路由变化、组件渲染、点击事件)
  3. npm 包结构(package.json、打包配置)

1. SDK 核心逻辑(src/core.ts

// src/core.ts (修改后)
export interface AnalyticsOptions {serverUrl: string;appId: string;userId?: string;maxQueueSize?: number;interval?: number;enableErrorTracking?: boolean; // 是否启用错误监控
}export class AnalyticsSDK {private serverUrl: string;private appId: string;private userId: string;private queue: EventData[] = [];private maxQueueSize: number;private interval: number;constructor(options: AnalyticsOptions) {this.serverUrl = options.serverUrl;this.appId = options.appId;this.userId = options.userId || this.getUserId();this.maxQueueSize = options.maxQueueSize || 10;this.interval = options.interval || 5000;this.startFlushTimer();window.addEventListener("beforeunload", () => this.flush(true));// 开启错误监控if (options.enableErrorTracking) {this.setupErrorTracking();}}private getUserId(): string {let uid = localStorage.getItem("analytics_uid");if (!uid) {uid = "uid_" + Math.random().toString(36).substring(2, 9);localStorage.setItem("analytics_uid", uid);}return uid;}public track(eventName: string, properties: Record<string, any> = {}) {const event: EventData = {eventName,properties,appId: this.appId,userId: this.userId,timestamp: Date.now(),url: window.location.href,ua: navigator.userAgent,};this.queue.push(event);if (this.queue.length >= this.maxQueueSize) {this.flush();}}private flush(sync = false) {if (this.queue.length === 0) return;const events = this.queue.splice(0, this.queue.length);if (sync && navigator.sendBeacon) {navigator.sendBeacon(this.serverUrl, JSON.stringify(events));} else {fetch(this.serverUrl, {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify(events),}).catch(() => {this.queue.unshift(...events);});}}private startFlushTimer() {setInterval(() => this.flush(), this.interval);}/** 绑定全局错误监控 */private setupErrorTracking() {// JS运行时错误 & 资源加载错误window.onerror = (message, source, lineno, colno, error) => {this.track("error_event", {type: "js_error",message,source,lineno,colno,stack: error?.stack || "",});};// Promise 未处理异常window.addEventListener("unhandledrejection", (event) => {this.track("error_event", {type: "unhandled_promise",reason: event.reason ? event.reason.toString() : "",});});}
}

2. React Hook 支持(src/react.tsx

这里做几个封装:

  • usePageView:监听页面切换(适配 React Router)
  • useTrackClick:点击埋点 Hook
  • withTrackClick:HOC 包裹组件
// src/react.tsx
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { AnalyticsSDK } from "./core";let sdk: AnalyticsSDK | null = null;export const initAnalytics = (instance: AnalyticsSDK) => {sdk = instance;
};// Hook: 页面访问埋点
export const usePageView = () => {const location = useLocation();useEffect(() => {if (sdk) {sdk.track("page_view", { path: location.pathname });}}, [location]);
};// Hook: 点击埋点
export const useTrackClick = (eventName: string, props?: Record<string, any>) => {return () => {if (sdk) {sdk.track(eventName, props);}};
};// HOC: 包裹组件自动埋点点击
export const withTrackClick = <P extends object>(Component: React.ComponentType<P>,eventName: string,props?: Record<string, any>
) => {return (componentProps: P) => {const handleClick = () => {if (sdk) {sdk.track(eventName, props);}};return <div onClick={handleClick}><Component {...componentProps} /></div>;};
};

3. npm 包目录结构

my-analytics-sdk/
├── package.json
├── tsconfig.json
├── rollup.config.js      # 打包配置
├── src/
│   ├── core.ts           # SDK核心逻辑
│   ├── react.tsx         # React hooks/HOC
│   └── index.ts          # 入口文件

src/index.ts

export { AnalyticsSDK } from "./core";
export { initAnalytics, usePageView, useTrackClick, withTrackClick } from "./react";

4. 打包配置(rollup.config.js

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" },{ file: "dist/index.esm.js", format: "esm" },],plugins: [typescript(), terser()],external: ["react", "react-router-dom"],
};

5. 使用示例(React 项目中)

// App.tsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AnalyticsSDK, initAnalytics, usePageView, useTrackClick } from "my-analytics-sdk";const sdk = new AnalyticsSDK({serverUrl: "https://your-server.com/track",appId: "my-app",
});
initAnalytics(sdk);function Home() {usePageView();const handleClick = useTrackClick("click_home_button", { id: "btn1" });return <button onClick={handleClick}>点我埋点</button>;
}function About() {usePageView();return <div>About Page</div>;
}export default function App() {return (<BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} /></Routes></BrowserRouter>);
}

✅ 至此,就有了一个 支持 React 路由 + 手动/自动埋点的 SDK,并且能发布成 npm 包
只需 npm publish 就能在其他项目中直接 npm install my-analytics-sdk 使用。


6. 增加错误监控

加一个 错误监控(window.onerror + unhandledrejection)自动埋点 的功能,让 SDK 既能做埋点又能做异常监控

错误监控其实是前端埋点 SDK 很重要的一部分。我们可以在 SDK 初始化时 自动监听全局的:

  • window.onerror → 捕获同步 JS 错误 / 资源加载错误
  • window.onunhandledrejection → 捕获未处理的 Promise 异常

并把这些错误也当作特殊事件(error_event)上报。


6.1 使用方式(React 项目里)

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AnalyticsSDK, initAnalytics, usePageView } from "my-analytics-sdk";const sdk = new AnalyticsSDK({serverUrl: "https://your-server.com/track",appId: "my-app",enableErrorTracking: true, // 开启错误监控
});
initAnalytics(sdk);function Home() {usePageView();const throwError = () => {throw new Error("手动触发一个错误试试");};const rejectPromise = () => {Promise.reject("手动触发 Promise 异常");};return (<div><h1>首页</h1><button onClick={throwError}>抛 JS 错误</button><button onClick={rejectPromise}>抛 Promise 错误</button></div>);
}function About() {usePageView();return <div>关于页面</div>;
}export default function App() {return (<BrowserRouter><Routes><Route path="/" element={<Home />} /><Route path="/about" element={<About />} /></Routes></BrowserRouter>);
}

6.2 上报到服务端的错误数据示例

[{"eventName": "error_event","properties": {"type": "js_error","message": "Uncaught Error: 手动触发一个错误试试","source": "http://localhost:3000/static/js/main.js","lineno": 42,"colno": 13,"stack": "Error: 手动触发一个错误试试\n    at onClick..."},"appId": "my-app","userId": "uid_xxx","timestamp": 1693042000000,"url": "http://localhost:3000/","ua": "Mozilla/5.0 ..."}
]

✅ 至此,SDK 已经同时支持 埋点 + 异常监控,而且能直接作为 npm 包发布。
这样就能在实际项目中做到埋点、错误监控一体化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2844.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode+Java+dpI

509.斐波那契数斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a;F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n >…

IntelliJ IDEA 新手入门教程-Java、Web、Maven创建(带图解)

&#xff08;以下内容大部分来自上述课程&#xff09; ps&#xff1a;本人用的是2021.3.2 1. 下载 可以参考图文&#xff0c;但破解我没试过&#xff1a;图文参考 中文插件&#xff1a;中文插件 ps&#xff1a;中文插件的文章是博客园的&#xff0c;直接一个idea新手专栏&a…

[React]Antd Upload组件上传多个文件

前言实现需求&#xff1a;上传多个文件。其实就是获取多个文件的绝对路径交给后端接口去处理。Upload组件 首先&#xff0c;这里不能调用Upload的onChange函数&#xff0c;因为上传中、完成、失败都会调用这个函数&#xff0c;在多个文件的情况下会出现多次调用问题。改用befor…

Java-多态

多态是 Java 面向对象三大特性&#xff08;封装、继承、多态&#xff09;中最灵活也最核心的概念之一。它允许我们用统一的方式处理不同的对象&#xff0c;大幅提升代码的扩展性和复用性。本文将结合实际案例&#xff0c;从概念、实现到应用&#xff0c;全面解析 Java 多态的核…

Redis常规指令及跳表

第一部分&#xff1a;Redis 常规指令Redis 是一个键值存储系统&#xff0c;其指令通常以 COMMAND KEY_NAME [ARGUMENTS...] 的形式存在。下面我们按照数据结构和功能来分类。1. 全局/键操作指令这些指令不特定于某一数据类型&#xff0c;适用于所有键。指令描述示例KEYS patter…

指纹云手机×Snapchat Spotlight:动态GPS+陀螺仪仿生方案

——基于时空坐标系重构与生物运动模拟的AR营销突破​​一、Snapchat Spotlight广告的技术困局​设备指纹关联风险​Snapchat通过陀螺仪基线值&#xff08;0.1误差&#xff09;和GPS坐标&#xff08;精度&#xff1c;5米&#xff09;构建设备指纹&#xff0c;相似度&#xff1e…

Java 编辑器与 IDE:开发者手中的利剑与盾牌

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 &#x1f349;学习方向&#xff1a;C/C方向学习者…

不再让Windows更新!Edge游戏助手卸载及关闭自动更新

文章目录Windows系统更新问题方法一&#xff1a;通过注册表手动设置1. 打开注册表编辑器2. 定位到目标路径3. 创建新的DWORD值4. 修改数值方法二&#xff1a;命令行设置1. 打开命令提示符2. 输入命令验证设置是否生效恢复更新Edge关闭游戏助手Edge关闭后台运行Edge关闭自动更新…

从Android到鸿蒙:一场本应无缝的转型-优雅草卓伊凡

从Android到鸿蒙&#xff1a;一场本应无缝的转型-优雅草卓伊凡看到Android开发者询问如何转向鸿蒙&#xff0c;卓伊凡不禁摇头&#xff1a;真正的Android工程师根本不需要“学习”鸿蒙&#xff0c;只需要简单查阅文档即可。近年来&#xff0c;随着鸿蒙系统的不断发展&#xff0…

Linux的线程概念与控制

目录 1、Linux的线程概念 1.1 什么是线程 1.2 分页式存储管理 1.3 线程的优点 1.4 线程的缺点 3、Linux的线程控制 3.1 POSIX线程库 3.2 线程创建 3.3 线程退出 3.4 线程等待 3.5 线程分离 1、Linux的线程概念 1.1 什么是线程 首先Linux内核不区分"进程"…

云原生俱乐部-RH294知识点归纳(3)

其实ansible还剩下使用角色和ansible内容集合来简化playbook、对ansible进行故障排除和自动执行Linux管理任务三部分。至于如何对ansible进行故障排除&#xff0c;只有在生产中碰到了故障才用得上&#xff0c;并且即使碰上的还是需要具体问题具体分析&#xff0c;但是可以该部分…

Flink 实时加购数据“维表补全”实战:从 Kafka 到 HBase 再到 Redis 的完整链路

一、业务背景 在电商实时运营场景中&#xff0c;加购行为&#xff08;AddShoppingCart&#xff09; 是最核心的用户行为之一&#xff0c;每秒钟可能产生数万条加购事件。以某头部电商平台为例&#xff0c;大促期间加购QPS可突破50万。 为了支持实时推荐、实时营销、实时大屏等业…