基于Vue3實(shí)現(xiàn)前端埋點(diǎn)上報插件并打包發(fā)布到npm的詳細(xì)過程
前端埋點(diǎn)對于那些營銷活動的項(xiàng)目是必須的,它可以反應(yīng)出用戶的喜好與習(xí)慣,從而讓項(xiàng)目的運(yùn)營者們能夠調(diào)整策略優(yōu)化流程提高用戶體驗(yàn)從而獲取更多的$。這篇文章將實(shí)現(xiàn)一個Vue3版本的埋點(diǎn)上報插件,主要功能有
- 通過Vue自定義指令形式實(shí)現(xiàn)點(diǎn)擊事件上報
- 提供手動調(diào)用上報方法
- 上報每個頁面訪問人數(shù)與次數(shù)(UV,PV)
- 上報用戶在每個頁面停留時長
項(xiàng)目環(huán)境搭建
本項(xiàng)目采用pnpm進(jìn)行Monorepo環(huán)境搭建,因?yàn)槲磥磉@個項(xiàng)目可能會加入更多的工具包.
安裝pnpm
npm install pnpm -g
初始化package.json
pnpm init
新建配置文件 .npmrc
shamefully-hoist = true
新建pnpm-workspace.yaml
packages: - "packages/**" - "play"
此時我們的packages
目錄和play
目錄便關(guān)聯(lián)起來的,我們后面就可以愉快的在本地調(diào)試了。其中packages
是我們各種包存放的地方,具體我們本次開發(fā)的埋點(diǎn)插件v-tracking
便是其中之一。play則是一個Vue3項(xiàng)目用來測試我們的本地包,它的創(chuàng)建方法這里就不再詳細(xì)說了。最終它的目錄結(jié)構(gòu)如下
插件開發(fā)
終端進(jìn)入v-tracking
,執(zhí)行pnpm init
讓它成為一個包,然后新建index.js
作為入口。
在vue3是通過 app.use(plugin)
的形式引入插件的,它會直接調(diào)用插件的install
方法.install
會接收到應(yīng)用實(shí)例和傳遞給app.use()
的額外選項(xiàng)作為參數(shù)。所以我們在v-tracking/index.js
默認(rèn)導(dǎo)出一個帶有install
函數(shù)的對象
export default { install: (app, options) => { console.log(options) } }
進(jìn)入paly
執(zhí)行pnpm add v-tracking
此時你會發(fā)現(xiàn)paly
下的package.json
多了個這樣的依賴
這樣就是表示play
已經(jīng)關(guān)聯(lián)到本地的包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')
啟動項(xiàng)目我們會發(fā)現(xiàn)install
函數(shù)被調(diào)用了,并且獲取到了傳來的額外參數(shù).
點(diǎn)擊事件上報
點(diǎn)擊事件的上報我們提供兩種方式,一種是以Vue自定義指令的形式,一種是手動調(diào)用上報方法。因?yàn)橹噶钚问降狞c(diǎn)擊上報并不能實(shí)現(xiàn)異步上報,所以加入手動調(diào)用上報的方法
vue自定義指令
首先我們簡單了解一下什么是自定義指令。我們都用過Vue的內(nèi)置的一系列指令 (比如v-model
或v-show
) 等,而Vue還提供了注冊自定義指令的函數(shù)directive
用法如下,其中el
是我們綁定指令的dom,binding
則是指令傳來的一系列參數(shù),比如
<div v-example:foo.bar="baz">
binding則是這樣一個對象
{ arg: 'foo', modifiers: { bar: true }, value: /* `baz` 的值 */, oldValue: /* 上一次更新時 `baz` 的值 */ }
了解完指令我們便可以開始自定義指令click
的開發(fā)了。其實(shí)很簡單,就是監(jiān)聽el
的點(diǎn)擊事件然后獲取到指令的value
上報給后端即可
export default { install: (app, options) => { app.directive('click', (el, bind) => { el.addEventListener('click', () => { console.log(bind.value) }) }) } }
我們在play
的page1.vue
種進(jìn)行綁定指令測試
<template> <div v-click="{ eventName: 'test1' }">test1</div> </template>
我們點(diǎn)擊test1
便可以在控制臺看到我們需要上報的數(shù)據(jù)
手動上報方法
我們可以手動調(diào)用上報方法掛載在實(shí)例全局即可,在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
中進(jìn)行使用
<template> <div v-click="{ eventName: 'test1' }">test1</div> </template> <script setup> import { getCurrentInstance } from 'vue'; const { proxy } = getCurrentInstance() proxy.$vtrack({ eventName: 'test1' }) </script>
同樣的我們可以獲取到我們需要的上報數(shù)據(jù)。
頁面訪問次數(shù)上報(pv,uv)
對于頁面訪問次數(shù)或者人數(shù)我們可以通過檢測路由的變化從而上報當(dāng)前頁面事件。比如在page1
頁面我們可以以prefix_/page1
(這個前綴可以由自己來定義)形式上報。但是在插件中如何檢測路由變化呢?
起初我想通過監(jiān)聽onhashchange
事件來監(jiān)聽路由變化的,但是經(jīng)過測試發(fā)現(xiàn)Vue中的push事件根本不會觸發(fā)onhashchange
。所以我便引入了@vue/reactivity
,通過它的reactive
讓傳入app
實(shí)例進(jìn)行一個響應(yīng)式包裹,再通過effect
函數(shù)監(jiān)聽路由變化從而實(shí)現(xiàn)統(tǒng)計每個頁面的進(jìn)入事件,首先安裝
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); }; }, };
然后在項(xiàng)目中切換路由就會獲取到需要上報的事件
頁面停留時間(TP)
頁面停留時長同樣借助effect
函數(shù),通過計算頁面變化的時間差從而上報頁面停留時長事件,一般當(dāng)進(jìn)入第二個頁面才會統(tǒng)計第一個頁面的TP,進(jìn)入三個頁面計算第二個頁面的TP。。。所以我們把邏輯寫在getVisitor
函數(shù)中然后給它改個名
//上報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
,因此我們切換頁面的時候可以看到同時上報的兩個事件
獲取公共參數(shù)
根據(jù)用戶傳來的固定參數(shù)baseParams
和事件前綴prefix
調(diào)整我們上報事件形式。假設(shè)在main.js
用戶傳來這些數(shù)據(jù)
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作為單獨(dú)參數(shù)上報,不再使用上面的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的請求函數(shù),這里不考慮請求失敗的情況,此時需要用戶傳入一個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 || {}) }); }; }, };
此時便可以看到事件的請求了
打包發(fā)布
最后使用vite進(jìn)行打包發(fā)布,全局安裝vite
pnpm add vite -w -D
然后在v-tracking
下新建vite.config.js
,配置庫模式打包c(diǎn)js和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
代表如果項(xiàng)目支持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目錄下執(zhí)行pnpm publish
進(jìn)行發(fā)布(這里需要注冊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");
注意
因?yàn)樯婕暗铰酚蓹z測,所以必須配合vue-router
使用
Option
- sbaseParams (string)
公共參數(shù),每次上報都會攜帶的參數(shù),比如用戶的登錄信息 uid 等
- baseUrl (string)
上報的后臺請求地址,后端接口需按照前端請求參數(shù)設(shè)計
- prefix (string)
PV&UV&TP 事件前綴,一般用于區(qū)分不同項(xiàng)目等(建議和普通事件前綴一致)
- isVisTP (Boolean)
是否統(tǒng)計頁面 UV&PV&PT
Options 示例
app.use(vTracking, { baseParams: { uid: 123 }, baseUrl: "http://example/event", prefix: "app", isVisTP: false, });
點(diǎn)擊指令上報
<template> <div>page1</div> <div v-click="{ eventName: 'test1' }">click</div> </template>
后臺接收數(shù)據(jù)格式為
{ 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>
后臺接收數(shù)據(jù)格式為
{ uid: 123, eventName: "test1" }
UV&PV
isVisTP
為 true 時候插件會自動上報每個頁面進(jìn)入時的數(shù)據(jù),其中后臺接收數(shù)據(jù)格式為
{ uid: 123, UPVEventName: `${prefix}_${path}` }
其中path
為頁面路由路徑,如/page1
頁面停留時長(TP)
isVisTP
為 true 時候插件會自動上報每個頁面用戶停留時長,其中后臺接收數(shù)據(jù)格式為
{ uid: 123, TP: { path: "/page2", time: 1269446 }, }
time 則表示時長(ms)
寫在最后
本篇文章旨在提供一些思路,難免會有不妥或者錯誤之處,也歡迎大家評論區(qū)指出不勝感激。倉庫地址vue-utils
到此這篇關(guān)于基于Vue3實(shí)現(xiàn)一個前端埋點(diǎn)上報插件并打包發(fā)布到npm的文章就介紹到這了,更多相關(guān)Vue打包發(fā)布到npm內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
el-form表單el-form-item label不換行問題及解決
這篇文章主要介紹了el-form表單el-form-item label不換行問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10vue組件實(shí)現(xiàn)彈出框點(diǎn)擊顯示隱藏效果
這篇文章主要為大家詳細(xì)介紹了vue組件實(shí)現(xiàn)彈出框點(diǎn)擊顯示隱藏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-04-04vue+axios?methods方法return取不到值問題及解決
這篇文章主要介紹了vue+axios?methods方法return取不到值問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10在Vue中使用scoped屬性實(shí)現(xiàn)樣式隔離的原因解析
scoped是Vue的一個特殊屬性,可以應(yīng)用于<style>標(biāo)簽中的樣式,這篇文章給大家介紹在Vue中,使用scoped屬性為什么可以實(shí)現(xiàn)樣式隔離,感興趣的朋友一起看看吧2023-12-12