前端(vue)入門到精通課程,老師在線輔導(dǎo):聯(lián)系老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API調(diào)試工具:點(diǎn)擊使用
埋點(diǎn)一直是 H5 項(xiàng)目中的重要一環(huán),埋點(diǎn)數(shù)據(jù)更是后期改善業(yè)務(wù)和技術(shù)優(yōu)化的重要基礎(chǔ)。【推薦學(xué)習(xí):web前端、編程教學(xué)】
在日常的工作中,經(jīng)常會有產(chǎn)品或者業(yè)務(wù)的同學(xué)來問,“這個項(xiàng)目現(xiàn)在有哪些埋點(diǎn)?”,“這個埋點(diǎn)用在哪些地方?”像這樣的問題基本上都是問一次查一次代碼,效率很低。
這也許跟埋點(diǎn)本身的性質(zhì)有關(guān)系。埋點(diǎn)屬于相對獨(dú)立的功能,隨著迭代的進(jìn)行,開發(fā)者很難記住埋點(diǎn)的用途。開發(fā)者出于自測驗(yàn)證的需要,也得對項(xiàng)目中的埋點(diǎn)數(shù)據(jù)加以整理。因此結(jié)合當(dāng)前的場景,可以實(shí)現(xiàn)一個工具:通過對代碼進(jìn)行掃描,分析埋點(diǎn)相關(guān)的代碼,并對之加以處理,轉(zhuǎn)化成特定的數(shù)據(jù),供后續(xù)在其他的管理平臺中使用。
實(shí)現(xiàn)思路
這個工具大致可以分成三個部分,JSDoc 提取埋點(diǎn)、路由依賴分析和 ESLint 插件。
- JSDoc 是根據(jù) JavaScript 中的注釋信息,生成 API 文檔的一個工具。結(jié)合 JSDoc 的這一個特性,這個埋點(diǎn)工具把 JSDoc 作為核心部分,用于輸出代碼中的埋點(diǎn)數(shù)據(jù)。
- Webpack 插件作為輔助,為 JSDoc 提供路由信息。
- ESLint 插件則作為最后的檢驗(yàn),確保文件中的埋點(diǎn)代碼都有對應(yīng)的 JSDoc 注釋。
自定義 JSDoc 標(biāo)記埋點(diǎn)
我們知道,JSDoc 可以根據(jù)代碼中的注釋輸出一份文檔。首先我們自定義一個 JSDoc 的 tag 來標(biāo)注這是一個埋點(diǎn)的注釋,這樣后續(xù)處理時(shí)可以過濾掉其他注釋的干擾。結(jié)合具體項(xiàng)目中使用的代碼可以畫出這樣一個流程圖:
下面是具體的代碼實(shí)現(xiàn)的過程。
編寫 JSDoc 插件,自定義一個 tag:
// jsdoc.plugin.js // 自定義一個 @log,含有 @log 才是埋點(diǎn)的注釋 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) { // 對文件預(yù)處理 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 轉(zhuǎn) js } }, };
自定義 JSDoc 模版。
// publish.js exports.publish = function (taffyData, opts, tutorials) { // ... data().each(function (doclet) { // 有 log 這個 tag 的才是埋點(diǎn)注釋 if (doclet.meta && doclet.meta.log) { doclet.tags?.forEach((item) => { // 獲取對應(yīng)的路由地址 }); // 拿到埋點(diǎn)數(shù)據(jù) logData.push({}); } }); // 輸出 md 文檔 fs.writeFileSync(outpath, mdContent, 'utf8'); };
到這里,已經(jīng)可以完整地輸出代碼中的所有埋點(diǎn)了。此時(shí)再來看下目前這個工具的能力:
- 自動提取埋點(diǎn)信息,生成埋點(diǎn)文檔:✅
- 自動給埋點(diǎn)注釋添加自定義 tag(@log):❌
- 自動給埋點(diǎn)注釋添加上報(bào)的埋點(diǎn)信息:❌
- 自動給埋點(diǎn)注釋添加路由信息:❌
- 自動給埋點(diǎn)注釋添加埋點(diǎn)描述信息:❌
- 自動提示沒有注釋的埋點(diǎn)代碼:❌
通過上面的梳理我們可以看出:
- 需要手動給每個埋點(diǎn)加上注釋
- 需要手動去查每個埋點(diǎn)所對應(yīng)的路由
- 如果忘了給埋點(diǎn)加注釋怎么辦?
做這個工具的初衷,就是為省去一些重復(fù)繁瑣的工作,如果為了能自動從代碼中輸入一份文檔而增加了其他一些工作量,這未免有點(diǎn)得不償失。通過對這些問題的分析,可以得出以下的解決方案:
- 需要手動給每個埋點(diǎn)加上注釋 -> 自動填充代碼 -> ESLint fix 功能 / VSCode 插件
- 需要手動去查每個埋點(diǎn)所對應(yīng)的路由 -> 自動找到組件所對應(yīng)的路由 -> Webpack 依賴分析
- 如果忘了給埋點(diǎn)加注釋怎么辦?-> 忘寫注釋有提示 -> ESLint 插件
到這一步解決問題的方法就已經(jīng)變得明朗了。接下來讓看一下 webpack 插件與 ESLint 插件的實(shí)現(xiàn)過程。
路由依賴分析
webpack 本身自帶依賴分析,輕松就能拿到組件間的父子關(guān)系。
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 文件的依賴關(guān)系 if (/.vue/.test(path) && /.vue/.test(fatherPath)) { // 將組件間的父子關(guān)系存到變量中 } }); });
把組件之間的依賴關(guān)系拼成我們想要的數(shù)據(jù)格式
[ { "path": "src/views/register-v2/index.vue", "deps": [ { "path": "src/components/landing-banner/index.vue", "deps": [] } ] } // ... ]
組件之間的依賴關(guān)系有了,接下來就是找到組件和路由的對應(yīng)關(guān)系,這里我們用 AST 來解析路由文件,獲取路由和組件的對應(yīng)關(guān)系。
// 遍歷路由文件 for (let i = 0; i < this.routePaths.length; i++) { // ... traverse(ast, { enter(path) { // 找出組件和路由的對應(yīng)關(guān)系 path.node.properties.forEach((item) => { // 組件 if (item.key.name === 'component') { } // 路由地址 if (item.key.name === 'path') { } }); }, }); }
同樣地,把組件與路由的映射關(guān)系拼成合適的數(shù)據(jù)格式。
{ "src/views/register-v3/index.vue": "/register" // ... }
再將路由的映射關(guān)系和組件間的依賴關(guān)系整合到一起,得出每個組件與路由的對應(yīng)關(guān)系。
{ "src/components/landing-banner/index.vue": [ "/register_v2", "/register" //... ] // ... }
因?yàn)槭褂?AST 遍歷的方式來解析路由文件,目前支持的解析的路由文件寫法有以下四種,基本上滿足了當(dāng)前的場景:
const page1 = (resolve) => { require.ensure( [], () => { resolve(require('page1.vue')); }, 'page1', ); }; const page2 = () => import( /* webpackChunkName: "page2" */ '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( /* webpackChunkName: "page4" */ 'page4.vue' ), }, ];
再得到了上面的對應(yīng)關(guān)系之后,可以把埋點(diǎn)數(shù)據(jù)放到傳到埋點(diǎn)管理平臺上,從而實(shí)現(xiàn)一鍵查詢:
編寫 ESLint 插件
先來看看代碼中埋點(diǎn)上報(bào)的三種方式:
// 神策 sdk sensors.track('xxx', {}); // 掛載到 Vue 實(shí)例中 this.$sa.track('xxx', {}); // 裝飾器 @SensorTrack('xxx', {})
觀察上面三種方式,可以知道埋點(diǎn)上報(bào)是通過 track 函數(shù)和 SensorTrack 函數(shù),所以我們的 ESLint 插件對這兩個函數(shù)進(jìn)行校驗(yàn)。
function create(context) { // 調(diào)用 track 函數(shù)的對象 const checkList = ['sensor', 'sensors', '$sa', 'sa']; return { Literal: function (node) { // ... // 調(diào)用埋點(diǎn)函數(shù)而缺少注釋時(shí) if ( isNoComment && ((isTrack && isSensor) || (is$Track && isThisExpression)) ) { context.report({ node, messageId: 'missingComment', fix: function (fixer) { // 自動修復(fù) }, }); } // 使用修飾器但沒有注釋時(shí) if ( callee.name === 'SensorTrack' && sourceCode.getCommentsBefore(node).length === 0 ) { context.report({ node, messageId: 'missingComment', fix: function (fixer) { // 自動修復(fù) }, }); } }, }; }
看下完成后的效果:
效果對比
我們再來對比下優(yōu)化前后的區(qū)別:
優(yōu)化前 | 優(yōu)化后 | |
---|---|---|
自動提取埋點(diǎn)信息,生成埋點(diǎn)文檔 | ✅ | ✅ |
自動給埋點(diǎn)注釋添加自定義 tag(@log) | ❌ | ✅ |
自動給埋點(diǎn)注釋添加上報(bào)的埋點(diǎn)信息 | ❌ | ✅ |
自動給埋點(diǎn)注釋添加路由信息 | ❌ | ✅ |
自動給埋點(diǎn)注釋添加埋點(diǎn)描述信息 | ❌ | ❌ |
自動提示沒有注釋的埋點(diǎn)代碼 | ❌ | ✅ |
優(yōu)化之后除了整個流程基本都由工具自動完成,剩下一個埋點(diǎn)描述信息。因?yàn)槁顸c(diǎn)的描述信息只是為了讓我們更好地理解這個埋點(diǎn),本身并不在上報(bào)的代碼中,所以工具沒有辦法自動生成,但是我們可以直接在產(chǎn)品提供的埋點(diǎn)文檔中拷貝過來完成這一步。
總結(jié)
在項(xiàng)目中接入這個工具之后,可以快速地知道項(xiàng)目的埋點(diǎn)有哪些以及各個埋點(diǎn)所在的頁面,也方便我們對埋點(diǎn)的梳理,同時(shí)利用導(dǎo)出的埋點(diǎn)數(shù)據(jù)開發(fā)后臺應(yīng)用,有效地提升了開發(fā)者效率。
這個工具的實(shí)現(xiàn)是在 JSDoc、webpack 和 ESLint 插件的加持下水到渠成的,說是水到渠成是因?yàn)橐婚_始的想法只是做到第一步,先有個一鍵查詢功能和能夠輸出一份文檔用著先。但是第一版出來后發(fā)現(xiàn)要手動去處理這些埋點(diǎn)注釋還是比較繁瑣,恰巧平常開發(fā)中常見的 webpack 插件和 ESLint 插件可以很好地解決這些問題,于是便有路由依賴分析和 ESLint 插件。像是《牧羊少年奇幻之旅》中所說的,“如果你下定決心要做一件事情,整個宇宙都會合力幫助你。”
【推薦學(xué)習(xí):web前端開發(fā)、編程基礎(chǔ)視頻教程】