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

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

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

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

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 包裹组件
tsx 复制代码
// 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

ts 复制代码
export { AnalyticsSDK } from "./core";
export { initAnalytics, usePageView, useTrackClick, withTrackClick } from "./react";

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

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 项目中)

tsx 复制代码
// 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 项目里)

tsx 复制代码
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 上报到服务端的错误数据示例

json 复制代码
[
  {
    "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 包发布。

这样就能在实际项目中做到埋点、错误监控一体化。

相关推荐
四月_h6 分钟前
在 Vue 3 + TypeScript 项目中实现主题切换功能
前端·vue.js·typescript
qq_4275060812 分钟前
vue3写一个简单的时间轴组件
前端·javascript·vue.js
雨枪幻。1 小时前
spring boot开发:一些基础知识
开发语言·前端·javascript
lecepin2 小时前
AI Coding 资讯 2025.8.27
前端·ai编程
TimelessHaze2 小时前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯3 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越3 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区3 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
qfZYG3 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器
bug爱好者3 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript