目录
- 前端sdk 之小满np
- 安装
- 01 搭建环境
- 01-项目目录
- 01-2 依赖包
- 01-3 rollup.config.js
- 01-4 tsconfig.json 28行
- 01-5 package.json
- 01-6 src / core / index.ts
- 01-7打包效果
- 02 初始化 Tracher
- 02-1 core / index.ts
- 02-2 types/ index.ts
- 03 重写history事件 监听history | hash 路由等跳转操作事件
- 03-1 src / utils / pv.ts
- 03-2 src / core / index.ts
- 03-3 npm run build 之后 创建 index.html
- 03-4 效果
- 04 添加用户标识
- 04-1 src / code /index.ts
- 05 上报 navigator.sendBeacon
- 05-1 src / core / index.ts
- 05-2 写一个接口 测试埋点
- 05-2-2 安装依赖
- 05-2-2 接口 express / index.js
- 05-2-3 启动后端服务
- 05-2-4 打开index.html
- 05-2-4 测试埋点
- 06 dom 上报
- 06-1 src / core / index.ts
- 06-2 npm run build 之后 index.html 修改与添加事件
- 06-3 效果
- 07 js报错上报 error 事件 promise报错 unhandledrejection
- 07-1 src / core / index.ts
- 07-2 index.html
前端sdk 之小满np
安装
npm init -y
tsc --init
01 搭建环境
01-项目目录
- 介绍
- src / types 定义类型
- src / core 核心代码
- src / utils / pv 工具函数
- PV:页面访问量,即PageView,用户每次对网站的访问均被记录
- 技术架构
- 埋点就是 数据采集-数据处理-数据分析和挖掘,如用户停留时间,用户哪个按钮点的多,等
- 技术架构使用ts + rollup
01-2 依赖包
npm install rollup -D
npm install rollup-plugin-dts -D
npm install rollup-plugin-typescript2 -D
npm install typescript -D
01-3 rollup.config.js
import ts from "rollup-plugin-typescript2";
import path from "path"
import dts from "rollup-plugin-dts";
export default [
{
//入口文件
input: "./src/core/index.ts",
output: [
//打包esModule
{
file: path.resolve(__dirname, "./dist/index.esm.js"),
format: "es",
},
//打包common js
{
file: path.resolve(__dirname, "./dist/index.cjs.js"),
format: "cjs",
},
//打包 AMD CMD UMD
{
file: path.resolve(__dirname, "./dist/index.js"),
format: "umd",
name: "tracker",
},
// {
// input: "./src/core/index.ts",
// file: path.resolve(__dirname, "./dist/index.js"),
// format: "umd",
// name: "tracker",
// },
],
//配置ts
plugins: [ts()],
},
{
//打包声明文件
input: "./src/core/index.ts",
output: {
file: path.resolve(__dirname, "./dist/index.d.ts"),
format: "es",
},
plugins: [dts()],
},
];
01-4 tsconfig.json 28行
/* Modules */
"module": "ESNext",
01-5 package.json
- 配置脚本、还有module、browser、keywords、files等
{
"name": "vue-sdk",
"version": "1.0.5",
"description": "",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"browser":"dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rollup -c"
},
"keywords": ["前端","埋点","tracker"],
"author": "",
"files": ["dist"],
"license": "ISC",
"devDependencies": {
"rollup": "^2.76.0",
"rollup-plugin-dts": "^4.2.2",
"rollup-plugin-typescript2": "^0.32.1",
"typescript": "^4.7.4"
}
}
01-6 src / core / index.ts
export const a = 1000;
console.log("a", a);
// "type":"module",
01-7打包效果
- cjs
- …
02 初始化 Tracher
02-1 core / index.ts
import { DefaultOptons, TrackerConfig, Options } from "../types/index";
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
}
private initDef(): DefaultOptons {
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
}
02-2 types/ index.ts
/**
* @requestUrl 接口地址
* @historyTracker history上报
* @hashTracker hash上报
* @domTracker 携带Tracker-key 点击事件上报
* @sdkVersionsdk版本
* @extra透传字段
* @jsError js 和 promise 报错异常上报
*/
export interface DefaultOptons {
uuid: string | undefined,
requestUrl: string | undefined,
historyTracker: boolean,
hashTracker: boolean,
domTracker: boolean,
sdkVersion: string | number,
extra: Record<string, any> | undefined,
jsError:boolean
}
//必传参数 requestUrl
export interface Options extends Partial<DefaultOptons> {
requestUrl: string,
}
//版本
export enum TrackerConfig {
version = '1.0.0'
}
//上报必传参数
export type reportTrackerData = {
[key: string]: any,
event: string,
targetKey: string
}
03 重写history事件 监听history | hash 路由等跳转操作事件
- PV:页面访问量,即PageView,用户每次对网站的访问均被记录
- 主要监听了 history 和 hash
- history API go back forward pushState replaceState
- history 无法通过 popstate 监听 pushState replaceState 只能重写其函数 在utils/pv
- hash 使用hashchange 监听
03-1 src / utils / pv.ts
- 重写history事件,以便通过popstate 监听 pushState replaceState
export const createHistoryEvnent = <T extends keyof History>(type: T): () => any => {
const origin = history[type];
return function (this: any) {
const res = origin.apply(this, arguments)
var e = new Event(type)
window.dispatchEvent(e)
return res;
}
}
03-2 src / core / index.ts
- 引入 createHistoryEvnent 并且初始化之
import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv"
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
this.initTracker()
}
private initDef(): DefaultOptons {
// 修改 history路由 以便于记录埋点事件
window.history['pushState'] = createHistoryEvnent('pushState')
window.history['replaceState'] = createHistoryEvnent('replaceState')
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
private captureEvents <T>(mouseEventList:string[],targetKey:string,data?:T) {
mouseEventList.forEach(event=>{
window.addEventListener(event,()=>{
console.log('监听到了');
})
})
}
private initTracker(){
// 若是用户 开启 historyTracker 循环监听用户的事件
if ( this.data.historyTracker ) {
// 'history-pv' - 此参数需要与后台进行协商定义
this.captureEvents(['pushState','replaceState','popstate'],'history-pv')
}
if (this.data.hashTracker) {
// 'hash-pv' - 此参数需要与后台进行协商定义
this.captureEvents(['hashChange'],'hash-pv')
}
}
}
03-3 npm run build 之后 创建 index.html
- 引入打包后的文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./dist/index.js"></script>
<script>
new tracker({
historyTracker: true,
});
</script>
</body>
</html>
03-4 效果
history.pushState('aaa','','/a'
04 添加用户标识
- UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客
- 用户唯一表示 可以在登录之后通过接口返回的id 进行设置值 提供了setUserId
04-1 src / code /index.ts
import { DefaultOptons, TrackerConfig, Options } from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
this.initTracker();
}
private initDef(): DefaultOptons {
// 修改 history路由 以便于记录埋点事件
window.history["pushState"] = createHistoryEvnent("pushState");
window.history["replaceState"] = createHistoryEvnent("replaceState");
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
// uuid 用户的唯一标识
public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
this.data.uuid = uuid;
}
// 用户的自定义参数
public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
this.data.extra = extra;
}
private captureEvents<T>(
mouseEventList: string[],
targetKey: string,
data?: T
) {
mouseEventList.forEach((event) => {
window.addEventListener(event, () => {
console.log("监听到了");
});
});
}
private initTracker() {
// 若是用户 开启 historyTracker 循环监听用户的事件
if (this.data.historyTracker) {
// 'history-pv' - 此参数需要与后台进行协商定义
this.captureEvents(
["pushState", "replaceState", "popstate"],
"history-pv"
);
}
if (this.data.hashTracker) {
// 'hash-pv' - 此参数需要与后台进行协商定义
this.captureEvents(["hashChange"], "hash-pv");
}
}
}
05 上报 navigator.sendBeacon
- 为什么要使用这个去上报
- 这个上报的机制 跟 XMLHttrequest 对比 navigator.sendBeacon 即使页面关闭了 也会完成请求 而XMLHTTPRequest 不一定
05-1 src / core / index.ts
- reportTracker
- sendTracker
import {
DefaultOptons,
TrackerConfig,
Options,
reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
this.initTracker();
}
private initDef(): DefaultOptons {
// 修改 history路由 以便于记录埋点事件
window.history["pushState"] = createHistoryEvnent("pushState");
window.history["replaceState"] = createHistoryEvnent("replaceState");
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
// uuid 用户的唯一标识
public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
this.data.uuid = uuid;
}
// 用户的自定义参数
public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
this.data.extra = extra;
}
private captureEvents<T>(
mouseEventList: string[],
targetKey: string,
data?: T
) {
mouseEventList.forEach((event) => {
window.addEventListener(event, () => {
// console.log("监听到了");
this.reportTracker({
event,
targetKey,
data,
});
});
});
}
private initTracker() {
// 若是用户 开启 historyTracker 循环监听用户的事件
if (this.data.historyTracker) {
// 'history-pv' - 此参数需要与后台进行协商定义
// this.captureEvents(
// ["pushState", "replaceState", "popstate"],
// "history-pv"
// );
this.captureEvents(["pushState"], "history-pv");
this.captureEvents(["replaceState"], "history-pv");
this.captureEvents(["popstate"], "history-pv");
}
if (this.data.hashTracker) {
// 'hash-pv' - 此参数需要与后台进行协商定义
this.captureEvents(["hashChange"], "hash-pv");
}
}
// 手动上报
public sendTracker<T extends reportTrackerData>(data: T) {
this.reportTracker(data);
}
// 上报用户操作 - 监听到的时候 调用
private reportTracker<T>(data: T) {
// 拿到上报的data数据
const params = Object.assign(this.data, data, {
time: new Date().getTime(),
});
// 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
let headers = {
type: "application/x-www-form-urlencoded",
};
let blob = new Blob([JSON.stringify(params)], headers);
navigator.sendBeacon(this.data.requestUrl, blob);
}
}
05-2 写一个接口 测试埋点
-
npm run build
前端先打包后,进行测试
05-2-2 安装依赖
- 创建后端的express文件目录,安装以下依赖
npm i express -S
npm i cros -s
05-2-2 接口 express / index.js
const express = require("express")
const cors = require("cors")
const app = express()
app.use(cors())
app.use(express.urlencoded({extended:false}))
app.post("/tracker",(req,res)=>{
console.log('req',req.body);
res.send(200)
})
app.listen(9000,()=>{
console.log('监听服务端口 9000');
})
05-2-3 启动后端服务
node index.js
05-2-4 打开index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./dist/index.js"></script>
<script>
new tracker({
requestUrl:"http://localhost:9000/tracker",
historyTracker: true,
});
</script>
</body>
</html>
05-2-4 测试埋点
history.pushState('aaa','','/a')
- 效果
06 dom 上报
- 主要是给需要监听的元素添加一个属性 用来区分是否需要监听 target-key
06-1 src / core / index.ts
import {
DefaultOptons,
TrackerConfig,
Options,
reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
// dom节点操作 上报-01
const MouseEventList: string[] = [
"click",
"dblclick",
"contextmenu",
"mousedown",
"mouseup",
"mouseenter",
"mouseout",
"mouseover",
];
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
this.initTracker();
}
private initDef(): DefaultOptons {
// 修改 history路由 以便于记录埋点事件
window.history["pushState"] = createHistoryEvnent("pushState");
window.history["replaceState"] = createHistoryEvnent("replaceState");
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
// uuid 用户的唯一标识
public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
this.data.uuid = uuid;
}
// 用户的自定义参数
public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
this.data.extra = extra;
}
private captureEvents<T>(
mouseEventList: string[],
targetKey: string,
data?: T
) {
mouseEventList.forEach((event) => {
window.addEventListener(event, () => {
// console.log("监听到了");
this.reportTracker({
event,
targetKey,
data,
});
});
});
}
private initTracker() {
// 若是用户 开启 historyTracker 循环监听用户的事件
if (this.data.historyTracker) {
this.captureEvents(["pushState"], "history-pv");
this.captureEvents(["replaceState"], "history-pv");
this.captureEvents(["popstate"], "history-pv");
}
if (this.data.hashTracker) {
// 'hash-pv' - 此参数需要与后台进行协商定义
this.captureEvents(["hashChange"], "hash-pv");
}
// dom 节点上报 -03
if (this.data.domTracker) {
console.log('11');
this.targetKeyReport();
}
// if (this.data.jsError) {
// this.jsError();
// }
}
// 手动上报
public sendTracker<T extends reportTrackerData>(data: T) {
this.reportTracker(data);
}
// 上报用户操作 - 监听到的时候 调用
private reportTracker<T>(data: T) {
// 拿到上报的data数据
const params = Object.assign(this.data, data, {
time: new Date().getTime(),
});
// 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
let headers = {
type: "application/x-www-form-urlencoded",
};
let blob = new Blob([JSON.stringify(params)], headers);
navigator.sendBeacon(this.data.requestUrl, blob);
}
// dom 节点上报 -02
private targetKeyReport() {
MouseEventList.forEach((event) => {
window.addEventListener(event, (e) => {
const target = e.target as HTMLElement;
const targetKey = target.getAttribute("target-key");
// 如果有上报属性,则需要上报
if (targetKey) {
this.reportTracker({
event,
targetKey,
});
}
});
});
}
}
06-2 npm run build 之后 index.html 修改与添加事件
- domTracker:true
- 添加上报
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./dist/index.js"></script>
<button target-key="btn">添加上报</button>
<button>无添加</button>
<script>
new tracker({
requestUrl: "http://localhost:9000/tracker",
historyTracker: true,
domTracker:true
});
</script>
</body>
</html>
06-3 效果
07 js报错上报 error 事件 promise报错 unhandledrejection
07-1 src / core / index.ts
import {
DefaultOptons,
TrackerConfig,
Options,
reportTrackerData,
} from "../types/index";
import { createHistoryEvnent } from "../utils/pv";
// dom节点操作 上报-01
const MouseEventList: string[] = [
"click",
"dblclick",
"contextmenu",
"mousedown",
"mouseup",
"mouseenter",
"mouseout",
"mouseover",
];
export default class Tracher {
public data: Options;
constructor(options: Options) {
this.data = Object.assign(this.initDef(), options);
this.initTracker();
}
private initDef(): DefaultOptons {
// 修改 history路由 以便于记录埋点事件
window.history["pushState"] = createHistoryEvnent("pushState");
window.history["replaceState"] = createHistoryEvnent("replaceState");
return <DefaultOptons>{
sdkVersion: TrackerConfig.version,
historyTracker: false,
hashTracker: false,
domTracker: false,
jsError: false,
};
}
// uuid 用户的唯一标识
public setUserId<T extends DefaultOptons["uuid"]>(uuid: T) {
this.data.uuid = uuid;
}
// 用户的自定义参数
public setExtra<T extends DefaultOptons["extra"]>(extra: T) {
this.data.extra = extra;
}
private captureEvents<T>(
mouseEventList: string[],
targetKey: string,
data?: T
) {
mouseEventList.forEach((event) => {
window.addEventListener(event, () => {
// console.log("监听到了");
this.reportTracker({
event,
targetKey,
data,
});
});
});
}
private initTracker() {
// 若是用户 开启 historyTracker 循环监听用户的事件
if (this.data.historyTracker) {
this.captureEvents(["pushState"], "history-pv");
this.captureEvents(["replaceState"], "history-pv");
this.captureEvents(["popstate"], "history-pv");
}
if (this.data.hashTracker) {
// 'hash-pv' - 此参数需要与后台进行协商定义
this.captureEvents(["hashChange"], "hash-pv");
}
// dom 节点上报 -03
if (this.data.domTracker) {
this.targetKeyReport();
}
// js错误上报 - 1
if (this.data.jsError) {
this.jsError();
}
}
// 手动上报
public sendTracker<T extends reportTrackerData>(data: T) {
this.reportTracker(data);
}
// 上报用户操作 - 监听到的时候 调用
private reportTracker<T>(data: T) {
// 拿到上报的data数据
const params = Object.assign(this.data, data, {
time: new Date().getTime(),
});
// 修改请求头,修改为键值对的形式 并且转换为JSON字符串传递
let headers = {
type: "application/x-www-form-urlencoded",
};
let blob = new Blob([JSON.stringify(params)], headers);
navigator.sendBeacon(this.data.requestUrl, blob);
}
// dom 节点上报 -02
private targetKeyReport() {
MouseEventList.forEach((event) => {
window.addEventListener(event, (e) => {
const target = e.target as HTMLElement;
const targetKey = target.getAttribute("target-key");
// 如果有上报属性,则需要上报
if (targetKey) {
this.reportTracker({
event,
targetKey,
});
}
});
});
}
// js错误上报 - 1
private jsError() {
this.errorEvent();
this.promiseReject();
}
//捕获js报错
private errorEvent() {
window.addEventListener("error", (event) => {
this.reportTracker({
event: "error",
targetKey: "message",
message: event.message,
});
});
}
//捕获promise 错误
private promiseReject() {
window.addEventListener("unhandledrejection", (event) => {
event.promise.catch((error) => {
this.reportTracker({
event: "promise",
targetKey: "reject",
// targetKey: "message",
message: error,
});
});
});
}
}
07-2 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="./dist/index.js"></script>
<button target-key="btn">添加上报</button>
<button>无添加</button>
<script>
new tracker({
requestUrl: "http://localhost:9000/tracker",
historyTracker: true,
domTracker:true,
jsError:true
});
</script>
</body>
</html>