前端埋点SDK

7/18/2022 资料

# 前端埋点

埋点 (opens new window)就是 数据采集-数据处理-数据分析和挖掘,如用户停留时间,用户哪个按钮点的多 ==

打包工具 --- rollup

使用rollup 应为 rollup打包干净,而webpack非常臃肿,可读性差,所以rollup非常适合开发SDK和一些框架,webpack 适合开发一些项目

目录结构

src
	--- core 核心代码
	--- utils 工具函数存放
	--- types 声明文件
rollup.config.js
tsconfig.json
package.json
1
2
3
4
5
6
7

# 开发依赖

npm install rollup -D
npm install rollup-plugin-dts -D
npm install rollup-plugin-typescript2 -D
npm install typescript -D
1
2
3
4

# 配置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
    {
      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()]
}] 
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

# 类型定义

/**
 * @requestUrl 接口地址
 * @historyTracker history上报
 * @hashTracker hash上报
 * @domTracker 携带Tracker-key 点击事件上报
 * @historyTracker sdkVersion sdk版本
 * @historyTracker 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
}


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
}
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

# 核心代码

  1. PV:页面访问量,即PageView,用户每次对网站的访问均被记录

    • 主要监听了 historyhash

    • history 无法通过 popstate 监听 只能重写其函数 在utils/pv

      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;
        }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
    • hash 使用hashchange 监听

  2. UV(独立访客):即Unique Visitor,访问您网站的一台电脑客户端为一个访客

    • 用户唯一表示 可以在登录之后通过接口返回的id 进行设置值 提供了setUserId
    • 也可以使用canvas 指纹追踪技术
  3. 使用 navigator.sendBeacon 上报

    • 这个上报的机制 跟 XMLHttrequest 对比
    • navigator.sendBeacon 即使页面关闭了 也会完成请求 而XMLHTTPRequest不一定
  4. DOM事件监听

    • 主要是给需要监听的元素添加一个属性 用来区分是否需要监听 target-key="当前元素的信息"
import { DefaultOptons, Options, TrackerConfig, reportTrackerData } from "../types/core";
import { createHistoryEvnent } from "../utils/pv";

const MouseEventList: string[] = ['click', 'dblclick', 'contextmenu', 'mousedown', 'mouseup', 'mouseenter', 'mouseout', 'mouseover']

export default class Tracker {
  public data: Options;
  private version: string | undefined;

  public constructor(options: Options) {
    this.data = Object.assign(this.initDef(), options)
    this.installInnerTrack()
  }

  private initDef(): DefaultOptons {
    this.version = TrackerConfig.version;
    window.history['pushState'] = createHistoryEvnent("pushState")
    window.history['replaceState'] = createHistoryEvnent('replaceState')
    return <DefaultOptons>{
      sdkVersion: this.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
  }

  public sendTracker<T extends reportTrackerData>(data: T) {
    this.reportTracker(data)
  }

  private captureEvents<T>(MouseEventList: string[], targetKey: string, data?: T) {
    MouseEventList.forEach(event => {
      window.addEventListener(event, () => {
        this.reportTracker({ event, targetKey, data })
      })
    })
  }
  private installInnerTrack() {
    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()
    }
  }
  //dom 点击上报
  private targetKeyReport() {
    MouseEventList.forEach(event => {
      window.addEventListener(event, (e) => {
        const target = e.target as HTMLElement
        const targetValue = target.getAttribute('target-key')
        if (targetValue) {
          this.sendTracker({
            targetKey: targetValue,
            event
          })
        }
      })
    })
  }
  private jsError() {
    this.errorEvent()
    this.promiseReject()
  }
  //捕获js报错
  private errorEvent() {
    window.addEventListener('error', (e) => {
      this.sendTracker({
        targetKey: 'message',
        event: 'error',
        message: e.message
      })
    })
  }
  //捕获promise 错误
  private promiseReject() {
    window.addEventListener('unhandledrejection', (event) => {
      event.promise.catch(error => {
        this.sendTracker({
          targetKey: "reject",
          event: "promise",
          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-urlencoded'
    };
    let blob = new Blob([JSON.stringify(params)], headers);
    navigator.sendBeacon(this.data.requestUrl, blob)
  }
}
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

# 配置package.json

参考 package.json 配置

# 发布npm

一定要使用npm 的源 不能使用淘宝镜像 否则 报错403

添加用户
npm adduser
登陆
npm login
发布包
npm publish
删除包
npm unpublish
使包过期
npm deprecate
1
2
3
4
5
6
7
8
9
10
Last Updated: 11/2/2022, 9:37:04 PM