从零构建前端监控框架:原理与实践

在当今的互联网应用开发中,前端性能和用户体验的重要性不言而喻。为了确保前端应用的高质量运行,前端监控工具成为了开发者的得力助手。本文将深入剖析如何构建一个类似 monitoring-tool 的前端监控框架,包括其实现原理以及详细的构建步骤。

一、前端监控的重要性

在复杂的前端应用中,性能问题可能导致页面加载缓慢、用户操作卡顿,严重影响用户体验,进而导致用户流失。据统计,页面加载时间每延长 1 秒,用户流失率可能增加 10% 以上。错误未及时发现和处理可能引发应用崩溃或功能异常,例如未捕获的 JavaScript 错误可能导致整个页面交互失效。通过前端监控,我们可以实时了解应用的运行状态,及时发现并解决问题,提升应用的稳定性和性能,为用户提供更流畅的体验。

二、实现原理

1. 数据采集

性能指标采集

  • 利用 Performance API
    Performance API 提供了丰富的性能数据,通过 performance.timing 可以精确记录页面加载各阶段的时间。例如,navigationStart 表示浏览器开始卸载当前页面的时间,responseStart 表示浏览器开始接收响应数据的时间,两者的差值可得出服务器响应时间。监听 load 事件可以获取页面完全加载的时间,DOMContentLoaded 事件则表示 DOM 树构建完成的时间,用于确定页面关键加载节点。
  • 计算页面渲染时间
    通过 performance.getEntriesByType('paint') 获取绘制相关的性能条目,计算首次内容绘制(FCP)、最大内容绘制(LCP)等时间,帮助了解页面从开始渲染到可交互的时长,找出可能影响用户体验的瓶颈。

错误信息采集

  • JavaScript 错误捕获
    借助 window.onerror 捕获各类 JavaScript 错误,包括语法错误、运行时错误等,获取错误信息、发生错误的文件路径、行号、列号以及错误堆栈,方便快速定位问题根源。
  • Promise 错误处理
    监听 unhandledrejection 事件,确保 Promise 中未处理的错误不会被遗漏,提升错误捕获的全面性。
  • 资源加载错误
    针对图片或脚本加载失败等资源加载错误,通过监听 imgscript 等元素的 error 事件,及时记录错误情况,避免因资源问题影响页面功能。

用户行为采集

  • 交互事件监听
    广泛监听页面交互事件,如 clickscrollinput 等。当用户点击按钮时,记录点击元素的标签名、时间和相关上下文(如元素 ID、类名),深入了解用户操作流程。
  • 滚动事件跟踪
    跟踪滚动事件,记录用户滚动的位置和时间,分析用户在页面上的浏览深度和行为模式,为优化页面布局和内容提供数据支持。

2. 数据上报

  • 创建上报请求
    采集到数据后,将其格式化为适合传输的 JSON 格式,然后利用 XMLHttpRequestfetch 创建 POST 请求,将数据发送到指定的后端接口,确保数据准确、及时地传输。
  • 设置上报时机和策略
    • 即时上报:对于关键数据(如错误信息)采用即时上报,确保数据及时送达后端,但可能增加网络请求频率。
    • 定时批量上报:对于性能指标和用户行为数据,可采用定时批量上报,例如每隔 5 秒集中发送一次数据,平衡性能与数据实时性。
    • 网络缓存机制:考虑网络状况,在网络不稳定时,利用 localStorageIndexedDB 缓存数据,待网络恢复后自动重新上报,保证数据不丢失。

3. 数据处理和分析(后端部分)

  • 接收和存储数据
    后端接口接收前端上报的数据后,存储到数据库中。对于结构化数据可使用 MySQL,对于非结构化数据可使用 MongoDB,为后续分析提供持久化的数据支持。
  • 数据清洗和预处理
    清理无效或错误的数据(如重复数据、格式错误的数据),确保分析数据的准确性。解析时间格式为统一的时间戳,便于后续计算和比较,同时进行数据统计,如计算性能指标的平均值、最大值和最小值等。
  • 数据分析和可视化
    通过数据分析工具和算法,深入挖掘性能数据背后的规律。例如,分析不同时间段或页面的性能趋势,找出性能波动的原因。将分析结果以直观的图表形式展示,如柱状图展示不同页面的加载时间对比,折线图呈现性能指标随时间的变化趋势,帮助开发者快速理解和定位问题。

4. 可扩展性设计

  • 插件机制
    提供插件接口,允许开发者根据项目特定需求扩展监控功能。插件应包含初始化函数和数据处理函数,例如编写插件采集业务相关的自定义数据(如用户购物流程中的关键操作信息),或者对采集到的数据进行个性化处理后再上报。
  • 配置灵活性
    通过配置文件或初始化参数,让开发者能够轻松调整监控范围(如仅监控关键页面或功能模块)、自定义上报地址(适应不同的后端部署环境)、灵活设置数据采集频率(根据应用的实际情况平衡性能和数据完整性)。

5. 与前端框架集成

若针对特定前端框架(如 Vue、React)设计,可将监控功能封装为插件或指令。在 Vue 中,利用生命周期钩子 mounteddestroyed 嵌入数据采集逻辑;在 React 中,通过自定义 Hooks 实现。与框架的路由系统紧密结合,实现页面切换时的性能监控和数据上报,提供全面的应用监控解决方案。


6. 安全性考虑

  • 数据加密
    在数据上报过程中,对敏感信息(如用户身份标识、业务数据等)使用 AES 等加密算法进行加密,防止数据在传输过程中被窃取或篡改,确保数据安全。
  • 权限管理
    后端接口实施严格的权限验证,采用 JWT 令牌认证,只有经过授权的前端应用才能成功上报数据,防止非法数据注入,保障系统的安全性和稳定性。

三、构建步骤

1. 项目初始化

创建项目文件夹,使用 npm init 生成 package.json 文件,初始化项目基本信息,如项目名称、版本号和依赖管理等。

2. 依赖安装

根据项目需求,安装必要的依赖库。例如,使用 performance-now 获取更精确的时间戳,axios 处理 HTTP 请求,typescript 进行类型安全开发,并安装相应的类型定义文件。

3. 性能指标采集模块

封装 Performance API 的使用,创建函数获取页面加载时间、资源加载时间等关键性能指标:

1
2
3
4
function getPageLoadTime() {
const { performance } = window;
return performance.timing.loadEventEnd - performance.timing.navigationStart;
}

监听页面加载事件,在合适时机调用采集函数并存储数据:

1
2
3
window.addEventListener('load', () => {
const loadTime = getPageLoadTime(); // 存储数据以便后续上报
});

4. 错误信息采集模块

实现 window.onerror 事件监听器,捕获 JavaScript 错误并记录详细信息:

1
2
3
4
5
6
7
8
9
10
window.onerror = function (message, source, lineno, colno, error) {
const errorData = {
message,
source,
lineno,
colno,
stack: error && error.stack
};
// 处理错误数据,如存储或上报
};

针对 Promise 错误和资源加载错误,分别添加相应的监听器:

1
2
3
4
5
6
7
window.addEventListener('unhandledrejection', (event) => {
const errorData = {
message: event.reason.message,
stack: event.reason.stack
};
// 处理Promise错误数据
});

5. 用户行为采集模块

为常见的用户交互事件添加事件监听器,记录用户行为数据:

1
2
3
4
5
6
7
8
9
10
document.addEventListener('click', (event) => {
const behaviorData = {
type: 'click',
target: event.target.tagName,
time: new Date().getTime(),
targetId: event.target.id,
targetClass: event.target.className
};
// 处理行为数据
});

6. 数据上报模块

封装数据上报函数,将采集到的数据格式化为 JSON 格式,并使用 fetchaxios 发送 POST 请求到后端接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function reportData(data) {
try {
const response = await fetch('your-backend-api-url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN' // 权限验证令牌
},
body: JSON.stringify(data)
});
const result = await response.json();
// 处理上报结果
} catch (error) {
// 处理上报失败情况,存入缓存
localStorage.setItem('monitoringData', JSON.stringify(data));
}
}

7. 后端搭建(简要概述)

选择合适的后端技术栈,如 Node.js + Express:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken'); // 用于权限验证

// 解析JSON请求体
app.use(bodyParser.json());

// 权限验证中间件
function verifyToken(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(403).send('No token provided');
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) return res.status(401).send('Invalid token');
req.user = decoded;
next();
});
}

app.post('/api/report', verifyToken, (req, res) => {
const data = req.body;
// 将数据存储到数据库(如MySQL或MongoDB)
console.log('Received data:', data);
res.send('Data received successfully');
});

const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});

8. 可扩展性设计

定义插件接口,规定插件的结构和功能规范:

1
2
3
4
5
// 插件接口定义
interface MonitorPlugin {
init(config: any): void;
processData(data: any): any;
}

在主监控模块中,提供加载插件的机制:

1
2
3
4
5
6
7
8
9
10
class Monitor {
plugins: MonitorPlugin[] = [];
addPlugin(plugin: MonitorPlugin) {
this.plugins.push(plugin);
plugin.init(this.config);
}
processWithPlugins(data: any) {
return this.plugins.reduce((acc, plugin) => plugin.processData(acc), data);
}
}

9. 安全性增强

对于数据加密,使用 AES 算法对敏感数据进行加密处理:

1
2
3
4
5
6
7
const crypto = require('crypto');
function encryptData(data, key) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}

10. 测试与优化

编写单元测试用例,覆盖各个功能模块,使用 Jest 测试框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
test('getPageLoadTime should return correct value', () => {
const expectedLoadTime = 1000;
// 模拟performance.timing.loadEventEnd和navigationStart
Object.defineProperty(window.performance, 'timing', {
value: {
loadEventEnd: expectedLoadTime + 500,
navigationStart: 500
},
writable: true
});
const result = getPageLoadTime();
expect(result).toBe(expectedLoadTime);
});

进行性能优化,避免监控代码对应用性能产生过大影响,例如优化数据采集的频率,减少不必要的事件监听和计算。

11. 文档编写

撰写详细的文档,包括项目的使用方法、API 文档、配置说明等。例如,说明如何初始化监控实例、配置上报地址、添加自定义插件等,方便其他开发者使用和扩展该监控框架。


通过以上步骤,我们可以逐步构建一个功能完备、可扩展且安全的前端监控框架,为前端应用的性能优化和错误排查提供有力支持。在实际应用中,可根据具体需求进一步定制和完善该框架,以满足不同项目的监控需求。欢迎访问 monitoring-tool 查看更多实现细节和示例代码

探索微前端架构:多种实现方式与实践思考

在当今的前端开发领域,微前端架构正逐渐成为热门话题。它借鉴了微服务的理念,将原本庞大的单体前端应用拆解为多个小型前端应用,并能让它们协同工作,就如同一个完整的应用一样。今天,咱们就一起来深入探讨微前端架构的几种常见实现方式以及在实际应用中需要考虑的方方面面。

一、微前端架构概述

微前端架构的核心思想,就是把 Web 应用从单一的单体应用转变成多个小型前端应用聚合而成的形态。这些小型前端应用具备独立运行、独立开发以及独立部署的能力,而且还能共享组件,实现并行开发。这里所说的前端应用是基于前后端分离的单应用页面,这是谈论微前端的基础前提。

前端国际化全流程解决方案

背景

进入项目组,接到的需求是vue框架的项目实现国际化,实现中英文版本的切换。
目前常用的前端国际化实现方式是使用配置文件的方式,使用的是同一套界面,根据语言的不同加载对应的配置文件。

而实现这一功能,面对的问题是需要将项目中的中文抽离出中文的语言包,并将其翻译输出英文语言包,以及项目中中文替换成语言包中的变量,工作量大且容易出错。
因此,我们需要一种更智能、更高效的解决方案来应对这一挑战,经过深入研究各种技术方案,是不是可以通过插件实现自动识别代码中的可翻译字符串、生成翻译文件并同步不同语言这一繁琐的过程呢 – 于是一个搞定项目国际化的插件就此诞生。

vue2老项目vite升级改造

前言

Vite是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

Vite 意在提供开箱即用的配置,同时它的 插件 APIJavaScript API 带来了高度的可扩展性,并有完整的类型支持。

vite包含如下特点:

  • 光速启动
  • 热模块替换
  • 按需编译

本次在开发环境增加vite的启动方式,提升开发效率,在生产环境保持原有模式。

如何做组件库的单元测试

前言

在我们开发完一个组件库的后,在做单元测试时,代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到80%或 90%。于是乎,测试人员费尽心思设计案例覆盖代码。用代码覆盖率来衡量,有利也有有弊。

首先,让我们先来了解一下所谓的“代码覆盖率”。我找来了所谓的定义:

代码覆盖率 = 代码的覆盖程度,一种度量方式。

关于如何开发组件库,可看这篇:

如何基于vue开发ui组件库(heaven-ui)

如何搭建一个自己的脚手架

脚手架

搭建脚手架的目的就是快速的搭建项目的基本结构并提供项目规范和约定。目前日常工作中常用的脚手架有 vue-cli、create-react-app、angular-cli 等等,都是通过简单的初始化命令,完成内容的快速构建。

其实我们也可以用git clone url来新建(复制)项目,再 low 一点的方法就是复制粘贴整个文件夹,一样也能达到初始化的目的。

脚手架的本质也是从远程下载一个模板来进行一个新项目,但是脚手架可是高级版的克隆,它主要是提供了交互式的命令让我们可以动态的更改模板,然后用一句命令就可以实现其他内置依赖的初始化,方便了多人协作,不需要将文件传来传去。

接下来我们就开始实现一个简易版的脚手架heaven-cli(可自行命名),目标是实现一个heaven init template-name project-name这样的命令,废话少说,开始进入正题吧!

pm2的基本使用

PM2

PM2 是一个带有负载均衡功能的 Node 应用进程管理器。

主要特性:

  • 内建负载均衡(使用 Node cluster 集群模块)
  • 后台运行
  • 0 秒停机重载
  • 具有 Ubuntu 和 CentOS 的启动脚本
  • 停止不稳定的进程(避免无限循环)
  • 控制台检测
  • 提供 HTTP API
  • 远程控制和实时的接口 API(Nodejs 模块,允许和 PM2 进程管理器交互)

Git提交规范保护

Git 规范

Git 作为现在最流行的分布式管理工具,基本上是每个团队的必备。

Git Commit格式校验

  • 准备commitlint/cli用于格式校验
  • 准备husky用于git提交代码时触发校验

husky

husky 是创建 git 客户端 hooks 的神器。

目前最新版本是 7.*,文档在这里(opens new window)

初始化 husky 只需要三步。首先要安装:

1
$ npm i husky --save-dev

执行下面的脚本,会初始化一个 .husky 目录:

1
$ npx husky install

初始化后,就可以添加 hook 了。

添加一个 commit-msg 钩子,并指定一个默认 shell:

1
$ npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

现在在执行 git commit 之前,就会执行上面指定的 shell。

  
 git

理解js中的new

  • 新生成了一个对象
  • 链接到原型
  • 绑定 this
  • 返回新对象

在调用 new 的过程中会发生以上四件事情,我们也可以试着来自己实现一个 new

1
2
3
4
5
6
7
8
9
10
11
12
13
function create() {
// 创建一个空的对象
let obj = new Object()
// 获得构造函数
let Con = [].shift.call(arguments)
// 链接到原型
obj.__proto__ = Con.prototype
// 绑定 this,执行构造函数
let result = Con.apply(obj, arguments)
// 确保 new 出来的是个对象
return typeof result === 'object' ? result : obj
}


:D 一言句子获取中...