UNPKG

perf-observer-kit

Version:

A lightweight, flexible library for monitoring web performance metrics including Core Web Vitals, resource loading performance, long tasks, and navigation timing.

294 lines 12 kB
import { logger } from '../utils/logger'; import { NetworkMetricsCollector } from '../utils/network-metrics'; import { calculateTime } from '../utils/time'; /** * 导航计时观察者 * 收集页面加载性能指标,包括导航计时、网络信息等 */ export class NavigationObserver { constructor(options) { // 存储收集到的指标数据 this.metrics = {}; // 当前实例状态标记 this.isStarted = false; this.hasCollectedMetrics = false; // 性能观察器和超时控制 this.perfObserver = null; this.timeoutId = null; // 配置常量 this.MAX_WAIT_TIME_MS = 10000; // 10秒超时限制 this.MAX_POLL_ATTEMPTS = 10; // 最大轮询次数 this.POLL_INTERVAL_MS = 200; // 轮询间隔 this.metricsUpdateCallback = options.onUpdate; this.options = { enabled: options.enabled !== undefined ? options.enabled : true, includeRawTiming: options.includeRawTiming || false, onUpdate: options.onUpdate }; logger.debug('创建导航计时观察者', { enabled: this.options.enabled, includeRawTiming: this.options.includeRawTiming }); } start() { // 避免重复启动或已收集过指标 if (!this.canStart()) { return; } this.isStarted = true; this.timeoutId = window.setTimeout(() => { logger.warn(`等待超过${this.MAX_WAIT_TIME_MS}ms,强制收集可用的导航性能数据`); this.collectNavigationData(true); // 强制收集 this.cleanupResources(); }, this.MAX_WAIT_TIME_MS); // 尝试使用PerformanceObserver(现代浏览器首选方法) if (typeof PerformanceObserver !== 'undefined') { try { this.perfObserver = new PerformanceObserver((entries) => { const navigationEntries = entries.getEntries() .filter(entry => entry.entryType === 'navigation'); if (navigationEntries.length === 0) { return; } const latestEntry = navigationEntries[navigationEntries.length - 1]; if (this.isLoadEventComplete(latestEntry)) { this.processNavigationData(latestEntry); this.cleanupResources(); } }); this.perfObserver.observe({ type: 'navigation', buffered: true }); logger.debug('开始使用PerformanceObserver监测导航性能'); } catch (error) { logger.error('PerformanceObserver初始化失败', error); this.startWithTraditionalMethod(); } } else { this.startWithTraditionalMethod(); } } stop() { logger.info('停止导航性能数据收集'); this.isStarted = false; this.cleanupResources(); } /** * 获取收集到的导航性能指标 */ getMetrics() { return this.metrics; } /** * 检查是否可以开始收集指标 */ canStart() { if (!this.options.enabled) { logger.debug('导航计时观察者已禁用'); return false; } if (this.isStarted) { logger.debug('导航计时观察者已处于活动状态'); return false; } if (this.hasCollectedMetrics) { logger.debug('导航计时观察者已收集过指标'); return false; } return true; } /** * 清理超时定时器 */ clearTimeout() { if (this.timeoutId !== null) { window.clearTimeout(this.timeoutId); this.timeoutId = null; } } /** * 清理所有资源 */ cleanupResources() { // 清理性能观察者 if (this.perfObserver) { this.perfObserver.disconnect(); this.perfObserver = null; } // 清理定时器 this.clearTimeout(); } /** * 使用传统方法开始收集导航性能数据 */ startWithTraditionalMethod() { logger.info('使用传统方法收集导航性能数据'); if (document.readyState === 'complete') { this.pollForNavigationData(); } else { window.addEventListener('load', () => { setTimeout(() => this.pollForNavigationData(), 100); }); } } /** * 通过轮询方式检查并获取导航性能数据 */ pollForNavigationData(attemptCount = 0) { // 超过最大尝试次数,强制收集 if (attemptCount > this.MAX_POLL_ATTEMPTS) { logger.warn(`轮询${this.MAX_POLL_ATTEMPTS}次后未获取到完整数据,强制收集可用数据`); this.collectNavigationData(true); return; } // 尝试获取导航性能条目 const navEntry = this.getLatestNavigationEntry(); if (!navEntry) { logger.warn('未找到导航性能条目'); return; } // 检查数据是否完整 if (this.isLoadEventComplete(navEntry)) { this.collectNavigationData(); this.clearTimeout(); } else { // 延迟后再次尝试 setTimeout(() => this.pollForNavigationData(attemptCount + 1), this.POLL_INTERVAL_MS); } } /** * 获取最新的导航性能条目 */ getLatestNavigationEntry() { if (!('performance' in window) || !('getEntriesByType' in performance)) { return null; } const entries = performance.getEntriesByType('navigation'); if (!entries || entries.length === 0) { return null; } return entries[entries.length - 1]; } /** * 收集导航计时数据 */ collectNavigationData(forceProcess = false) { const navEntry = this.getLatestNavigationEntry(); if (!navEntry) { logger.warn('无法收集导航性能数据:未找到导航性能条目'); return; } // 检查loadEvent数据是否完整 if (!forceProcess && !this.isLoadEventComplete(navEntry)) { logger.warn('导航性能数据不完整:load事件未完成'); return; } this.processNavigationData(navEntry); } /** * 判断load事件是否已完成 */ isLoadEventComplete(entry) { // 检查loadEvent相关属性是否有效 const hasValidStart = typeof entry.loadEventStart === 'number' && entry.loadEventStart > 0; const hasValidEnd = typeof entry.loadEventEnd === 'number' && entry.loadEventEnd > 0; const isComplete = hasValidStart && hasValidEnd && entry.loadEventEnd >= entry.loadEventStart; if (!isComplete) { logger.debug('load事件数据不完整', { loadEventStart: entry.loadEventStart, loadEventEnd: entry.loadEventEnd }); } return isComplete; } /** * 处理导航性能条目,提取和计算指标 */ processNavigationData(entry) { var _a, _b, _c, _d; // 计算性能指标 const timingMetrics = { // 资源加载阶段 unloadTime: calculateTime(entry.unloadEventEnd, entry.unloadEventStart), redirectTime: calculateTime(entry.redirectEnd, entry.redirectStart), // Service Worker阶段 serviceWorkerTime: entry.workerStart > 0 ? calculateTime(entry.workerStart, entry.fetchStart) : undefined, // DNS/TCP/SSL阶段 appCacheTime: calculateTime(entry.domainLookupStart, entry.fetchStart), dnsTime: calculateTime(entry.domainLookupEnd, entry.domainLookupStart), tcpTime: calculateTime(entry.connectEnd, entry.connectStart), sslTime: entry.secureConnectionStart > 0 ? calculateTime(entry.connectEnd, entry.secureConnectionStart) : undefined, // 请求/响应阶段 requestTime: calculateTime(entry.responseStart, entry.requestStart), ttfb: calculateTime(entry.responseStart, entry.requestStart), resourceFetchTime: calculateTime(entry.responseStart, entry.fetchStart), responseTime: calculateTime(entry.responseEnd, entry.responseStart), // DOM处理阶段 initDOMTime: calculateTime(entry.domInteractive, entry.responseEnd), processingTime: calculateTime(entry.domComplete, entry.domInteractive), contentLoadTime: calculateTime(entry.domContentLoadedEventEnd, entry.domContentLoadedEventStart), // 页面加载完成指标 domContentLoaded: calculateTime(entry.domContentLoadedEventEnd, entry.startTime), loadEventDuration: calculateTime(entry.loadEventEnd, entry.loadEventStart), frontEndTime: calculateTime(entry.loadEventEnd, entry.responseEnd), totalLoadTime: calculateTime(entry.loadEventEnd, entry.startTime) }; // 获取网络信息 const networkMetrics = NetworkMetricsCollector.getNetworkInformation(); // 获取当前页面URL const pageUrl = typeof window !== 'undefined' ? window.location.href : entry.name; logger.info('导航性能指标', { url: entry.name, // 移除查询参数 ttfb: `${((_a = timingMetrics.ttfb) === null || _a === void 0 ? void 0 : _a.toFixed(2)) || 0}ms`, domContentLoaded: `${((_b = timingMetrics.domContentLoaded) === null || _b === void 0 ? void 0 : _b.toFixed(2)) || 0}ms`, loadEventDuration: `${((_c = timingMetrics.loadEventDuration) === null || _c === void 0 ? void 0 : _c.toFixed(2)) || 0}ms`, totalLoadTime: `${((_d = timingMetrics.totalLoadTime) === null || _d === void 0 ? void 0 : _d.toFixed(2)) || 0}ms` }); // 整合所有指标 this.metrics = { ...timingMetrics, url: pageUrl, metric: 'navigation', networkMetrics, timestamp: Date.now(), complete: true }; // 根据配置添加原始计时数据 if (this.options.includeRawTiming) { this.metrics.rawTiming = { navigationStart: entry.startTime, unloadEventStart: entry.unloadEventStart, unloadEventEnd: entry.unloadEventEnd, redirectStart: entry.redirectStart, redirectEnd: entry.redirectEnd, fetchStart: entry.fetchStart, domainLookupStart: entry.domainLookupStart, domainLookupEnd: entry.domainLookupEnd, connectStart: entry.connectStart, connectEnd: entry.connectEnd, secureConnectionStart: entry.secureConnectionStart, requestStart: entry.requestStart, responseStart: entry.responseStart, responseEnd: entry.responseEnd, domInteractive: entry.domInteractive, domContentLoadedEventStart: entry.domContentLoadedEventStart, domContentLoadedEventEnd: entry.domContentLoadedEventEnd, domComplete: entry.domComplete, loadEventStart: entry.loadEventStart, loadEventEnd: entry.loadEventEnd, type: entry.type, redirectCount: entry.redirectCount, }; } // 调用回调函数 this.metricsUpdateCallback(this.metrics); this.hasCollectedMetrics = true; } } //# sourceMappingURL=navigation-timing.js.map