<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          還在手動(dòng)埋點(diǎn)么?out 了!不到百行代碼實(shí)現(xiàn)自動(dòng)埋點(diǎn)

          共 7174字,需瀏覽 15分鐘

           ·

          2021-07-27 22:01

          埋點(diǎn)是一個(gè)常見的需求,就是在函數(shù)里面上報(bào)一些信息。像一些性能的埋點(diǎn),每個(gè)函數(shù)都要處理,很繁瑣。能不能自動(dòng)埋點(diǎn)呢?

          答案是可以的。埋點(diǎn)只是在函數(shù)里面插入了一段代碼,這段代碼不影響其他邏輯,這種函數(shù)插入不影響邏輯的代碼的手段叫做函數(shù)插樁。

          我們可以基于 babel 來(lái)實(shí)現(xiàn)自動(dòng)的函數(shù)插樁,在這里就是自動(dòng)的埋點(diǎn)。

          思路分析

          比如這樣一段代碼:

          import aa from 'aa';
          import * as bb from 'bb';
          import {cc} from 'cc';
          import 'dd';

          function a ({
              console.log('aaa');
          }

          class B {
              bb() {
                  return 'bbb';
              }
          }

          const c = () => 'ccc';

          const d = function ({
              console.log('ddd');
          }

          我們要實(shí)現(xiàn)埋點(diǎn)就是要轉(zhuǎn)成這樣:

          import _tracker2 from "tracker";
          import aa from 'aa';
          import * as bb from 'bb';
          import { cc } from 'cc';
          import 'dd';

          function a({
            _tracker2();

            console.log('aaa');
          }

          class B {
            bb() {
              _tracker2();

              return 'bbb';
            }

          }

          const c = () => {
            _tracker2();

            return 'ccc';
          };

          const d = function ({
            _tracker2();

            console.log('ddd');
          };

          有兩方面的事情要做:

          • 引入 tracker 模塊。如果已經(jīng)引入過(guò)就不引入,沒(méi)有的話就引入,并且生成個(gè)唯一 id 作為標(biāo)識(shí)符
          • 對(duì)所有函數(shù)在函數(shù)體開始插入 tracker 的代碼

          代碼實(shí)現(xiàn)

          掘金小冊(cè)《babel 插件通關(guān)秘籍》中有具體 api 的詳細(xì)介紹。

          模塊引入

          引入模塊這種功能顯然很多插件都需要,這種插件之間的公共函數(shù)會(huì)放在 helper,這里我們使用 @babel/helper-module-imports。

          const importModule = require('@babel/helper-module-imports');

          // 省略一些代碼
          importModule.addDefault(path, 'tracker',{
              nameHint: path.scope.generateUid('tracker')
          })

          首先要判斷是否被引入過(guò):在 Program 根結(jié)點(diǎn)里通過(guò) path.traverse 來(lái)遍歷 ImportDeclaration,如果引入了 tracker 模塊,就記錄 id 到 state,并用 path.stop 來(lái)終止后續(xù)遍歷;沒(méi)有就引入 tracker 模塊,用 generateUid 生成唯一 id,然后放到 state。

          當(dāng)然 default import 和 namespace import 取 id 的方式不一樣,需要分別處理下。

          我們把 tracker 模塊名作為參數(shù)傳入,通過(guò) options.trackerPath 來(lái)取。

          Program: {
              enter (path, state) {
                  path.traverse({
                      ImportDeclaration (curPath) {
                          const requirePath = curPath.get('source').node.value;
                          if (requirePath === options.trackerPath) {// 如果已經(jīng)引入了
                              const specifierPath = curPath.get('specifiers.0');
                              if (specifierPath.isImportSpecifier()) { 
                                  state.trackerImportId = specifierPath.toString();
                              } else if(specifierPath.isImportNamespaceSpecifier()) {
                                  state.trackerImportId = specifierPath.get('local').toString();// tracker 模塊的 id
                              }
                              path.stop();// 找到了就終止遍歷
                          }
                      }
                  });
                  if (!state.trackerImportId) {
                      state.trackerImportId  = importModule.addDefault(path, 'tracker',{
                          nameHint: path.scope.generateUid('tracker')
                      }).name; // tracker 模塊的 id
                      state.trackerAST = api.template.statement(`${state.trackerImportId}()`)();// 埋點(diǎn)代碼的 AST
                  }
              }
          }

          我們?cè)谟涗?tracker 模塊的 id 的時(shí)候,也生成調(diào)用 tracker 模塊的 AST,使用 template.statement.

          函數(shù)插樁

          函數(shù)插樁要找到對(duì)應(yīng)的函數(shù),這里要處理的有:ClassMethod、ArrowFunctionExpression、FunctionExpression、FunctionDeclaration 這些節(jié)點(diǎn)。

          當(dāng)然有的函數(shù)沒(méi)有函數(shù)體,這種要包裝一下,然后修改下 return 值。如果有函數(shù)體,就直接在開始插入就行了。

          'ClassMethod|ArrowFunctionExpression|FunctionExpression|FunctionDeclaration'(path, state) {
              const bodyPath = path.get('body');
              if (bodyPath.isBlockStatement()) { // 有函數(shù)體就在開始插入埋點(diǎn)代碼
                  bodyPath.node.body.unshift(state.trackerAST);
              } else { // 沒(méi)有函數(shù)體要包裹一下,處理下返回值
                  const ast = api.template.statement(`{${state.trackerImportId}();return PREV_BODY;}`)({PREV_BODY: bodyPath.node});
                  bodyPath.replaceWith(ast);
              }
          }

          這樣我們就實(shí)現(xiàn)了自動(dòng)埋點(diǎn)。

          效果演示

          我們來(lái)試下效果:

          const { transformFromAstSync } = require('@babel/core');
          const  parser = require('@babel/parser');
          const autoTrackPlugin = require('./plugin/auto-track-plugin');
          const fs = require('fs');
          const path = require('path');

          const sourceCode = fs.readFileSync(path.join(__dirname, './sourceCode.js'), {
              encoding'utf-8'
          });

          const ast = parser.parse(sourceCode, {
              sourceType'unambiguous'
          });

          const { code } = transformFromAstSync(ast, sourceCode, {
              plugins: [[autoTrackPlugin, {
                  trackerPath'tracker'
              }]]
          });

          console.log(code);

          效果如下:

          我們實(shí)現(xiàn)了自動(dòng)埋點(diǎn)!

          總結(jié)

          函數(shù)插樁是在函數(shù)中插入一段邏輯但不影響函數(shù)原本邏輯,埋點(diǎn)就是一種常見的函數(shù)插樁,我們完全可以用 babel 來(lái)自動(dòng)做。

          實(shí)現(xiàn)思路分為引入 tracker 模塊和函數(shù)插樁兩部分:

          引入 tracker 模塊需要判斷 ImportDeclaration 是否包含了 tracker 模塊,沒(méi)有的話就用 @babel/helper-module-import 來(lái)引入。

          函數(shù)插樁就是在函數(shù)體開始插入一段代碼,如果沒(méi)有函數(shù)體,需要包裝一層,并且處理下返回值。

          最后



          如果你覺得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          1. 點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)

          2. 歡迎加我微信「 sherlocked_93 」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...

          3. 關(guān)注公眾號(hào)「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。


          點(diǎn)個(gè)在看支持我吧,轉(zhuǎn)發(fā)就更好了


          瀏覽 42
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩精品av一区二区 | 免费无码一级A片大黄在线观看 | 日韩无码第三页 | 欧美激情人妻少妇中文字幕视频在线 | 人妻AV片 |