performance-helper 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,349 @@
1
+ # Performance SDK
2
+
3
+ 前端性能监控 SDK,用于采集和上报前端应用的性能指标、资源加载情况和错误信息。
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ **性能指标监控**:FCP、LCP、FID、CLS、TBT、TTFB 等 Web Vitals 指标
8
+ - ✅ **长任务监控**:监控阻塞主线程的长任务,记录详细的归因信息(脚本URL、DOM元素等)
9
+ - ✅ **LCP 元素定位**:自动记录 LCP 元素路径,支持可视化高亮显示
10
+ - ✅ **资源加载监控**:监控所有资源的加载时间和大小
11
+ - ✅ **错误监控**:自动捕获 JavaScript 错误、Promise 错误和资源加载错误
12
+ - ✅ **数据上报**:支持批量上报和立即上报,使用 sendBeacon 确保数据不丢失
13
+ - ✅ **采样率控制**:支持配置采样率,减少数据量
14
+ - ✅ **TypeScript 支持**:完整的类型定义
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ npm install performance-helper
20
+ ```
21
+
22
+ ## 快速开始
23
+
24
+ ### 基础使用
25
+
26
+ ```typescript
27
+ import PerformanceHelper from 'performance-helper';
28
+
29
+ const sdk = new PerformanceHelper({
30
+ reportUrl: 'https://your-api.com/report',
31
+ appId: 'your-app-id',
32
+ userId: 'user-123',
33
+ });
34
+
35
+ // 初始化 SDK
36
+ sdk.init();
37
+ ```
38
+
39
+ ### 完整配置
40
+
41
+ ```typescript
42
+ const sdk = new PerformanceHelper({
43
+ reportUrl: 'https://your-api.com/report', // 必需:上报地址
44
+ appId: 'your-app-id', // 可选:应用ID
45
+ userId: 'user-123', // 可选:用户ID
46
+ immediate: false, // 可选:是否立即上报,默认 false
47
+ batchInterval: 5000, // 可选:批量上报间隔(毫秒),默认 5000
48
+ monitorResources: true, // 可选:是否监控资源加载,默认 true
49
+ monitorErrors: true, // 可选:是否监控错误,默认 true
50
+ monitorPerformance: true, // 可选:是否监控性能指标,默认 true
51
+ sampleRate: 1, // 可选:采样率 0-1,默认 1
52
+ });
53
+
54
+ sdk.init();
55
+ ```
56
+
57
+ ### 使用示例
58
+
59
+ #### 获取性能指标并高亮 LCP 元素
60
+
61
+ ```typescript
62
+ // 获取性能指标
63
+ const metrics = sdk.getPerformanceMetrics();
64
+ console.log('LCP:', metrics.lcp);
65
+ console.log('TBT:', metrics.tbt);
66
+ console.log('LCP Element:', metrics.lcpElement);
67
+
68
+ // 高亮显示 LCP 元素(用于调试)
69
+ if (metrics.lcpElement) {
70
+ sdk.highlightLCPElement({
71
+ borderColor: '#ff0000',
72
+ borderWidth: '3px',
73
+ backgroundColor: 'rgba(255, 255, 0, 0.2)',
74
+ scrollIntoView: true,
75
+ });
76
+ }
77
+
78
+ // 查看长任务信息
79
+ if (metrics.longTasks && metrics.longTasks.length > 0) {
80
+ metrics.longTasks.forEach((task) => {
81
+ console.log(`长任务: ${task.duration}ms`);
82
+ task.attribution.forEach((attr) => {
83
+ if (attr.scriptURL) {
84
+ console.log(` 脚本: ${attr.scriptURL}`);
85
+ }
86
+ if (attr.elementPath) {
87
+ console.log(` 元素: ${attr.elementPath}`);
88
+ }
89
+ });
90
+ });
91
+ }
92
+ ```
93
+
94
+ ## API
95
+
96
+ ### 方法
97
+
98
+ #### `init()`
99
+ 初始化 SDK,开始监控。
100
+
101
+ ```typescript
102
+ sdk.init();
103
+ ```
104
+
105
+ #### `reportPerformance()`
106
+ 手动上报性能指标。
107
+
108
+ ```typescript
109
+ sdk.reportPerformance();
110
+ ```
111
+
112
+ #### `getPerformanceMetrics()`
113
+ 获取当前性能指标。
114
+
115
+ ```typescript
116
+ const metrics = sdk.getPerformanceMetrics();
117
+ console.log(metrics.fcp, metrics.lcp, metrics.fid);
118
+ ```
119
+
120
+ #### `getResources()`
121
+ 获取所有资源加载信息。
122
+
123
+ ```typescript
124
+ const resources = sdk.getResources();
125
+ ```
126
+
127
+ #### `getSlowResources(threshold?)`
128
+ 获取慢资源(超过阈值的资源)。
129
+
130
+ ```typescript
131
+ const slowResources = sdk.getSlowResources(2000); // 超过 2 秒的资源
132
+ ```
133
+
134
+ #### `getErrors()`
135
+ 获取所有错误信息。
136
+
137
+ ```typescript
138
+ const errors = sdk.getErrors();
139
+ ```
140
+
141
+ #### `reportError(error, context?)`
142
+ 手动上报错误。
143
+
144
+ ```typescript
145
+ try {
146
+ // some code
147
+ } catch (error) {
148
+ sdk.reportError(error, { customField: 'value' });
149
+ }
150
+ ```
151
+
152
+ #### `reportCustom(type, data)`
153
+ 上报自定义数据。
154
+
155
+ ```typescript
156
+ sdk.reportCustom('custom', { event: 'click', button: 'submit' });
157
+ ```
158
+
159
+ #### `highlightLCPElement(options?)`
160
+ 高亮显示 LCP 元素(用于调试),会在元素周围添加高亮边框并自动滚动到元素位置。
161
+
162
+ ```typescript
163
+ // 使用默认样式高亮
164
+ sdk.highlightLCPElement();
165
+
166
+ // 自定义样式
167
+ sdk.highlightLCPElement({
168
+ borderColor: '#00ff00',
169
+ borderWidth: '5px',
170
+ backgroundColor: 'rgba(0, 255, 0, 0.3)',
171
+ scrollIntoView: true,
172
+ });
173
+ ```
174
+
175
+ #### `removeLCPHighlight()`
176
+ 移除 LCP 元素的高亮效果。
177
+
178
+ ```typescript
179
+ sdk.removeLCPHighlight();
180
+ ```
181
+
182
+ #### `destroy()`
183
+ 销毁 SDK,清理资源并上报剩余数据。
184
+
185
+ ```typescript
186
+ sdk.destroy();
187
+ ```
188
+
189
+ ## 性能指标说明
190
+
191
+ ### FCP (First Contentful Paint)
192
+ 首次内容绘制时间,页面首次渲染文本、图片等内容的时间。
193
+
194
+ ### LCP (Largest Contentful Paint)
195
+ 最大内容绘制时间,页面最大内容元素渲染完成的时间。
196
+
197
+ ### FID (First Input Delay)
198
+ 首次输入延迟,用户首次与页面交互到浏览器响应该交互的时间。
199
+
200
+ ### CLS (Cumulative Layout Shift)
201
+ 累积布局偏移,页面布局稳定性的指标。
202
+
203
+ ### TBT (Total Blocking Time)
204
+ 总阻塞时间,所有长任务(>50ms)的阻塞时间总和。用于衡量页面交互响应性。
205
+
206
+ ### TTFB (Time to First Byte)
207
+ 首字节时间,从请求到接收到第一个字节的时间。
208
+
209
+ ### LCP Element
210
+ LCP 元素路径,记录导致最大内容绘制的 DOM 元素,使用 CSS 选择器路径格式。
211
+
212
+ ### 长任务 (Long Tasks)
213
+ 记录所有超过 50ms 的长任务,包含详细的归因信息:
214
+ - 任务类型(script、layout、style、paint、composite 等)
215
+ - 脚本 URL(如果是脚本执行导致的长任务)
216
+ - DOM 元素信息(如果是 DOM 操作导致的长任务)
217
+ - 容器信息(如果是 iframe 等容器中的任务)
218
+
219
+ ### 其他指标
220
+ - `dns`: DNS 查询时间
221
+ - `tcp`: TCP 连接时间
222
+ - `request`: 请求响应时间
223
+ - `parse`: DOM 解析时间
224
+ - `domContentLoaded`: DOMContentLoaded 事件时间
225
+ - `load`: Load 事件时间
226
+
227
+ ## 数据格式
228
+
229
+ ### 性能指标数据
230
+
231
+ ```json
232
+ {
233
+ "type": "performance",
234
+ "data": {
235
+ "fcp": 1200,
236
+ "lcp": 2500,
237
+ "lcpElement": "body > div#app > img.hero-image",
238
+ "fid": 50,
239
+ "cls": 0.1,
240
+ "tbt": 150,
241
+ "ttfb": 300,
242
+ "dns": 20,
243
+ "tcp": 100,
244
+ "request": 200,
245
+ "parse": 500,
246
+ "domContentLoaded": 1500,
247
+ "load": 3006,
248
+ "longTasks": [
249
+ {
250
+ "startTime": 1000,
251
+ "duration": 120,
252
+ "attribution": [
253
+ {
254
+ "taskType": "script",
255
+ "scriptURL": "https://example.com/heavy-script.js",
256
+ "name": "long-task"
257
+ }
258
+ ]
259
+ },
260
+ {
261
+ "startTime": 2000,
262
+ "duration": 80,
263
+ "attribution": [
264
+ {
265
+ "taskType": "layout",
266
+ "elementPath": "body > div#app > div.container",
267
+ "elementTag": "div",
268
+ "elementClass": "container"
269
+ }
270
+ ]
271
+ }
272
+ ]
273
+ },
274
+ "timestamp": 1234567890,
275
+ "url": "https://example.com",
276
+ "userAgent": "Mozilla/5.0...",
277
+ "appId": "your-app-id",
278
+ "userId": "user-123"
279
+ }
280
+ ```
281
+
282
+ ### 资源数据
283
+
284
+ ```json
285
+ {
286
+ "type": "resource",
287
+ "data": {
288
+ "name": "https://example.com/image.png",
289
+ "type": "image",
290
+ "duration": 500,
291
+ "size": 102400,
292
+ "startTime": 1000,
293
+ "url": "https://example.com/image.png"
294
+ },
295
+ "timestamp": 1234567890,
296
+ "url": "https://example.com",
297
+ "userAgent": "Mozilla/5.0...",
298
+ "appId": "your-app-id",
299
+ "userId": "user-123"
300
+ }
301
+ ```
302
+
303
+ ### 错误数据
304
+
305
+ ```json
306
+ {
307
+ "type": "error",
308
+ "data": {
309
+ "message": "Uncaught TypeError: Cannot read property 'x' of undefined",
310
+ "source": "https://example.com/app.js",
311
+ "lineno": 42,
312
+ "colno": 10,
313
+ "stack": "Error: ...",
314
+ "timestamp": 1234567890,
315
+ "url": "https://example.com",
316
+ "userAgent": "Mozilla/5.0..."
317
+ },
318
+ "timestamp": 1234567890,
319
+ "url": "https://example.com",
320
+ "userAgent": "Mozilla/5.0...",
321
+ "appId": "your-app-id",
322
+ "userId": "user-123"
323
+ }
324
+ ```
325
+
326
+ ## 浏览器兼容性
327
+
328
+ - Chrome 51+
329
+ - Firefox 55+
330
+ - Safari 11+
331
+ - Edge 79+
332
+
333
+ ## 开发
334
+
335
+ ```bash
336
+ # 安装依赖
337
+ npm install
338
+
339
+ # 编译
340
+ npm run build
341
+
342
+ # 开发模式(监听文件变化)
343
+ npm run dev
344
+ ```
345
+
346
+ ## License
347
+
348
+ MIT
349
+
@@ -0,0 +1,35 @@
1
+ import { ErrorInfo } from '../types';
2
+ /**
3
+ * 错误监控器
4
+ */
5
+ export declare class ErrorMonitor {
6
+ private errors;
7
+ /**
8
+ * 开始监控错误
9
+ */
10
+ start(): void;
11
+ /**
12
+ * 监控 JavaScript 错误
13
+ */
14
+ private monitorJSErrors;
15
+ /**
16
+ * 监控 Promise 未捕获的错误
17
+ */
18
+ private monitorUnhandledRejections;
19
+ /**
20
+ * 监控资源加载错误
21
+ */
22
+ private monitorResourceErrors;
23
+ /**
24
+ * 错误回调(可被覆盖)
25
+ */
26
+ private onError;
27
+ /**
28
+ * 获取所有错误
29
+ */
30
+ getErrors(): ErrorInfo[];
31
+ /**
32
+ * 手动上报错误
33
+ */
34
+ reportError(error: Error, context?: Record<string, any>): void;
35
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 错误监控器
3
+ */
4
+ export class ErrorMonitor {
5
+ constructor() {
6
+ this.errors = [];
7
+ }
8
+ /**
9
+ * 开始监控错误
10
+ */
11
+ start() {
12
+ // 监控 JavaScript 错误
13
+ this.monitorJSErrors();
14
+ // 监控 Promise 未捕获的错误
15
+ this.monitorUnhandledRejections();
16
+ // 监控资源加载错误
17
+ this.monitorResourceErrors();
18
+ }
19
+ /**
20
+ * 监控 JavaScript 错误
21
+ */
22
+ monitorJSErrors() {
23
+ window.addEventListener('error', (event) => {
24
+ var _a;
25
+ const errorInfo = {
26
+ message: event.message,
27
+ source: event.filename,
28
+ lineno: event.lineno,
29
+ colno: event.colno,
30
+ stack: (_a = event.error) === null || _a === void 0 ? void 0 : _a.stack,
31
+ timestamp: Date.now(),
32
+ url: window.location.href,
33
+ userAgent: navigator.userAgent,
34
+ };
35
+ this.errors.push(errorInfo);
36
+ this.onError(errorInfo);
37
+ });
38
+ }
39
+ /**
40
+ * 监控 Promise 未捕获的错误
41
+ */
42
+ monitorUnhandledRejections() {
43
+ window.addEventListener('unhandledrejection', (event) => {
44
+ var _a, _b;
45
+ const errorInfo = {
46
+ message: ((_a = event.reason) === null || _a === void 0 ? void 0 : _a.message) || String(event.reason) || 'Unhandled Promise Rejection',
47
+ stack: (_b = event.reason) === null || _b === void 0 ? void 0 : _b.stack,
48
+ timestamp: Date.now(),
49
+ url: window.location.href,
50
+ userAgent: navigator.userAgent,
51
+ };
52
+ this.errors.push(errorInfo);
53
+ this.onError(errorInfo);
54
+ });
55
+ }
56
+ /**
57
+ * 监控资源加载错误
58
+ */
59
+ monitorResourceErrors() {
60
+ document.addEventListener('error', (event) => {
61
+ const target = event.target;
62
+ if (target && (target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK')) {
63
+ const errorInfo = {
64
+ message: `Resource load error: ${target.tagName}`,
65
+ source: target.src || target.href || '',
66
+ timestamp: Date.now(),
67
+ url: window.location.href,
68
+ userAgent: navigator.userAgent,
69
+ };
70
+ this.errors.push(errorInfo);
71
+ this.onError(errorInfo);
72
+ }
73
+ }, true);
74
+ }
75
+ /**
76
+ * 错误回调(可被覆盖)
77
+ */
78
+ onError(errorInfo) {
79
+ // 可以在这里添加自定义错误处理逻辑
80
+ }
81
+ /**
82
+ * 获取所有错误
83
+ */
84
+ getErrors() {
85
+ return this.errors;
86
+ }
87
+ /**
88
+ * 手动上报错误
89
+ */
90
+ reportError(error, context) {
91
+ const errorInfo = {
92
+ message: error.message,
93
+ stack: error.stack,
94
+ timestamp: Date.now(),
95
+ url: window.location.href,
96
+ userAgent: navigator.userAgent,
97
+ ...context,
98
+ };
99
+ this.errors.push(errorInfo);
100
+ this.onError(errorInfo);
101
+ }
102
+ }
@@ -0,0 +1,67 @@
1
+ import { PerformanceMetrics } from '../types';
2
+ /**
3
+ * 性能指标采集器
4
+ */
5
+ export declare class PerformanceCollector {
6
+ private metrics;
7
+ private lcpObserver?;
8
+ private fidObserver?;
9
+ private clsObserver?;
10
+ private fcpObserver?;
11
+ private longTaskObserver?;
12
+ private initialized;
13
+ /**
14
+ * 初始化观察器(需要在页面加载前调用)
15
+ */
16
+ init(): void;
17
+ /**
18
+ * 采集所有性能指标
19
+ */
20
+ collect(): PerformanceMetrics;
21
+ /**
22
+ * 采集 Navigation Timing 指标
23
+ * 使用 PerformanceNavigationTiming API(新标准)
24
+ */
25
+ private collectNavigationTiming;
26
+ /**
27
+ * 降级方案:使用旧的 PerformanceTiming API(已废弃,仅作兼容)
28
+ */
29
+ private collectNavigationTimingLegacy;
30
+ /**
31
+ * 观察 FCP (First Contentful Paint)
32
+ * 使用 PerformanceObserver 更可靠
33
+ */
34
+ private observeFCP;
35
+ /**
36
+ * 初始化所有性能观察器
37
+ * 在 init() 时调用,用于设置 PerformanceObserver
38
+ */
39
+ private collectWebVitals;
40
+ /**
41
+ * 观察 LCP
42
+ */
43
+ private observeLCP;
44
+ /**
45
+ * 观察 FID
46
+ */
47
+ private observeFID;
48
+ /**
49
+ * 观察 CLS
50
+ */
51
+ private observeCLS;
52
+ /**
53
+ * 观察长任务并计算 TBT (Total Blocking Time)
54
+ * TBT = 所有长任务(>50ms)的阻塞时间总和
55
+ * 阻塞时间 = 任务持续时间 - 50ms
56
+ */
57
+ private observeTBT;
58
+ /**
59
+ * 更新 TBT (Total Blocking Time)
60
+ * 从已收集的长任务列表中重新计算 TBT,确保准确性
61
+ */
62
+ private updateTBT;
63
+ /**
64
+ * 销毁观察器
65
+ */
66
+ destroy(): void;
67
+ }