@linhey/react-debug-inspector 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @linhey/react-debug-inspector 🎯
2
+
3
+ 一个轻量级的 React 调试辅助工具,让你在浏览器中直接识别组件、标签及其对应的源码行号。
4
+
5
+ ## 特性
6
+
7
+ - 🚀 **零配置注入**:通过 Babel 插件自动为每个 JSX 元素添加 `data-debug` 属性。
8
+ - 🎯 **瞄准模式**:悬浮显示元素标识,单击一键复制。
9
+ - 🌳 **零污染**:生产环境构建时自动剔除,不增加包体积。
10
+ - ⌨️ **快捷键支持**:支持 Esc 退出及 Alt+右键 快速复制。
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ npm install @linhey/react-debug-inspector --save-dev
16
+ ```
17
+
18
+ ## 使用方法
19
+
20
+ ### 1. 配置 Vite (vite.config.ts)
21
+
22
+ ```typescript
23
+ import { defineConfig } from 'vite';
24
+ import react from '@vitejs/plugin-react';
25
+ import debugInspector from '@linhey/react-debug-inspector';
26
+
27
+ export default defineConfig({
28
+ plugins: [
29
+ react({
30
+ babel: {
31
+ // 仅在开发环境下启用注入
32
+ plugins: process.env.NODE_ENV === 'development' ? [debugInspector] : []
33
+ }
34
+ }),
35
+ ],
36
+ });
37
+ ```
38
+
39
+ ### 2. 初始化交互界面 (main.tsx)
40
+
41
+ ```typescript
42
+ import { initInspector } from '@linhey/react-debug-inspector';
43
+
44
+ if (import.meta.env.DEV) {
45
+ initInspector();
46
+ }
47
+ ```
48
+
49
+ ## 交互说明
50
+
51
+ - **🎯 按钮**:位于右下角,点击进入/退出审查模式。
52
+ - **悬浮模式**:进入模式后,鼠标指向的元素将被高亮,并显示 `组件:标签:行号`。
53
+ - **单击左键**:复制标识到剪贴板并退出。
54
+ - **Esc**:退出模式。
55
+ - **Alt + 右键**:非模式下亦可直接复制标识。
56
+
57
+ ## License
58
+
59
+ MIT
@@ -0,0 +1,13 @@
1
+ declare function babelPluginDebugLabel(): {
2
+ visitor: {
3
+ FunctionDeclaration(path: any): void;
4
+ VariableDeclarator(path: any): void;
5
+ };
6
+ };
7
+
8
+ /**
9
+ * 初始化浏览器端的元素定位器交互界面
10
+ */
11
+ declare function initInspector(): void;
12
+
13
+ export { babelPluginDebugLabel as default, initInspector };
@@ -0,0 +1,13 @@
1
+ declare function babelPluginDebugLabel(): {
2
+ visitor: {
3
+ FunctionDeclaration(path: any): void;
4
+ VariableDeclarator(path: any): void;
5
+ };
6
+ };
7
+
8
+ /**
9
+ * 初始化浏览器端的元素定位器交互界面
10
+ */
11
+ declare function initInspector(): void;
12
+
13
+ export { babelPluginDebugLabel as default, initInspector };
package/dist/index.js ADDED
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ default: () => index_default,
24
+ initInspector: () => initInspector
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/babel-plugin.ts
29
+ function babelPluginDebugLabel() {
30
+ return {
31
+ visitor: {
32
+ FunctionDeclaration(path) {
33
+ const name = path.node.id?.name;
34
+ if (!name || name[0] !== name[0].toUpperCase()) return;
35
+ injectAllJSX(path, name);
36
+ },
37
+ VariableDeclarator(path) {
38
+ const name = path.node.id?.name;
39
+ if (!name || name[0] !== name[0].toUpperCase()) return;
40
+ const init = path.node.init;
41
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
42
+ injectAllJSX(path.get("init"), name);
43
+ }
44
+ }
45
+ }
46
+ };
47
+ function injectAllJSX(path, componentName) {
48
+ path.traverse({
49
+ JSXElement(jsxPath) {
50
+ const { openingElement } = jsxPath.node;
51
+ const tagName = getTagName(openingElement.name);
52
+ const line = openingElement.loc?.start.line || "0";
53
+ const debugId = `${componentName}:${tagName}:${line}`;
54
+ const alreadyHas = openingElement.attributes.some(
55
+ (attr) => attr.type === "JSXAttribute" && attr.name?.name === "data-debug"
56
+ );
57
+ if (alreadyHas) return;
58
+ openingElement.attributes.push({
59
+ type: "JSXAttribute",
60
+ name: { type: "JSXIdentifier", name: "data-debug" },
61
+ value: { type: "StringLiteral", value: debugId }
62
+ });
63
+ }
64
+ });
65
+ }
66
+ function getTagName(node) {
67
+ if (node.type === "JSXIdentifier") return node.name;
68
+ if (node.type === "JSXMemberExpression") {
69
+ return `${getTagName(node.object)}.${node.property.name}`;
70
+ }
71
+ return "unknown";
72
+ }
73
+ }
74
+
75
+ // src/runtime.ts
76
+ function initInspector() {
77
+ if (typeof window === "undefined") return;
78
+ let isInspecting = false;
79
+ const toggleBtn = document.createElement("button");
80
+ toggleBtn.innerHTML = "\u{1F3AF}";
81
+ toggleBtn.title = "\u5F00\u542F\u7EC4\u4EF6\u5B9A\u4F4D\u5668";
82
+ toggleBtn.style.cssText = `
83
+ position: fixed;
84
+ bottom: 24px;
85
+ right: 24px;
86
+ width: 44px;
87
+ height: 44px;
88
+ border-radius: 22px;
89
+ background: #0ea5e9;
90
+ color: white;
91
+ border: 2px solid white;
92
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
93
+ cursor: pointer;
94
+ z-index: 9999999;
95
+ font-size: 20px;
96
+ display: flex;
97
+ align-items: center;
98
+ justify-content: center;
99
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
100
+ `;
101
+ document.body.appendChild(toggleBtn);
102
+ const overlay = document.createElement("div");
103
+ overlay.style.cssText = `
104
+ position: fixed;
105
+ pointer-events: none;
106
+ z-index: 9999998;
107
+ background: rgba(14, 165, 233, 0.15);
108
+ border: 2px dashed #0ea5e9;
109
+ display: none;
110
+ transition: all 0.1s ease-out;
111
+ `;
112
+ document.body.appendChild(overlay);
113
+ const tooltip = document.createElement("div");
114
+ tooltip.style.cssText = `
115
+ position: fixed;
116
+ pointer-events: none;
117
+ z-index: 9999999;
118
+ background: #1e293b;
119
+ color: #38bdf8;
120
+ padding: 4px 10px;
121
+ border-radius: 6px;
122
+ font-size: 11px;
123
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
124
+ font-weight: 700;
125
+ box-shadow: 0 4px 6px rgba(0,0,0,0.2);
126
+ display: none;
127
+ white-space: nowrap;
128
+ transition: all 0.1s ease-out;
129
+ `;
130
+ document.body.appendChild(tooltip);
131
+ const stopInspecting = () => {
132
+ isInspecting = false;
133
+ toggleBtn.style.transform = "scale(1)";
134
+ toggleBtn.style.background = "#0ea5e9";
135
+ document.body.style.cursor = "";
136
+ overlay.style.display = "none";
137
+ tooltip.style.display = "none";
138
+ };
139
+ toggleBtn.onclick = () => {
140
+ isInspecting = !isInspecting;
141
+ if (isInspecting) {
142
+ toggleBtn.style.transform = "scale(0.9)";
143
+ toggleBtn.style.background = "#ef4444";
144
+ document.body.style.cursor = "crosshair";
145
+ } else {
146
+ stopInspecting();
147
+ }
148
+ };
149
+ window.addEventListener("mousemove", (e) => {
150
+ if (!isInspecting) return;
151
+ const target = e.target;
152
+ if (target === toggleBtn || target === overlay || target === tooltip) return;
153
+ const debugEl = target.closest("[data-debug]");
154
+ if (debugEl) {
155
+ const debugId = debugEl.getAttribute("data-debug") || "";
156
+ const rect = debugEl.getBoundingClientRect();
157
+ overlay.style.display = "block";
158
+ overlay.style.top = rect.top + "px";
159
+ overlay.style.left = rect.left + "px";
160
+ overlay.style.width = rect.width + "px";
161
+ overlay.style.height = rect.height + "px";
162
+ tooltip.style.display = "block";
163
+ tooltip.textContent = debugId;
164
+ tooltip.style.color = "#38bdf8";
165
+ const tooltipY = rect.top < 30 ? rect.bottom + 4 : rect.top - 28;
166
+ tooltip.style.top = tooltipY + "px";
167
+ tooltip.style.left = rect.left + "px";
168
+ } else {
169
+ overlay.style.display = "none";
170
+ tooltip.style.display = "none";
171
+ }
172
+ });
173
+ window.addEventListener(
174
+ "click",
175
+ (e) => {
176
+ if (!isInspecting) return;
177
+ const target = e.target;
178
+ if (target === toggleBtn) return;
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+ const debugEl = target.closest("[data-debug]");
182
+ if (debugEl) {
183
+ const debugId = debugEl.getAttribute("data-debug");
184
+ if (debugId) {
185
+ navigator.clipboard.writeText(debugId).then(() => {
186
+ tooltip.textContent = "\u2705 Copied!";
187
+ tooltip.style.color = "#10b981";
188
+ overlay.style.background = "rgba(16, 185, 129, 0.2)";
189
+ overlay.style.borderColor = "#10b981";
190
+ setTimeout(stopInspecting, 600);
191
+ });
192
+ }
193
+ } else {
194
+ stopInspecting();
195
+ }
196
+ },
197
+ { capture: true }
198
+ );
199
+ window.addEventListener("keydown", (e) => {
200
+ if (e.key === "Escape" && isInspecting) stopInspecting();
201
+ });
202
+ }
203
+
204
+ // src/index.ts
205
+ var index_default = babelPluginDebugLabel;
206
+ // Annotate the CommonJS export names for ESM import in node:
207
+ 0 && (module.exports = {
208
+ initInspector
209
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,182 @@
1
+ // src/babel-plugin.ts
2
+ function babelPluginDebugLabel() {
3
+ return {
4
+ visitor: {
5
+ FunctionDeclaration(path) {
6
+ const name = path.node.id?.name;
7
+ if (!name || name[0] !== name[0].toUpperCase()) return;
8
+ injectAllJSX(path, name);
9
+ },
10
+ VariableDeclarator(path) {
11
+ const name = path.node.id?.name;
12
+ if (!name || name[0] !== name[0].toUpperCase()) return;
13
+ const init = path.node.init;
14
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) {
15
+ injectAllJSX(path.get("init"), name);
16
+ }
17
+ }
18
+ }
19
+ };
20
+ function injectAllJSX(path, componentName) {
21
+ path.traverse({
22
+ JSXElement(jsxPath) {
23
+ const { openingElement } = jsxPath.node;
24
+ const tagName = getTagName(openingElement.name);
25
+ const line = openingElement.loc?.start.line || "0";
26
+ const debugId = `${componentName}:${tagName}:${line}`;
27
+ const alreadyHas = openingElement.attributes.some(
28
+ (attr) => attr.type === "JSXAttribute" && attr.name?.name === "data-debug"
29
+ );
30
+ if (alreadyHas) return;
31
+ openingElement.attributes.push({
32
+ type: "JSXAttribute",
33
+ name: { type: "JSXIdentifier", name: "data-debug" },
34
+ value: { type: "StringLiteral", value: debugId }
35
+ });
36
+ }
37
+ });
38
+ }
39
+ function getTagName(node) {
40
+ if (node.type === "JSXIdentifier") return node.name;
41
+ if (node.type === "JSXMemberExpression") {
42
+ return `${getTagName(node.object)}.${node.property.name}`;
43
+ }
44
+ return "unknown";
45
+ }
46
+ }
47
+
48
+ // src/runtime.ts
49
+ function initInspector() {
50
+ if (typeof window === "undefined") return;
51
+ let isInspecting = false;
52
+ const toggleBtn = document.createElement("button");
53
+ toggleBtn.innerHTML = "\u{1F3AF}";
54
+ toggleBtn.title = "\u5F00\u542F\u7EC4\u4EF6\u5B9A\u4F4D\u5668";
55
+ toggleBtn.style.cssText = `
56
+ position: fixed;
57
+ bottom: 24px;
58
+ right: 24px;
59
+ width: 44px;
60
+ height: 44px;
61
+ border-radius: 22px;
62
+ background: #0ea5e9;
63
+ color: white;
64
+ border: 2px solid white;
65
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
66
+ cursor: pointer;
67
+ z-index: 9999999;
68
+ font-size: 20px;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
73
+ `;
74
+ document.body.appendChild(toggleBtn);
75
+ const overlay = document.createElement("div");
76
+ overlay.style.cssText = `
77
+ position: fixed;
78
+ pointer-events: none;
79
+ z-index: 9999998;
80
+ background: rgba(14, 165, 233, 0.15);
81
+ border: 2px dashed #0ea5e9;
82
+ display: none;
83
+ transition: all 0.1s ease-out;
84
+ `;
85
+ document.body.appendChild(overlay);
86
+ const tooltip = document.createElement("div");
87
+ tooltip.style.cssText = `
88
+ position: fixed;
89
+ pointer-events: none;
90
+ z-index: 9999999;
91
+ background: #1e293b;
92
+ color: #38bdf8;
93
+ padding: 4px 10px;
94
+ border-radius: 6px;
95
+ font-size: 11px;
96
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
97
+ font-weight: 700;
98
+ box-shadow: 0 4px 6px rgba(0,0,0,0.2);
99
+ display: none;
100
+ white-space: nowrap;
101
+ transition: all 0.1s ease-out;
102
+ `;
103
+ document.body.appendChild(tooltip);
104
+ const stopInspecting = () => {
105
+ isInspecting = false;
106
+ toggleBtn.style.transform = "scale(1)";
107
+ toggleBtn.style.background = "#0ea5e9";
108
+ document.body.style.cursor = "";
109
+ overlay.style.display = "none";
110
+ tooltip.style.display = "none";
111
+ };
112
+ toggleBtn.onclick = () => {
113
+ isInspecting = !isInspecting;
114
+ if (isInspecting) {
115
+ toggleBtn.style.transform = "scale(0.9)";
116
+ toggleBtn.style.background = "#ef4444";
117
+ document.body.style.cursor = "crosshair";
118
+ } else {
119
+ stopInspecting();
120
+ }
121
+ };
122
+ window.addEventListener("mousemove", (e) => {
123
+ if (!isInspecting) return;
124
+ const target = e.target;
125
+ if (target === toggleBtn || target === overlay || target === tooltip) return;
126
+ const debugEl = target.closest("[data-debug]");
127
+ if (debugEl) {
128
+ const debugId = debugEl.getAttribute("data-debug") || "";
129
+ const rect = debugEl.getBoundingClientRect();
130
+ overlay.style.display = "block";
131
+ overlay.style.top = rect.top + "px";
132
+ overlay.style.left = rect.left + "px";
133
+ overlay.style.width = rect.width + "px";
134
+ overlay.style.height = rect.height + "px";
135
+ tooltip.style.display = "block";
136
+ tooltip.textContent = debugId;
137
+ tooltip.style.color = "#38bdf8";
138
+ const tooltipY = rect.top < 30 ? rect.bottom + 4 : rect.top - 28;
139
+ tooltip.style.top = tooltipY + "px";
140
+ tooltip.style.left = rect.left + "px";
141
+ } else {
142
+ overlay.style.display = "none";
143
+ tooltip.style.display = "none";
144
+ }
145
+ });
146
+ window.addEventListener(
147
+ "click",
148
+ (e) => {
149
+ if (!isInspecting) return;
150
+ const target = e.target;
151
+ if (target === toggleBtn) return;
152
+ e.preventDefault();
153
+ e.stopPropagation();
154
+ const debugEl = target.closest("[data-debug]");
155
+ if (debugEl) {
156
+ const debugId = debugEl.getAttribute("data-debug");
157
+ if (debugId) {
158
+ navigator.clipboard.writeText(debugId).then(() => {
159
+ tooltip.textContent = "\u2705 Copied!";
160
+ tooltip.style.color = "#10b981";
161
+ overlay.style.background = "rgba(16, 185, 129, 0.2)";
162
+ overlay.style.borderColor = "#10b981";
163
+ setTimeout(stopInspecting, 600);
164
+ });
165
+ }
166
+ } else {
167
+ stopInspecting();
168
+ }
169
+ },
170
+ { capture: true }
171
+ );
172
+ window.addEventListener("keydown", (e) => {
173
+ if (e.key === "Escape" && isInspecting) stopInspecting();
174
+ });
175
+ }
176
+
177
+ // src/index.ts
178
+ var index_default = babelPluginDebugLabel;
179
+ export {
180
+ index_default as default,
181
+ initInspector
182
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@linhey/react-debug-inspector",
3
+ "version": "1.0.0",
4
+ "description": "A developer tool to inspect React components in browser and jump to source code.",
5
+ "author": "linhey",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
15
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
16
+ "prepublishOnly": "npm run build"
17
+ },
18
+ "devDependencies": {
19
+ "@babel/core": "^7.24.0",
20
+ "tsup": "^8.0.2",
21
+ "typescript": "^5.4.2"
22
+ },
23
+ "peerDependencies": {
24
+ "react": ">=16.8.0"
25
+ },
26
+ "keywords": [
27
+ "react",
28
+ "debug",
29
+ "inspector",
30
+ "vite",
31
+ "babel-plugin"
32
+ ]
33
+ }