埋点
就是数据采集、数据处理、数据分析和挖掘,如用户停留时间,用户点击情况等
库设计
webpack臃肿,可读性差,但适合开发一些项目
rollup打包干净,所以非常适合开发SDK和一些框架
此处就使用rollup作为打包工具
目录结构
依赖
1 2 3 4
| npm install rollup -D npm install rollup-plugin-dts -D npm install rollup-plugin-typescript2 -D npm install typescript -D
|
打包工具配置
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import ts from "rollup-plugin-typescript2" import dts from 'rollup-plugin-dts'
import path from "path"
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(), ] } ]
|
类型定义
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 31 32 33 34 35 36
|
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 }
|
核心功能
页面访问量
即PageView,简称PV
用户每次对网站的访问均会被记录,记录时主要监听了history和hash
hash可以使用使用hashchange监听
history只能通过popstate监听,无法通过pushState、replaceState这些API监听,只能重写其函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| export const createHistoryEvent = <T extends keyof History>(type: T)=> { const origin = history[type]
return function(this:any) { const res = origin.apply(this,arguments) const e = new Event(type)
window.dispatchEvent(e)
return res } }
|
监听:
1 2 3 4 5 6 7 8 9 10 11
|
private captureEvents <T>(mouseEventList:string[],targetKey:string,data?:T) { mouseEventList.forEach(event=> { window.addEventListener(event,()=> { console.log("监听到了") this.reportTracker({event,targetKey,data}) }) }) }
|
独立访客
即Unique Visitor,简称UV,访问网站的一台电脑客户端为一个访客
需要生成用户唯一表示,这个是后台的工作,前台只需要获取并使用接口返回的id即可
1 2 3 4 5 6 7 8 9
| public setUserId <T extends DefaultOptons["uuid"]>(uuid:T) { this.data.uuid = uuid }
public setExtra <T extends DefaultOptons["extra"]>(extra:T) { this.data.extra = extra }
|
也可以使用canvas指纹追踪技术
navigator.sendBeacon
使用navigator.sendBeacon
上报访问记录,即使页面关闭了,也会完成请求,而XMLHTTPRequest不一定
1 2 3 4 5 6 7 8 9 10 11 12
| private reportTracker <T>(data:T) { const params = Object.assign(this.data,data,{time: new Date().getTime()}) let headers = { type: "application/x-www-form-unlencoded" } let blob = new Blob([JSON.stringify(params)],headers)
navigator.sendBeacon(this.data.requestUrl,blob) }
|
DOM事件监听
给需要监听的元素添加一个属性,用来区分是否需要监听
1 2 3 4 5 6 7 8 9 10 11 12 13
| private targetKeyReport() { MouseEventList.forEach(ev => { window.addEventListener(ev,(e)=>{ const target = e.target as HTMLElement const targetKey = target.getAttribute("target-key") if(targetKey) { this.reportTracker({event:ev,targetKey}) } }) }) }
|
错误上报
需要处理error事件和promise的unhandledrejection报错
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
| 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: "message", message: error }) }) }) }
|
全部代码
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| import { DefaultOptons, TrackerConfig, Options } from "../types/index" import { createHistoryEvent } from "../utils/pv" const MouseEventList: string[] = ['click', 'dblclick', 'contextmenu', 'mousedown', 'mouseup', 'mouseenter', 'mouseout', 'mouseover']
export default class Tracker { public data:Options constructor(options:Options) { this.data = Object.assign(this.initDef(),options) this.installTracker() }
private initDef():DefaultOptons { window.history["pushState"] = createHistoryEvent("pushState") window.history["replaceState"] = createHistoryEvent("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 installTracker() { if(this.data.historyTracker) { this.captureEvents(["pushState","replaceState","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>(data:T) { this.reportTracker(data) }
private targetKeyReport() { MouseEventList.forEach(ev => { window.addEventListener(ev,(e)=>{ const target = e.target as HTMLElement const targetKey = target.getAttribute("target-key") if(targetKey) { this.reportTracker({event:ev,targetKey}) } }) }) }
private captureEvents <T>(mouseEventList:string[],targetKey:string,data?:T) { mouseEventList.forEach(event=> { window.addEventListener(event,()=> { console.log("监听到了") this.reportTracker({event,targetKey,data}) }) }) }
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: "message", message: error }) }) }) }
private reportTracker <T>(data:T) { const params = Object.assign(this.data,data,{time: new Date().getTime()}) let headers = { type: "application/x-www-form-unlencoded" } let blob = new Blob([JSON.stringify(params)],headers)
navigator.sendBeacon(this.data.requestUrl,blob) } }
|
发布npm
发布设置
package.json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { "name": "tracker", "version": "1.0.0", "description": "", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "browser": "dist/index.js", "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "rollup -c" }, "keywords": [], "author": "", "files": ["dist"], "license": "ISC", "devDependencies": { } }
|
上传
npm的源一定要是默认源
注册
依次输入用户名、密码、邮箱,输入正确邮箱验证码后注册完成
登录
依次输入用户名、密码、邮箱,输入正确邮箱验证码后登录完成
发布
发布完成后会收到npm的提示邮件