<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>

          【實戰(zhàn)】還在手動埋點(diǎn)么?out 了。不到百行代碼實現(xiàn)自動埋點(diǎn)

          共 7019字,需瀏覽 15分鐘

           ·

          2021-06-01 12:46

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

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

          我們可以基于 babel 來實現(xiàn)自動的函數(shù)插樁,在這里就是自動的埋點(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');
          }

          我們要實現(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)引入過就不引入,沒有的話就引入,并且生成個唯一 id 作為標(biāo)識符
          • 對所有函數(shù)在函數(shù)體開始插入 tracker 的代碼

          代碼實現(xiàn)

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

          模塊引入

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

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

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

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

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

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

          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
                  }
              }
          }

          我們在記錄 tracker 模塊的 id 的時候,也生成調(diào)用 tracker 模塊的 AST,使用 template.statement.

          函數(shù)插樁

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

          當(dāng)然有的函數(shù)沒有函數(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 { // 沒有函數(shù)體要包裹一下,處理下返回值
                  const ast = api.template.statement(`{${state.trackerImportId}();return PREV_BODY;}`)({PREV_BODY: bodyPath.node});
                  bodyPath.replaceWith(ast);
              }
          }

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

          效果演示

          我們來試下效果:

          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);

          效果如下:

          我們實現(xiàn)了自動埋點(diǎn)!

          總結(jié)

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

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

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

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

          代碼在這里,建議自己實現(xiàn)一遍。

          瀏覽 74
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  麻豆三级电影 | 亚洲色图一区二区三区 | 欧美色五月 | 搞基操逼摸奶黄色视频网站 | 婷婷五月天在线无码 |