工具分享:實現前端埋點的自動化管理

2022-12-07 18:00:52

前端(vue)入門到精通課程,老師線上輔導:聯絡老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:

埋點一直是 H5 專案中的重要一環,埋點資料更是後期改善業務和技術優化的重要基礎。【推薦學習:、】

在日常的工作中,經常會有產品或者業務的同學來問,「這個專案現在有哪些埋點?」,「這個埋點用在哪些地方?」像這樣的問題基本上都是問一次查一次程式碼,效率很低。

這也許跟埋點本身的性質有關係。埋點屬於相對獨立的功能,隨著迭代的進行,開發者很難記住埋點的用途。開發者出於自測驗證的需要,也得對專案中的埋點資料加以整理。因此結合當前的場景,可以實現一個工具:通過對程式碼進行掃描,分析埋點相關的程式碼,並對之加以處理,轉化成特定的資料,供後續在其他的管理平臺中使用。

實現思路

這個工具大致可以分成三個部分, 提取埋點、路由依賴分析和 外掛。

  • 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": "src/views/register-v2/index.vue",
    "deps": [
      {
        "path": "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') {
        }
      });
    },
  });
}
登入後複製

同樣地,把元件與路由的對映關係拼成合適的資料格式。

{
  "src/views/register-v3/index.vue": "/register"
  // ...
}
登入後複製

再將路由的對映關係和元件間的依賴關係整合到一起,得出每個元件與路由的對應關係。

{
  "src/components/landing-banner/index.vue": [
    "/register_v2",
    "/register"
    //...
  ]
  // ...
}
登入後複製

因為使用 AST 遍歷的方式來解析路由檔案,目前支援的解析的路由檔案寫法有以下四種,基本上滿足了當前的場景:

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'
      ),
  },
];
登入後複製

再得到了上面的對應關係之後,可以把埋點資料放到傳到埋點管理平臺上,從而實現一鍵查詢:

編寫 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 外掛。像是《牧羊少年奇幻之旅》中所說的,「如果你下定決心要做一件事情,整個宇宙都會合力幫助你。」

【推薦學習:、】

以上就是工具分享:實現前端埋點的自動化管理的詳細內容,更多請關注TW511.COM其它相關文章!