基于Vue3实现一个前端埋点上报插件并打包发布到npm
前端埋点对于那些营销活动的项目是必须的,它可以反应出用户的喜好与习惯,从而让项目的运营者们能够调整策略优化流程提高用户体验从而获取更多的$。这篇文章将实现一个Vue3版本的埋点上报插件,主要功能有
- 通过Vue自定义指令形式实现点击事件上报
- 提供手动调用上报方法
- 上报每个页面访问人数与次数(UV,PV)
- 上报用户在每个页面停留时长
项目环境搭建
本项目采用pnpm进行Monorepo环境搭建,因为未来这个项目可能会加入更多的工具包.
安装pnpm
npm install pnpm -g
初始化package.json
pnpm init
新建配置文件 .npmrc
shamefully-hoist = true
新建pnpm-workspace.yaml
packages:
- "packages/**"
- "play"
此时我们的packages目录和play目录便关联起来的,我们后面就可以愉快的在本地调试了。其中packages是我们各种包存放的地方,具体我们本次开发的埋点插件v-tracking便是其中之一。play则是一个Vue3项目用来测试我们的本地包,它的创建方法这里就不再详细说了。最终它的目录结构如下
插件开发
终端进入v-tracking,执行pnpm init让它成为一个包,然后新建index.js作为入口。
在vue3是通过 app.use(plugin)的形式引入插件的,它会直接调用插件的install方法.install会接收到应用实例和传递给 app.use() 的额外选项作为参数。所以我们在v-tracking/index.js默认导出一个带有install函数的对象
export default {
install: (app, options) => {
console.log(options)
}
}
进入paly执行pnpm add v-tracking此时你会发现paly下的package.json多了个这样的依赖
这样就是表示play已经关联到本地的包v-tracking@1.0.0的包了,然后我们在paly的main.js引入我们的插件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
import vTracking from 'v-tracking'
const app = createApp(App)
app.use(router)
app.use(vTracking, {
baseParams: {
uid: 123
}
})
app.mount('#app')
启动项目我们会发现install函数被调用了,并且获取到了传来的额外参数.
点击事件上报
点击事件的上报我们提供两种方式,一种是以Vue自定义指令的形式,一种是手动调用上报方法。因为指令形式的点击上报并不能实现异步上报,所以加入手动调用上报的方法
vue自定义指令
首先我们简单了解一下什么是自定义指令。我们都用过Vue的内置的一系列指令 (比如 v-model 或 v-show) 等,而Vue还提供了注册自定义指令的函数directive用法如下,其中el是我们绑定指令的dom,binding则是指令传来的一系列参数,比如
<div v-example:foo.bar="baz">
binding则是这样一个对象
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
了解完指令我们便可以开始自定义指令click的开发了。其实很简单,就是监听el的点击事件然后获取到指令的value上报给后端即可
export default {
install: (app, options) => {
app.directive('click', (el, bind) => {
el.addEventListener('click', () => {
console.log(bind.value)
})
})
}
}
我们在play的page1.vue种进行绑定指令测试
<template>
<div v-click="{ eventName: 'test1' }">test1</div>
</template>
我们点击test1便可以在控制台看到我们需要上报的数据
手动上报方法
我们可以手动调用上报方法挂载在实例全局即可,在vue3种挂载全局属性的方法是app.config.globalProperties.xxx,所以我们定义一个全局上报方法$vtrack
export default {
install: (app, options) => {
app.directive('click', (el, bind) => {
el.addEventListener('click', () => {
console.log(bind.value)
})
})
//挂载全局用于手动上报
app.config.globalProperties.$vtrack = (params) => {
console.log(params)
}
}
}
然后我们在page1.vue中进行使用
<template>
<div v-click="{ eventName: 'test1' }">test1</div>
</template>
<script setup>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance()
proxy.$vtrack({ eventName: 'test1' })
</script>
同样的我们可以获取到我们需要的上报数据。
页面访问次数上报(pv,uv)
对于页面访问次数或者人数我们可以通过检测路由的变化从而上报当前页面事件。比如在page1页面我们可以以prefix_/page1(这个前缀可以由自己来定义)形式上报。但是在插件中如何检测路由变化呢?
起初我想通过监听onhashchange事件来监听路由变化的,但是经过测试发现Vue中的push事件根本不会触发onhashchange。所以我便引入了@vue/reactivity,通过它的reactive让传入app实例进行一个响应式包裹,再通过effect函数监听路由变化从而实现统计每个页面的进入事件,首先安装
pnpm add @vue/reactivity -w
然后引用
import { reactive,effect } from '@vue/reactivity'
//uv and pv
const getVisitor = (app, prefix) => {
const globalProperties = reactive(app.config.globalProperties);
effect(() => {
const path = globalProperties.$route.path;
console.log({
eventName: `${prefix}_${path}`,
});
});
};
export default {
install: (app, options) => {
stayTime();
getVisitor(app, "track");
app.directive("click", (el, bind) => {
el.addEventListener("click", () => {
console.log(bind.value);
});
});
//挂载全局用于手动上报
app.config.globalProperties.$vtrack = (params) => {
console.log(params);
};
},
};
然后在项目中切换路由就会获取到需要上报的事件
页面停留时间(TP)
页面停留时长同样借助effect函数,通过计算页面变化的时间差从而上报页面停留时长事件,一般当进入第二个页面才会统计第一个页面的TP,进入三个页面计算第二个页面的TP。。。所以我们把逻辑写在getVisitor函数中然后给它改个名
//上报uv&pv&TP
const getVisitorAndTP = (app, prefix) => {
const globalProperties = reactive(app.config.globalProperties);
let startTime = new Date().getTime();
let path = "";
let lastPath = "";
effect(() => {
const endTime = new Date().getTime();
const TP = endTime - startTime;
startTime = endTime;
lastPath = path;
path = globalProperties.$route.path;
//间隔为0不上报
if (!TP) return;
console.log({
eventName: `${prefix}_${path}`,
});
//页面停留时长小于0.5s不上报
if (TP < 500) return;
console.log({
eventName: `${prefix}_${TP}_${lastPath}`,
});
});
};
export default {
install: (app, options) => {
getVisitorAndTP(app, "track");
app.directive("click", (el, bind) => {
el.addEventListener("click", () => {
console.log(bind.value);
});
});
//挂载全局用于手动上报
app.config.globalProperties.$vtrack = (params) => {
console.log(params);
};
},
};
上传TP事件的格式为prefix_TP_path,因此我们切换页面的时候可以看到同时上报的两个事件
获取公共参数
根据用户传来的固定参数baseParams和事件前缀prefix调整我们上报事件形式。假设在main.js用户传来这些数据
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import vTracking from "v-tracking";
const app = createApp(App);
app.use(router);
app.use(vTracking, {
baseParams: {
uid: 123,
userAgent: "Chrome",
},
prefix: "app",
});
app.mount("#app");
然后修改一下我们的插件(这里将uv/pv还有TP作为单独参数上报,不再使用上面的eventName形式,太懒了,上面的写法不想改了)
import { reactive, effect } from "@vue/reactivity";
//上报uv&pv&TP
const getVisitorAndTP = (app, prefix, baseParams) => {
const globalProperties = reactive(app.config.globalProperties);
let startTime = new Date().getTime();
let path = "";
let lastPath = "";
effect(() => {
const endTime = new Date().getTime();
const TP = endTime - startTime;
startTime = endTime;
lastPath = path;
path = globalProperties.$route.path;
//间隔为0不上报
if (!TP) return;
console.log({
...baseParams,
UPVEventName: `${prefix}_${path}`,
});
//页面停留时长小于0.5s不上报
if (TP < 500) return;
console.log({
...baseParams,
TP: {
path: lastPath,
time: TP,
},
});
});
};
export default {
install: (app, options) => {
const { prefix, baseParams } = options;
getVisitorAndTP(app, prefix || "track", baseParams || {});
app.directive("click", (el, bind) => {
el.addEventListener("click", () => {
console.log({ ...bind.value, ...(baseParams || {}) });
});
});
//挂载全局用于手动上报
app.config.globalProperties.$vtrack = (params) => {
console.log(params);
};
},
};
此时这控制台打印出事件类型上报格式为
引入axios
最后简单写一个axios的请求函数,这里不考虑请求失败的情况,此时需要用户传入一个baseUrl
import { reactive, effect } from "@vue/reactivity";
import axios from "axios";
axios.defaults.headers["Content-Type"] = "application/json";
const request = (baseUrl, params) => {
axios({
url: baseUrl,
method: "post",
data: params,
});
};
//上报uv&pv&TP
const getVisitorAndTP = (app, prefix, baseParams, baseUrl) => {
const globalProperties = reactive(app.config.globalProperties);
let startTime = new Date().getTime();
let path = "";
let lastPath = "";
effect(() => {
const endTime = new Date().getTime();
const TP = endTime - startTime;
startTime = endTime;
lastPath = path;
path = globalProperties.$route.path;
//间隔为0不上报
if (!TP) return;
request(baseUrl, {
...baseParams,
UPVEventName: `${prefix}_${path}`,
});
//页面停留时长小于0.5s不上报
if (TP < 500) return;
request(baseUrl, {
...baseParams,
TP: {
path: lastPath,
time: TP,
},
});
});
};
export default {
install: (app, options) => {
const { prefix, baseParams, baseUrl } = options;
getVisitorAndTP(app, prefix || "track", baseParams || {}, baseUrl);
app.directive("click", (el, bind) => {
el.addEventListener("click", () => {
request(baseUrl, { ...bind.value, ...(baseParams || {}) });
});
});
//挂载全局用于手动上报
app.config.globalProperties.$vtrack = (params) => {
request(baseUrl, { ...params, ...(baseParams || {}) });
};
},
};
此时便可以看到事件的请求了
打包发布
最后使用vite进行打包发布,全局安装vite
pnpm add vite -w -D
然后在v-tracking下新建vite.config.js,配置库模式打包cjs和es格式
import { defineConfig } from "vite";
import { resolve } from "path";
export default defineConfig({
build: {
target: "modules",
//压缩
minify: true,
rollupOptions: {
input: ["index.js"],
//忽略文件
external: ["@vue/reactivity", "axios"],
output: [
{
format: "es",
//不用打包成.es.js,这里我们想把它打包成.js
entryFileNames: "[name].js",
//配置打包根目录
dir: resolve(__dirname, "./dist/es"),
},
{
format: "cjs",
//不用打包成.mjs
entryFileNames: "[name].js",
//配置打包根目录
dir: resolve(__dirname, "./dist/lib"),
},
],
},
lib: {
entry: "./index.js",
name: "vtrack",
},
},
});
然后将v-tracking/package.json入口文件指向打包后路径,其中module代表如果项目支持es格式的话就会使用dist/es/index.js这个路径
{
"name": "v-tracking",
"version": "1.0.0",
"main": "dist/lib/index.js",
"module": "dist/es/index.js",
"description": "",
"keywords": [],
"files": [
"dist"
],
"dependencies": {
"@vue/reactivity": "^3.2.37",
"axios": "^0.27.2"
},
"author": "",
"license": "MIT"
}
最后在v-tracking目录下执行pnpm publish进行发布(这里需要注册npm账户等等)
使用说明
安装
npm install v-tracking -S
在 main.js 中引入插件
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router/index";
import vTracking from "v-tracking";
const app = createApp(App);
app.use(router);
app.use(vTracking, Options);
app.mount("#app");
注意
因为涉及到路由检测,所以必须配合vue-router使用
Options
- baseParams (string)
公共参数,每次上报都会携带的参数,比如用户的登录信息 uid 等
- baseUrl (string)
上报的后台请求地址,后端接口需按照前端请求参数设计
- prefix (string)
PV&UV&TP 事件前缀,一般用于区分不同项目等(建议和普通事件前缀一致)
- isVisTP (Boolean)
是否统计页面 UV&PV&PT
Options 示例
app.use(vTracking, {
baseParams: {
uid: 123
},
baseUrl: "http://example/event",
prefix: "app",
isVisTP: false,
});
点击指令上报
<template>
<div>page1</div>
<div v-click="{ eventName: 'test1' }">click</div>
</template>
后台接收数据格式为
{ uid: 123 , eventName: "test1" }
手动上报
<template>
<div>page1</div>
<div @click="track">click</div>
</template>
<script setup>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance()
//手动上报事件
const track = ()=>{
proxy.$vtrack({ eventName: 'test1' })
}
</script>
后台接收数据格式为
{ uid: 123, eventName: "test1" }
UV&PV
isVisTP为 true 时候插件会自动上报每个页面进入时的数据,其中后台接收数据格式为
{ uid: 123, UPVEventName: `${prefix}_${path}` }
其中path为页面路由路径,如/page1
页面停留时长(TP)
isVisTP为 true 时候插件会自动上报每个页面用户停留时长,其中后台接收数据格式为
{
uid: 123,
TP: { path: "/page2", time: 1269446 },
}
time 则表示时长(ms)
写在最后
本篇文章旨在提供一些思路,难免会有不妥或者错误之处,也欢迎大家评论区指出不胜感激。仓库地址vue-utils
都看到这了,点个赞再走吧~
基于Vue3实现一个前端埋点上报插件并打包发布到npm的更多相关文章
- 一个前端引用Facebook评论插件案例
最近公司海外的同事提了一个新的需求:那就是将Facebook的评论系统接入到公司海外网站的资讯详情页. 下面做一个简单的介绍: 首先我们登录到Facebook开发者平台:然后进入评论插件系统(http ...
- 学以致用:手把手教你撸一个工具库并打包发布,顺便解决JS浮点数计算精度问题
本文讲解的是怎么实现一个工具库并打包发布到npm给大家使用.本文实现的工具是一个分数计算器,大家考虑如下情况: \[ \sqrt{(((\frac{1}{3}+3.5)*\frac{2}{9}-\fr ...
- 12 - Vue3 UI Framework - 打包发布
基础组件库先做到这个阶段,后面我会继续新增.完善 接下来,我们对之前做的组件进行打包发布到 npm 返回阅读列表点击 这里 组件库优化 通用层叠样式表 我想大家都注意到了,前面我们在写组件的时候,sc ...
- 从零开始开发一个vue组件打包并发布到npm (把vue组件打包成一个可以直接引用的js文件)
自己写的组件 有的也挺好的,为了方便以后用自己再用或者给别人用,把组件打包发布到npm是最好不过了,本次打包支持 支持正常的组件调用方式,也支持Vue.use, 也可以直接引用打包好的js文件, 配合 ...
- 如何开发一个npm包并发布到npm中央仓库
转自: https://liaolongdong.com/2019/01/24/publish-public-npm.html 如何开发一个npm包并发布到npm中央仓库需求背景:平时在项目工作中可能 ...
- 如何从0开发一个Vue组件库并发布到npm
1.新建文件夹在终端打开执行 npm init -y 生成package.json如下,注意如果要发布到npm,name不能有下划线,大写字母等 { "name": "v ...
- 使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包
使用 js 和 Beacon API 实现一个简易版的前端埋点监控 npm 包 前端监控,埋点,数据收集,性能监控 Beacon API https://caniuse.com/beacon 优点,请 ...
- 基于 vite2 + Vue3 写一个在线帮助文档工具
提起帮助文档,想必大家都会想到 VuePress等,我也体验了一下,但是感觉和我的思路不太一样,我希望的是那种可以直接在线编辑文档,然后无需编译就可以直接发布的方式,另外可以在线写(修改)代码并且运行 ...
- 从0搭建vue3组件库: 如何完整搭建一个前端脚手架?
相信大家在前端开发中都使用过很多前端脚手架,如vue-cli,create-vite,create-vue等:本篇文章将会为大家详细介绍这些前端脚手架是如何实现的,并且从零实现一个create-kit ...
随机推荐
- 《原CSharp》第二回 巧习得元素分类 子不知怀璧其罪
第二回 巧习得元素分类 子不知怀璧其罪 ===================================================================== 云溪父亲见状看了看云 ...
- vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9
项目从vue2 升级vue3,VueI18n需要做适当的调整.主要是Vue I18n v8.x 到Vue I18n v9 or later 的变化,其中初始化: 具体可以参看:https://vue- ...
- 搭建zabbix及报错处理
搭建ZABBIX服务器准备工作 1.需要服务器是LAMP 或 LNMP 环境 2.主机名和IP要写在HOST文件里 3.iptables 和 selinux 必须关闭 一.先用最简单的方式搭建lamp ...
- mysql InnoDB通过.frm和.ibd恢复表和数据
ibdata1是一个用来构建innodb系统表空间的文件,这个文件包含了innodb表的元数据.撤销记录.修改buffer和双写buffer.如果file-per-table选项打开的话,该文件则不一 ...
- UiPath保存图片操作的介绍和使用
一.保存图像 (Save Image)的介绍 可以将图像保存到磁盘的一种活动 二.保存图像 (Save Image)在UiPath中的使用 1. 打开设计器,在设计库中新建一个Sequence,为序列 ...
- VisionPro · C# · 加载与保存取像工具
VisionPro 项目程序设计,取像工具可被包含在工具包内被调用,一般,为了满足程序取像可以实现单次取像,循环取像,实时取像等多方面应用,会将取像工具独立打包. 加载代码: 1 using Syst ...
- 临近梯度下降算法(Proximal Gradient Method)的推导以及优势
邻近梯度下降法 对于无约束凸优化问题,当目标函数可微时,可以采用梯度下降法求解:当目标函数不可微时,可以采用次梯度下降法求解:当目标函数中同时包含可微项与不可微项时,常采用邻近梯度下降法求解.上述三种 ...
- dubbo(九):timeout超时机制解析
在网络请求时,总会有各种异常情况出现,我们需要提前处理这种情况.在完善的rpc组件dubbo中,自然是不会少了这一层东西的.我们只需要通过一些简单的配置就可以达到超时限制的作用了. dubbo的设计理 ...
- SVM简要介绍
SVM 支持向量机(SVM),是一个用于解决二分类问题的有监督机器学习模型. 1.SVM的两个优点 更高的速度 在有一定的样本数量支持下(成千上万张),具有比其他模型有更好的效果 2.SVM的工作过程 ...
- 【有用的SQL】查Greenplum的数据字典
Greenplum 查询哪个表的分布键 ( Greenplum ) SELECT att.nspname AS 模式名 , att.relname AS 表名 , table_comment AS 表 ...