@ued2345/octopus-util
v1.1.7-1
Published
ued2345 utils
Downloads
29
Readme
技术选型
- typescript
- babel
- webpack
项目结构介绍
├── build
│ ├── build.js // 执行打包 npm run build
│ └── webpack.conf.js // webpack配置文件
├── dist
│ ├── octopus-util.min.js // 打包后的文件,也是被其它项目引入的文件
├── src
│ ├── device
│ │ │── getBrowser.ts // 获取操作系统类型
│ │ └── gettInApp.ts // 判断是否在APP内
│ ├── cookie
│ │ │── getCookie.ts // 获取cookie
│ │ │── setCookie.ts // 设置cookie
│ │ └── delCookie.ts // 删除cookie
│ ├── jsbrige
│ │ │── init // 二级目录
│ │ │ │── bridge.ts // 定义类型
│ │ │ └── brigeInit.ts // 创建bridge对象
│ │ └── jsBrige.ts // export jsbridge方法
│ ├── monitor
│ │ │── catchPerformance.ts // 性能监控上报
│ │ └── catchError.ts // 错误收集上报
│ ├── vue-plugin
│ │ │── directives // vue全局指令
│ │ │ └── bang // 埋点指令
│ │ └── vuePlugin.ts // export vuePlugin
│ └── index.ts // webpack打包入口文件
├── .babelrc // babel配置文件
├── tsconfig.config.js // ts配置文件
├── README.md // 项目说明文档
构建与部署
第一步:npm run build
第二步:npm发包
API文档
octopus = {
// 获取操作系统类型
getBrowser: () => {
android: boolean,
gecko: boolean,
iPad: boolean,
iPad: boolean,
iPhone: boolean,
ios: boolean,
mobile: boolean,
presto: boolean,
qq: boolean,
trident: boolean,
webApp: boolean,
webKit: boolean,
weixin: boolean,
},
// 通过userAgent 判断是否在APP内
getInApp: (uName) => {return boolean},
// JsBridge 与原生通信
JsBridge: {
// js调用webview事件
callHandle: ({method, params}, callback) => {},
// webView调用JS事件
registerHandle: (method, callback) => {},
},
// cookie 操作
setCookie: (name, value, days) => {},
getCookie: (name) => {},
delCookie: (name) => {},
VuePlugin, // vue工具(内含insert方法)
CatchError, // 错误收集
CatchPerformance // 性能监控
}
安装使用
npm install --save-dev @ued2345/octopus-util
项目中调用
- 浏览器
<script src="octopus-util.min.js"></script>
<script>
var browser = window.OcUtil.getBrowser()
</script>
- webpackregisterError
import OcUtil from '@ued2345/octopus-util'
const browser = OcUtil.getBrowser()
// 或单独引用方法
import {getBrowser} from '@ued2345/octopus-util'
错误收集
- window.onerror
- 资源加载失败捕获
- Vue组件错误捕获
- Promise中没有catch捕获错误会被捕获
- vue项目中接入
import Vue from 'vue'
import { CatchError} from "@ued2345/octopus-util";
CatchError.getInstance(Vue, {
reportFun: function(data) { // 错误上报
console.log('error:', data)
},
})
/**
* 上报数据格式(data)
* data = {
* type: string; // 错误类型:ERROR_RUNTIME, ERROR_LOAD__TYPE(SCRIPT, LINK,IMG, AUDIO, VIDEO), ERROR_VUE, ERROR_REJECT
* level: number; // error : 1 warning: 2 info: 3
* message: string; // 错误详情
* url: string; // 错误文件url或当前url
* col?: number;
* row?: number;
* }
**/
- 相关配置说明
// 调用方法
CatchError.getInstance(Vue, opts)
/**
* Vue: null 不会捕获vue 错误信息
* opts: {
* isReportNow: boolean; //是否立即上报(是:delay时间后立即执行多次上报,否:每隔delay时间上报一次)
* delay: number; // 延迟多长时间上报(默认3000)
* random: number; // 抽样上报 (0-1)1全量(默认全量)
* repeatNum: number; // 重复上报次数(默认3次),
* reportUnhandledRejection: boolean; // 是否捕获promise.reject错误
* reportJsError: boolean; // 是否上报js运行时错误
* reportResourceError: boolean; //是否上报资源加载错误
* reportFun: CallBack; // 上报错误函数
* }
**/
性能监控
- Navigation Timing API
- Resource Timing API
- NetWork Information API
- Paint Timing API
- FirstInput Timming API
vue项目接入
import {CatchPerformance} from "@ued2345/octopus-util";
CatchPerformance.getInstance({
reportFun: (data) => { // 性能上报方法
console.log('sssss:', data)
}
})
// CatchPerformance.getInstatnce(opts)
interface opts {
paintTiming?: boolean; // 渲染时间点信息
navigationTiming?: boolean; // 客户端收集性能数据
resourceTiming?: boolean; // 资源信息
firstInputTiming?: boolean; // 首次页面交互时间
networkInformation?: boolean; // 网络信息
dataConsumption?: boolean; // 是否统计资源大小
random?: number; // 抽样上报(0-1)1全量
reportFun?: (data) => void; // 上报函数
}
// reportFun: (data: Data) => void
type EventType =
| "navigationTiming"
| "networkInformation"
| "resourceTiming"
| "firstInputTiming"
| "paintTiming"
type Data = {name: EventType, data: any[]}[]
- Navigation Timing API
interface PerfumeNavigationTiming {
redirectTime?: number; // 重定向时间
dnsCatchTime?: number; // dns缓存时间
dnsTime?: number; // dns查询耗时
ttfbTime?: number; // 读取第一个字节的时间
unloadTime?: number; // 卸载页面耗时
tcpTime?: number; // tcp链接耗时
reqTime?: number; // request请求耗时
domTreeTime?: number, // 创建dom树
domAnalyzeTime?: number; // 解析dom树耗时
blankTime?: number; // 白屏时间
domReadyTime?: number; // domReadyTime耗时
loadTime?: number; // onload耗时
}
const getNavigationTiming = () => {
const t = (typeof this.w.PerformanceNavigationTiming === 'function') ? performance.getEntriesByType('navigation')[0] as any : this.wp.timing ;
if (!t) {
return {};
}
// We cache the navigation time for future times
return {
// 重定向时间:
redirectTime: t.redirectEnd - t.redirectStart,
// DNS缓存时间
dnsCatchTime: t.domainLookupStart - t.fetchStart,
// DNS解析时间
dnsTime: t.domainLookupEnd - t.domainLookupStart,
// ttfb 读取第一个字节的时间
ttfbTime: t.responseStart - t.domainLookupStart,
// 卸载页面耗时
unloadTime: t.unloadEventEnd - t.unloadEventStart,
// tcp链接耗时
tcpTime: t.connectEnd - t.connectStart,
// request请求耗时,页面下载耗时
reqTime: t.responseEnd - t.requestStart,
// dom渲染完成时间
domTreeTime: t.domInteractive - t.responseEnd,
//解析dom树耗时
domAnalyzeTime: t.domComplete - t.domInteractive,
// 白屏时间
blankTime: t.domInteractive - t.fetchStart,
// domReadyTime
domReadyTime: t.domContentLoadedEventEnd - t.fetchStart,
// onload耗时、首屏时间
loadTime: t.loadEventEnd - t.fetchStart
};
}
return {
name: 'navigationTiming',
data: <PerfumeNavigationTiming>Object
}
- Resource Timing API
type PerformanceEntryInitiatorType =
| 'beacon' // 源于 sendBeacon 方法
| 'link'
| 'fetch' // 来源于 fetch 方法
| 'img'
| 'other'
| 'script'
| 'xmlhttprequest';
return {
name: 'resourceTiming',
data: {
resourceTiming: <PerformanceEntryInitiatorType>[],
dataConsumption: this.config.dataConsumption ? <PerformanceEntryInitiatorType>Object : null
},
}
- NetWork Information API
type EffectiveConnectionType = '2g' | '3g' | '4g' | 'slow-2g';
interface PerfumeNetworkInformation {
downlink?: number; // 下行速度/带宽 Mb/s为单位的有效带宽
effectiveType?: EffectiveConnectionType; // 有效网络连接类型
onchange?: () => void; // 监听网络变化
rtt?: number; // 估算的往返时间
saveData?: boolean; // 打开/请求数据保护模式
}
return {
name: 'networkInformation',
data: <PerfumeNetworkInformation>Object
}
- Paint Timing API
let startTime: number
return {
name: 'paintTiming',
data: [{
name: 'first-paint',
value: startTime,
}, {
name: 'first-contentful-paint',
value: startTime
}]
}
- FirstInput Timming API
let duration: number // 从用户点击到响应时间
let event: Event // mousedown, pointerdown
return {
name: 'firstInputTiming',
data: [{
name: event,
value: duration
}]
}
vue相关工具
埋点工具
- 目的
优化大部分在项目中业务代码中混入埋点代码的情况
- 适用场景
可以改善以下三种情况下埋点代码的书写方式:
- 曝光: exposure(当该元素95%以上展示在可视区的时候触发)。
- 挂载: mounted(当该元素的dom生成的时候触发)。
- 点击: click(当该元素被点击的时候触发)。
- 使用示例
// main.js 或单页应用入口js
import {VuePlugin} from '@ued2345/octopus-util'
// 模拟上报函数
// 1。若cbDataType为默认的情况下,参数同vue自定义指令
const bang = (el, binding) => {console.log(`触发上报,类型:${binding.arg},值: ${binding.value}`)}
// 2、若cbDataType为'array'的情况下, 参数为 [{el, binding, vnode, oldVnode}]
// const bang = (dataArr) => {
// dataArr.forEach(item => {
// console.log(`触发上报,类型:${item.binding.arg},值: ${item.binding.value}`)
// })
// }
Vue.use(VuePlugin, {
bang: {
// 自定义配置
cb: bang
}
})
// test.vue
<div v-bang:exposure="`我曝光啦`"></div> //曝光后输出: "触发上报,类型:exposure,值: 我曝光啦"
<div v-bang:click="`我被点啦`"></div> //点击后输出: "触发上报,类型:click,值: 我被点啦"
<div v-bang:mounted="`我出生了啦`"></div> //dom生成后输出: "触发上报,类型:mounted,值: 我出生了啦"
低版本兼容 因为低版本机型不支持intersectionObserver需要安装相应polyfill,具体步骤如下:
- npm i intersection-observer --save
- 在单页应用入口文件处顶部引入,如vue-cli里的main.js
import 'intersection-observer'
支持配置
| 参数 | 说明 | 类型 | 是否必须 | 默认 | | ------ | ------ |------ | ------ |------ | | cb | 触发上报后的回调函数:当cbData为default时默认返回数据类型为:(el: HTMLElement, binding: DirectiveBinding, vnode: vnode, oldVnode: vnode) => void 详见官网。当cbDataType为'array'时会返回[{el, binding, vnode, oldVnode}]| 是 | 无 | | clickThrottleInterval | 点击事件上报节流的间隙时间 | number | 否 | 500 | | exposureMulti | 元素未销毁时是否可以多次发送曝光统计事件 | boolean| 否 | false | | imgErrExposure | 图片加载出错的时候是否算曝光成功(曝光事件绑定在img元素上时) | boolean | 否 | false | cbDataType | 回调函数收到的参数类型 | 'default'/'array' | 否 | 'default' exposureUseCache | 是否对曝光事件使用缓存, cacheLen:缓存的消息条数, cacheTime: 缓存的时间(ms) | {cacheLen: number, cacheTime: number} | 否 | false exposureRatio | 元素在可视区中展示超过多少比例时触发曝光 | number | 否 | 0.95
- 注意事项
- 在给不定高图片绑定曝光事件时,由于网络原因可能出现后面的图片先加载完曝光的情况,造成未在可视区内但是却曝光的假象。
- 原因:其实该图确实已经曝光,但是因为时间过短(前面图加载完之后该图就被挤出可视区了)肉眼难以察觉而已。
- 解决方法: 1.还是定高吧 2.这可能不能算个bug,因为它确实曝光过了(后期可能会加曝光最短时长的功能,能解决该问题)。
- 若同时给v-if,v-else元素绑定mounted埋点事件时,在两者tagName相同的情况下,切换显示时会造成后一个埋点事件未触发。
- 原因: 这是因为在切换显示时由于v-if和v-else的标签一样,而且没有key等唯一标识,在dom diff过程中默认该元素未发生变化,所以此时后面元素的指令insert钩子函数不会触发。
- 解决方法: 给两个元素设置不同的tagName或者给元素绑定唯一key属性, 如下
<div v-if="show" v-bang:mounted="`显示1`" key="显示1">显示1</div> <div v-else v-bang:mounted="`显示2`" key="显示2">显示2</div>
- 在给不定高图片绑定曝光事件时,由于网络原因可能出现后面的图片先加载完曝光的情况,造成未在可视区内但是却曝光的假象。