npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fe-tracking

v1.0.6

Published

前端可观测 SDK:RUM 监控平面 + CLS 取证平面,跨 Web / 小程序双端

Downloads

1,001

Readme

fe-tracking — 前端可观测 SDK

双平面架构:RUM 监控平面(告警/大盘,走 aegis)+ CLS 取证平面(完整操作链路,走批量日志)。 一套 core 同时服务 Web 端与小程序端(Taro),平台能力靠 adapter 注入。

定位

  • 核心零运行时依赖core/ 全部纯 TS;平台能力(请求 / 存储 / aegis 实例)通过 adapter 注入
  • 平台无关source(日志来源=哪个应用在上报)由接入方自定义,SDK 只透传不做分支判断;运行平台由选用的 adapter / bundle 入口决定,不占配置字段
  • 可插拔后端:取证平面的后端是 TrackingSink = (events) => Promise<void> 一个契约,CLS / 自建 / 任意后端都能接

安装

npm i fe-tracking

Web 端不需要额外 peer 依赖。小程序端(Taro)需要 @tarojs/taro,已声明为 optional peer —— Web 端项目不装它不会报警。

消费方 TS 解析兼容(1.0.1 起)

本包同时支持两种 TypeScript 模块解析策略,不要求消费方改 tsconfig

| 消费方 moduleResolution | 类型走哪条路 | |---|---| | bundler / node16 / nodenext | exports 字段 | | node(旧 node10 策略,Taro 项目常见默认) | main / types + typesVersions 前缀映射 |

⚠️ 1.0.0 在旧 node 策略下有 bugtypesVersions 用了通配 "*": ["dist/*"], 它把裸包名也套进映射、盖掉了 types 字段,导致 import from 'fe-tracking'TS2307 Cannot find module(而 subpath 反而正常)。1.0.1 改为按前缀显式展开修复。

这个 bug 在 SDK 仓自测里看不到 —— 本仓 tsconfig 用的是新策略,走 exports 不经 typesVersions。只有装进 moduleResolution: "node" 的项目才会现形。新增 subpath 目录时 记得同步往 typesVersions 加一条前缀,否则旧策略消费方取不到该目录的类型。

镜像事件字段变更(1.0.2 起)

Web 端「镜像模式」(aegis beforeRequest → CLS)的两项修正,消费方无需改代码, 升级即生效;两项都只是补字段,不改既有字段语义:

| 事件 | 变更 | 原因 | |---|---|---| | userAction(点击) | 新增 payload.viewPath = 完整 DOM XPath | 1.0.1 及之前把 aegis 的 view_type(标签名)与 view_tag(XPath)当成一个字段,写成 viewTag: d.view_type ?? d.view_tag——view_type 恒有值使 ?? 兜底永不触发,XPath 从未进过 CLS | | apiCall | action200 <url> 变为 200 <METHOD> <url> | aegis 日志里本就有 req method:,此前被丢弃;补上后与小程序端自采格式 200 GET /api/... 同形,两端链路可放在一起看 |

XPath 丢失的实际代价(2026-08-12 cms 分类页实测):同一秒的两条表格点击在 CLS 里 都只剩 {viewText:"", viewTag:"INPUT"},完全同形,被误判成「点了没反应反复点」; 补上 XPath 才看出是 TR[8]TR[9]——勾选了两个不同的行。

viewPath 是纯结构(标签名 + 序号),不含用户数据;截断至 300 字符。 method 正则限定 [A-Z]+,非 HTTP 动词形状一律不透传。

⚠️ action 格式变化会影响已有的 CQL 精确匹配:若有按 action:"200 https://…" 写死的查询或告警,需改成前缀/通配匹配。按 traceId / category / session 的查询不受影响。

请求参数镜像(1.0.3 起,默认关)

「启用/禁用」类操作常常开与关共用同一端点同一 method(如 PUT /admin/:id/enable), 方向只在请求体里。不镜像请求参数时,CLS 里两个相反操作完全同形——链路只能还原 「点了某个开关」,还原不了「开还是关」。

const mirror = createBeforeRequestMirror(tracking, {
  shouldIgnore: (log) => /* …防自反馈,见上 */,
  includeReqParam: true,   // ⚠️ 开之前先读下面这段
});

🔴 默认关是安全红线,不是保守默认:这一段的内容是消费方 api.reqParamHandler 的输出, 而 aegis 在该 handler 未配置时会原样上报请求体——登录接口的请求体含明文密码, 此时开启等于把密码写进 CLS。SDK 无从判断消费方配了什么,因此默认拒绝、由消费方 在确认自己的 handler 是字段白名单后显式承担这个判断。

即使开启,parseReqParam 仍有三重限幅:必须能解析成对象(数组/裸串丢弃)、 值必须是 string/number/boolean 原始类型(嵌套对象往往是整包透传的信号)、 最多 8 个键且序列化后 ≤200 字符。

消费方侧的配套写法(cms 实例,api.reqParamHandler 按接口+字段白名单):

const REQ_PARAM_WHITELIST = [
  { test: /\/admin\/categories\/[^/]+\/enable$/, pick: 'enabled' },
  { test: /\/admin\/products\/[^/]+\/visibility$/, pick: 'isLiveVisible' },
];
reqParamHandler: (body, url) => {
  const hit = REQ_PARAM_WHITELIST.find((w) => w.test.test(String(url ?? '')));
  if (!hit) return '';                       // 默认拒绝
  const b = typeof body === 'string' ? JSON.parse(body) : body;
  return JSON.stringify({ [hit.pick]: Boolean(b?.[hit.pick]) });  // 只取一个字段并重建
}

Taro 版本支持范围

peer 范围声明为 >=4,但这是已验证范围,不是技术下限:

  • 已验证:Taro 4.1.11(单测与三个接入端均在此版本)
  • 理论可用:Taro 3.x —— SDK 小程序侧只用到 Taro.requestTaro.getCurrentPagesuseDidShow 三个 API,均无 Taro 4 专属特性;官方 hooks 文档中 useLaunch / useLoad / useUnload 等标注了 "Taro v3.5.0+",而 useDidShow 未标版本,说明它更早就已提供
  • 未验证:3.x 下的类型定义与 getCurrentPages() 返回结构未实测,故不写进 peer 范围

Taro 3 项目装了会看到 peer 不匹配告警(npm 7+ 对 peer 只警告不拦截),功能预期可用。如需正式支持 3.x,请提 issue,我们补一轮 3.x 验证后放宽范围。

板块划分

| 板块 | 目录 | 职责 | 平台相关 | |------|------|------|---------| | 类型与配置 | src/types.ts | ITrackingConfig / TrackEvent / ITrackingAdapter 契约 | 否 | | 事件模型 | src/core/schema.ts | 统一事件 builder(who/when/where/what/correlate)+ CLS contents 扁平化 | 否 | | 会话 | src/core/session.ts | session_id 生成;storage 注入决定策略(内存单例 / sessionStorage 跨刷新) | 否 | | 队列 | src/core/queue.ts | 队列 / 面包屑 / LRU / 批量 / 定时 flush / 失败重发 | 否 | | CLS sink | src/sinks/cls.ts | 批量 POST /tracklog?topic_id=,事件 → logs[].contents 组包 | 否(request 注入) | | RUM sink | src/sinks/rum.ts | 封装注入的 aegis 实例(error 主动补报走这里进 RUM) | 否(aegis 注入) | | aegis 钩子工厂 | src/aegis/ | beforeReport(error 回流,防循环)/ beforeRequest(镜像 + level→category mapper) | 否(入参是 log 对象) | | 小程序平台件 | src/platform-mp/ | Taro adapter(Taro.request / 内存 storage)+ usePageReport 工厂 | 仅小程序端 | | Web 平台件 | src/platform-web/ | fetch adapter + sessionStorage | 仅 Web 端 | | 门面 | src/index.ts | createTracking(config, adapter):组装 session + queue + sinks + 路由 | 否 |

事件模型不含 version 字段——需要的话由接入方按需放进 payload,属有意裁剪。

数据流(谁调谁)

业务代码 track()/trackError()           aegis 自动采集
        │                                   │ beforeReport / beforeRequest 钩子
        ▼                                   ▼
   index.ts 门面 ──路由──► error 且非 aegis 来源 → sinks/rum.ts(进 RUM 告警)
        │
        ▼
   core/queue.ts(level 路由:all→队列+面包屑 / errorChain→仅面包屑;error 触发合并 flush)
        │
        ▼
   sinks/cls.ts ──批量 POST──► CLS /tracklog

防循环关键:aegis 钩子回流的 error 走内部 ingestAegisError(),只进 CLS 不再回写 aegis。

接入形态

// 小程序端(Taro):app.tsx
import { createTracking } from 'fe-tracking';
import { createMpAdapter } from 'fe-tracking/platform-mp/adapter';

// 第三参传 getter 而非实例:createTracking() 常先于 aegis 实例就绪,
// 直接传实例会把此刻的 null 冻结进闭包,永久拿不到后续初始化好的 aegis。
export const tracking = createTracking(TRACKING_CONFIG, createMpAdapter(), () => getRum());

// aegis 侧必配(防 SDK 自身的 CLS 请求被镜像/回流,形成自反馈回路):
new Aegis({
  // ...其余配置
  beforeReport: createBeforeReportHook(tracking, {
    shouldIgnore: (log) => [log.url, log.msg].some((v) => String(v ?? '').includes(tracking.getClsUrl())),
  }),
});

// Web 端:在 aegis 的 beforeRequest 里加镜像分支(必配,同上)
const mirror = createBeforeRequestMirror(tracking, {
  shouldIgnore: (log) => [log.url, log.msg].some((v) => String(v ?? '').includes(tracking.getClsUrl())),
});
beforeRequest: (data) => { mirror(data); return existingFilter(data); }

小程序页面侧只有两个 API:usePageReport()(pageView 自动)和它返回的 trackUserAction(action)

后端可插拔与多平台同时上报

// 场景 A:换成自建后端(config.cls 可省略;getClsUrl() 此时返回 '',shouldIgnore 需按自建 URL 排除)
const mySink: TrackingSink = (events) =>
  fetch('https://log.example.com/batch', { method: 'POST', body: JSON.stringify(events) })
    .then((r) => { if (!r.ok) throw new Error(String(r.status)); });
const t = createTracking({ env: 'prod', source: 'web' }, createWebAdapter(), null, { sinks: mySink });

// 场景 B:CLS + 自建双写(过渡期)——opts.sinks 是完全替换,保留 CLS 需显式组合
const t2 = createTracking(cfg, adapter, null, {
  sinks: [createClsSink({ ...cfg.cls!, source: cfg.source }, adapter.request), mySink],
});

多 sink 失败语义(有意设计):全部失败 → 整批保留、下次 flush 对所有 sink 重发(都没收到过,无重复风险);部分成功 → 不重发,失败侧丢该批——重发会让成功侧产生重复事件,对取证数据质量的伤害大于次要后端丢一批。

换监控平面(如用 Sentry 替代 aegis)需新写对应钩子工厂(照 src/aegis/ 模式),core 不动。

分发方式

npm 是唯一分发方式npm i fe-tracking)。历史上曾有一套「编译产物直拷」的 sync 分发脚本(scripts/sync.mjs + sync.targets.json + tsup bundle 产物),服务于消费仓幽灵依赖治理完成前、不便动包管理器的过渡期;三个消费仓(lanbao mp / cms / kol-mp)自 1.0.4 起均已改走 npm 依赖,该链路已于 2026-08 整体移除。如需考古,见 git history 中 scripts/sync.mjsdocs/archive/2026-08-03-tracking-sdk-implementation-plan.md

开发

npm test          # jest,128 用例
npm run typecheck # tsc --noEmit
npm run build     # tsc 输出 dist/(npm 包发布用)

prepublishOnly 会串跑 typecheck + test + build,发布前自动拦一道。