目录
前端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
安装
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 : [
{
file : path. resolve ( __dirname, "./dist/index.esm.js" ) ,
format : "es" ,
} ,
{
file : path. resolve ( __dirname, "./dist/index.cjs.js" ) ,
format : "cjs" ,
} ,
{
file : path. resolve ( __dirname, "./dist/index.js" ) ,
format : "umd" ,
name : "tracker" ,
} ,
] ,
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行
"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) ;
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
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
}
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 {
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 ( ) {
if ( this . data. historyTracker ) {
this . captureEvents ( [ 'pushState' , 'replaceState' , 'popstate' ] , 'history-pv' )
}
if ( this . data. hashTracker) {
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 {
window. history[ "pushState" ] = createHistoryEvnent ( "pushState" ) ;
window. history[ "replaceState" ] = createHistoryEvnent ( "replaceState" ) ;
return < DefaultOptons> {
sdkVersion : TrackerConfig. version,
historyTracker : false ,
hashTracker : false ,
domTracker : false ,
jsError : false ,
} ;
}
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 ( ) {
if ( this . data. historyTracker) {
this . captureEvents (
[ "pushState" , "replaceState" , "popstate" ] ,
"history-pv"
) ;
}
if ( this . data. hashTracker) {
this . captureEvents ( [ "hashChange" ] , "hash-pv" ) ;
}
}
}
05 上报 navigator.sendBeacon
为什么要使用这个去上报
这个上报的机制 跟 XMLHttrequest 对比 navigator.sendBeacon 即使页面关闭了 也会完成请求 而XMLHTTPRequest 不一定
05-1 src / core / index.ts
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 {
window. history[ "pushState" ] = createHistoryEvnent ( "pushState" ) ;
window. history[ "replaceState" ] = createHistoryEvnent ( "replaceState" ) ;
return < DefaultOptons> {
sdkVersion : TrackerConfig. version,
historyTracker : false ,
hashTracker : false ,
domTracker : false ,
jsError : false ,
} ;
}
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, ( ) => {
this . reportTracker ( {
event,
targetKey,
data,
} ) ;
} ) ;
} ) ;
}
private initTracker ( ) {
if ( this . data. historyTracker) {
this . captureEvents ( [ "pushState" ] , "history-pv" ) ;
this . captureEvents ( [ "replaceState" ] , "history-pv" ) ;
this . captureEvents ( [ "popstate" ] , "history-pv" ) ;
}
if ( this . data. hashTracker) {
this . captureEvents ( [ "hashChange" ] , "hash-pv" ) ;
}
}
public sendTracker< T extends reportTrackerData > ( data: T ) {
this . reportTracker ( data) ;
}
private reportTracker< T > ( data: T ) {
const params = Object. assign ( this . data, data, {
time : new Date ( ) . getTime ( ) ,
} ) ;
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 启动后端服务
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" ;
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 {
window. history[ "pushState" ] = createHistoryEvnent ( "pushState" ) ;
window. history[ "replaceState" ] = createHistoryEvnent ( "replaceState" ) ;
return < DefaultOptons> {
sdkVersion : TrackerConfig. version,
historyTracker : false ,
hashTracker : false ,
domTracker : false ,
jsError : false ,
} ;
}
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, ( ) => {
this . reportTracker ( {
event,
targetKey,
data,
} ) ;
} ) ;
} ) ;
}
private initTracker ( ) {
if ( this . data. historyTracker) {
this . captureEvents ( [ "pushState" ] , "history-pv" ) ;
this . captureEvents ( [ "replaceState" ] , "history-pv" ) ;
this . captureEvents ( [ "popstate" ] , "history-pv" ) ;
}
if ( this . data. hashTracker) {
this . captureEvents ( [ "hashChange" ] , "hash-pv" ) ;
}
if ( this . data. domTracker) {
console. log ( '11' ) ;
this . targetKeyReport ( ) ;
}
}
public sendTracker< T extends reportTrackerData > ( data: T ) {
this . reportTracker ( data) ;
}
private reportTracker< T > ( data: T ) {
const params = Object. assign ( this . data, data, {
time : new Date ( ) . getTime ( ) ,
} ) ;
let headers = {
type : "application/x-www-form-urlencoded" ,
} ;
let blob = new Blob ( [ JSON . stringify ( params) ] , headers) ;
navigator. sendBeacon ( this . data. requestUrl, blob) ;
}
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 修改与添加事件
< ! 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" ;
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 {
window. history[ "pushState" ] = createHistoryEvnent ( "pushState" ) ;
window. history[ "replaceState" ] = createHistoryEvnent ( "replaceState" ) ;
return < DefaultOptons> {
sdkVersion : TrackerConfig. version,
historyTracker : false ,
hashTracker : false ,
domTracker : false ,
jsError : false ,
} ;
}
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, ( ) => {
this . reportTracker ( {
event,
targetKey,
data,
} ) ;
} ) ;
} ) ;
}
private initTracker ( ) {
if ( this . data. historyTracker) {
this . captureEvents ( [ "pushState" ] , "history-pv" ) ;
this . captureEvents ( [ "replaceState" ] , "history-pv" ) ;
this . captureEvents ( [ "popstate" ] , "history-pv" ) ;
}
if ( this . data. hashTracker) {
this . captureEvents ( [ "hashChange" ] , "hash-pv" ) ;
}
if ( this . data. domTracker) {
this . targetKeyReport ( ) ;
}
if ( this . data. jsError) {
this . jsError ( ) ;
}
}
public sendTracker< T extends reportTrackerData > ( data: T ) {
this . reportTracker ( data) ;
}
private reportTracker< T > ( data: T ) {
const params = Object. assign ( this . data, data, {
time : new Date ( ) . getTime ( ) ,
} ) ;
let headers = {
type : "application/x-www-form-urlencoded" ,
} ;
let blob = new Blob ( [ JSON . stringify ( params) ] , headers) ;
navigator. sendBeacon ( this . data. requestUrl, blob) ;
}
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,
} ) ;
}
} ) ;
} ) ;
}
private jsError ( ) {
this . errorEvent ( ) ;
this . promiseReject ( ) ;
}
private errorEvent ( ) {
window. addEventListener ( "error" , ( event ) => {
this . reportTracker ( {
event : "error" ,
targetKey : "message" ,
message : event. message,
} ) ;
} ) ;
}
private promiseReject ( ) {
window. addEventListener ( "unhandledrejection" , ( event ) => {
event. promise. catch ( ( error ) => {
this . reportTracker ( {
event : "promise" ,
targetKey : "reject" ,
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>