Babel 的工作流程是「解析(Parse)→转换(Transform)→生成(Generate)」,插件的核心是在「转换阶段」通过@babel/types(AST 节点工具)遍历 DOM 相关节点(如 JSXElement、Element),为其添加data-id属性。

本文环境是react+vite+pnpm

  1. 安装依赖
1
2
# 安装核心依赖
pnpm add @babel/core @babel/types @babel/traverse --save-dev
  1. 插件核心代码(babel-plugin-add-data-id.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// babel-plugin-add-data-id.js
import * as t from '@babel/types';

// 生成data-id
function generateDataId(node, path) {
const componentName =
path.findParent((p) => p.isJSXElement() && p.node.openingElement.name.name)
?.node.openingElement.name.name || 'unknown';
const nodeType = node.openingElement.name.name;
const random = Math.random().toString(36).slice(2, 8);
return `${componentName}-${nodeType}-${random}`;
}

// 支持添加prefix
export default function (babelApi, options = { prefix: '' }) {
const { types: t } = babelApi;
return {
visitor: {
JSXElement(path) {
const openingElement = path.node.openingElement;
// 过滤无需埋点的节点
const ignoreTags = ['Fragment', 'br', 'hr'];
const nodeName = openingElement.name.name;
if (ignoreTags.includes(nodeName)) return;

// 检查是否已有data-id
const hasDataId = openingElement.attributes.some(
(attr) => t.isJSXAttribute(attr) && attr.name.name === 'data-id'
);

if (hasDataId) return;

// 生成并添加data-id属性
const dataIdAttr = t.jsxAttribute(
t.jsxIdentifier('data-id'),
t.stringLiteral(`${options.prefix}${generateDataId(path.node, path)}`)
);
openingElement.attributes.push(dataIdAttr);
},
},
};
}

  1. vite.config.ts中添加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import addDataIdPlugin from './babel-plugin-add-data-id.js';

// https://vite.dev/config/
export default defineConfig({
plugins: [
react({
babel: {
plugins: [[addDataIdPlugin, { prefix: 'track-' }]],
},
}),
],
});

  1. 验证是否有效
    • 原始代码:
1
2
3
4
5
<div id="root">
<div class="home" >
<button >提交</button>
</div>
</div>
  • 编译后的代码:
1
2
3
4
5
<div id="root">
<div class="home" data-id="track-unknown-div-k53ym8">
<button data-id="track-div-button-gn0bwx">提交</button>
</div>
</div>