工具分享:实现前端埋点的自动化管理
埋点一直是 H5 项目中的重要一环,埋点数据更是后期改善业务和技术优化的重要基础。【推荐学习:web前端、编程教学】
在日常的工作中,经常会有产品或者业务的同学来问,“这个项目现在有哪些埋点?”,“这个埋点用在哪些地方?”像这样的问题基本上都是问一次查一次代码,效率很低。
这也许跟埋点本身的性质有关系。埋点属于相对独立的功能,随着迭代的进行,开发者很难记住埋点的用途。开发者出于自测验证的需要,也得对项目中的埋点数据加以整理。因此结合当前的场景,可以实现一个工具:通过对代码进行扫描,分析埋点相关的代码,并对之加以处理,转化成特定的数据,供后续在其他的管理平台中使用。
实现思路
这个工具大致可以分成三个部分,JSDoc 提取埋点、路由依赖分析和 ESLint 插件。
- JSDoc 是根据 JavaScript 中的注释信息,生成 API 文档的一个工具。结合 JSDoc 的这一个特性,这个埋点工具把 JSDoc 作为核心部分,用于输出代码中的埋点数据。
- Webpack 插件作为辅助,为 JSDoc 提供路由信息。
- ESLint 插件则作为最后的检验,确保文件中的埋点代码都有对应的 JSDoc 注释。
自定义 JSDoc 标记埋点
我们知道,JSDoc 可以根据代码中的注释输出一份文档。首先我们自定义一个 JSDoc 的 tag 来标注这是一个埋点的注释,这样后续处理时可以过滤掉其他注释的干扰。结合具体项目中使用的代码可以画出这样一个流程图:
下面是具体的代码实现的过程。
编写 JSDoc 插件,自定义一个 tag:
// jsdoc.plugin.js
// 自定义一个 @log,含有 @log 才是埋点的注释
exports.defineTags = function (dictionary) {
dictionary.defineTag('log', {
canHaveName: true,
onTagged: function (doclet, tag) {
doclet.meta.log = tag.text;
},
});
};
解析 .ts 和 .vue 文件。
// jsdoc.plugin.js
exports.handlers = {
beforeParse: function (e) {
// 对文件预处理
if (/.vue/.test(e.filename)) {
// 解析 vue 文件
const component = compiler.parseComponent(e.source);
// 获取 vue 文件的 script 代码
const ast = parse.parse(component.script.content, {
// ...
});
}
if (/.ts/.test(e.filename)) {
// ts 转 js
}
},
};
自定义 JSDoc 模版。
// publish.js
exports.publish = function (taffyData, opts, tutorials) {
// ...
data().each(function (doclet) {
// 有 log 这个 tag 的才是埋点注释
if (doclet.meta && doclet.meta.log) {
doclet.tags?.forEach((item) => {
// 获取对应的路由地址
});
// 拿到埋点数据
logData.push({});
}
});
// 输出 md 文档
fs.writeFileSync(outpath, mdContent, 'utf8');
};
到这里,已经可以完整地输出代码中的所有埋点了。此时再来看下目前这个工具的能力:
- 自动提取埋点信息,生成埋点文档:✅
- 自动给埋点注释添加自定义 tag(@log):❌
- 自动给埋点注释添加上报的埋点信息:❌
- 自动给埋点注释添加路由信息:❌
- 自动给埋点注释添加埋点描述信息:❌
- 自动提示没有注释的埋点代码:❌
通过上面的梳理我们可以看出:
- 需要手动给每个埋点加上注释
- 需要手动去查每个埋点所对应的路由
- 如果忘了给埋点加注释怎么办?
做这个工具的初衷,就是为省去一些重复繁琐的工作,如果为了能自动从代码中输入一份文档而增加了其他一些工作量,这未免有点得不偿失。通过对这些问题的分析,可以得出以下的解决方案:
- 需要手动给每个埋点加上注释 -> 自动填充代码 -> ESLint fix 功能 / VSCode 插件
- 需要手动去查每个埋点所对应的路由 -> 自动找到组件所对应的路由 -> Webpack 依赖分析
- 如果忘了给埋点加注释怎么办?-> 忘写注释有提示 -> ESLint 插件
到这一步解决问题的方法就已经变得明朗了。接下来让看一下 webpack 插件与 ESLint 插件的实现过程。
路由依赖分析
webpack 本身自带依赖分析,轻松就能拿到组件间的父子关系。
compiler.hooks.normalModuleFactory.tap('routeAnalysePlugin', (nmf) => {
nmf.hooks.afterResolve.tapAsync('routeAnalysePlugin', (result, callback) => {
const { resourceResolveData } = result;
// 子组件
const path = resourceResolveData.path;
// 父组件
const fatherPath = resourceResolveData.context.issuer;
// 只获取 vue 文件的依赖关系
if (/.vue/.test(path) && /.vue/.test(fatherPath)) {
// 将组件间的父子关系存到变量中
}
});
});
把组件之间的依赖关系拼成我们想要的数据格式
[
{
"path": "class="lazy" data-src/views/register-v2/index.vue",
"deps": [
{
"path": "class="lazy" data-src/components/landing-banner/index.vue",
"deps": []
}
]
}
// ...
]
组件之间的依赖关系有了,接下来就是找到组件和路由的对应关系,这里我们用 AST 来解析路由文件,获取路由和组件的对应关系。
// 遍历路由文件
for (let i = 0; i < this.routePaths.length; i++) {
// ...
traverse(ast, {
enter(path) {
// 找出组件和路由的对应关系
path.node.properties.forEach((item) => {
// 组件
if (item.key.name === 'component') {
}
// 路由地址
if (item.key.name === 'path') {
}
});
},
});
}
同样地,把组件与路由的映射关系拼成合适的数据格式。
{
"class="lazy" data-src/views/register-v3/index.vue": "/register"
// ...
}
再将路由的映射关系和组件间的依赖关系整合到一起,得出每个组件与路由的对应关系。
{
"class="lazy" data-src/components/landing-banner/index.vue": [
"/register_v2",
"/register"
//...
]
// ...
}
因为使用 AST 遍历的方式来解析路由文件,目前支持的解析的路由文件写法有以下四种,基本上满足了当前的场景:
const page1 = (resolve) => {
require.ensure(
[],
() => {
resolve(require('page1.vue'));
},
'page1',
);
};
const page2 = () =>
import(
'page2.vue'
);
export default [
{ path: '/page1', component: page1 },
{ path: '/page2', component: page2 },
{
path: '/page3',
component: (resolve) => {
require.ensure(
[],
() => {
resolve(require('page3.vue'));
},
'page3',
);
},
},
{
path: '/page4',
component: () =>
import(
'page4.vue'
),
},
];
再得到了上面的对应关系之后,可以把埋点数据放到传到埋点管理平台上,从而实现一键查询:
编写 ESLint 插件
先来看看代码中埋点上报的三种方式:
// 神策 sdk
sensors.track('xxx', {});
// 挂载到 Vue 实例中
this.$sa.track('xxx', {});
// 装饰器
@SensorTrack('xxx', {})
观察上面三种方式,可以知道埋点上报是通过 track 函数和 SensorTrack 函数,所以我们的 ESLint 插件对这两个函数进行校验。
function create(context) {
// 调用 track 函数的对象
const checkList = ['sensor', 'sensors', '$sa', 'sa'];
return {
Literal: function (node) {
// ...
// 调用埋点函数而缺少注释时
if (
isNoComment &&
((isTrack && isSensor) || (is$Track && isThisExpression))
) {
context.report({
node,
messageId: 'missingComment',
fix: function (fixer) {
// 自动修复
},
});
}
// 使用修饰器但没有注释时
if (
callee.name === 'SensorTrack' &&
sourceCode.getCommentsBefore(node).length === 0
) {
context.report({
node,
messageId: 'missingComment',
fix: function (fixer) {
// 自动修复
},
});
}
},
};
}
看下完成后的效果:
效果对比
我们再来对比下优化前后的区别:
优化前 | 优化后 | |
---|---|---|
自动提取埋点信息,生成埋点文档 | ✅ | ✅ |
自动给埋点注释添加自定义 tag(@log) | ❌ | ✅ |
自动给埋点注释添加上报的埋点信息 | ❌ | ✅ |
自动给埋点注释添加路由信息 | ❌ | ✅ |
自动给埋点注释添加埋点描述信息 | ❌ | ❌ |
自动提示没有注释的埋点代码 | ❌ | ✅ |
优化之后除了整个流程基本都由工具自动完成,剩下一个埋点描述信息。因为埋点的描述信息只是为了让我们更好地理解这个埋点,本身并不在上报的代码中,所以工具没有办法自动生成,但是我们可以直接在产品提供的埋点文档中拷贝过来完成这一步。
总结
在项目中接入这个工具之后,可以快速地知道项目的埋点有哪些以及各个埋点所在的页面,也方便我们对埋点的梳理,同时利用导出的埋点数据开发后台应用,有效地提升了开发者效率。
这个工具的实现是在 JSDoc、webpack 和 ESLint 插件的加持下水到渠成的,说是水到渠成是因为一开始的想法只是做到第一步,先有个一键查询功能和能够输出一份文档用着先。但是第一版出来后发现要手动去处理这些埋点注释还是比较繁琐,恰巧平常开发中常见的 webpack 插件和 ESLint 插件可以很好地解决这些问题,于是便有路由依赖分析和 ESLint 插件。像是《牧羊少年奇幻之旅》中所说的,“如果你下定决心要做一件事情,整个宇宙都会合力帮助你。”
【推荐学习:web前端开发、编程基础视频教程】
以上就是工具分享:实现前端埋点的自动化管理的详细内容,更多请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341