vite_react 插件 find_code 最终版本

vite_react 插件 find_code 最终版本

当初在开发一个大型项目的时候,第一次接触 vite 构建,由于系统功能很庞大,在问题排查上和模块开发上比较耗时,然后就开始找解决方案,find-code 插件方案就这样实现出来了,当时觉得很好使,开发也很方便,于是自己开始琢磨自己开发一下整个流程 现如今也是零碎花费了两天时间做出了初版本的 find_code 插件

源码如下

// index.ts
import fs from "fs/promises";
import parser from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
export const processFile = async (filePath: string, filePathIndexMap: any) => {try {// 读取文件内容const code = await fs.readFile(filePath, "utf8");// 解析代码生成 ASTconst ast = parser.parse(code, {sourceType: "module",plugins: ["jsx", "typescript"],});// 遍历 AST(traverse as any).default(ast, {JSXOpeningElement(path: any) {const line = path?.node?.loc?.start?.line;const value = `${filePath}:${line}`;const index = `${Object.keys(filePathIndexMap)?.length || 0}`;filePathIndexMap[index] = value;const pathAttribute = {type: "JSXAttribute",name: { type: "JSXIdentifier", name: "data-path" },value: {type: "StringLiteral",value: index,},};// 检查是否已经存在 path 属性,如果不存在则添加const existingPathAttribute = path.node.attributes.find((attr: any) => {return (attr?.name &&attr?.name.type === "JSXIdentifier" &&attr?.name.name === "data-path");});if (!existingPathAttribute) {path.node.attributes.push(pathAttribute);}},});// 生成新代码,设置 retainLines 为 true 避免生成不必要的转义序列const { code: newCode } = (generate as any).default(ast, {retainLines: true,jsescOption: {minimal: true,},});return newCode;} catch (error) {console.error("处理文件时出错:", error);}
};
// vite-plugin-react-line-column.ts
import { createFilter } from "@rollup/pluginutils";
import { execSync } from "child_process";
import type { Plugin } from "vite";
import { processFile } from "./index";
import { parse } from "url";const vitePluginReactLineColumn = (): Plugin => {const filePathIndexMap = {} as any;return {// 定义插件名称name: "vite-plugin-react-line-column",// 设置插件执行顺序为 'post',在其他插件之后执行enforce: "pre",// 仅在开发环境执行apply: "serve",// 转换代码的 hookasync transform(code, id) {const filter = createFilter(/\.(js|jsx|ts|tsx)$/);if (!filter(id)) {return null;}const transformedCode = (await processFile(id, filePathIndexMap)) as any;return {code: transformedCode,map: null,};},async configureServer(server) {// 提供接口获取文件路径和索引的映射server.middlewares.use("/getPathIndexMap", (req, res) => {res.setHeader("Content-Type", "application/json");res.end(JSON.stringify(filePathIndexMap));});// 提供接口给一个路径跳转到 vscodeserver.middlewares.use("/jumpToVscode", (req, res) => {const query = parse(req?.url as string, true).query;const filePath = query.path;console.log(filePath, "filePath");if (!filePath || filePath == "undefined") {res.statusCode = 400;return res.end(JSON.stringify({ success: false, message: "缺少路径参数" }));}try {// 构建打开文件的命令const command = `code -g "${filePath}"`;// 同步执行命令execSync(command);res.setHeader("Content-Type", "application/json");res.end(JSON.stringify({ success: true }));} catch (error) {res.statusCode = 500;res.end(JSON.stringify({ success: false, message: "打开文件失败" }));}});},};
};export default vitePluginReactLineColumn;
// 创建选择框
function createSelector() {const selector = document.createElement("div");selector.style.cssText = `position: fixed;border: 2px solid #007AFF;background: rgba(0, 122, 255, 0.1);pointer-events: none;z-index: 999999;display: none;`;document.body.appendChild(selector);return selector;
}// 初始化选择器
const selector = createSelector();
let isSelecting = false;
let selectedElement = null;
let pathIndexMap = {};const init = async () => {const response = await fetch("/getPathIndexMap");pathIndexMap = await response.json();
};/* 根据当前元素递归查找 他的parentNode 是否有 data-path 没有就继续 直到 查到 body 标签结束 */
function findParentDataPath(element) {if (!element) return null;if (element.nodeType !== 1 || element.tagName == "body") return null; // 确保是元素节点if (element.hasAttribute("data-path")) {return element.getAttribute("data-path");}return findParentDataPath(element.parentNode);
}document.addEventListener("click", (e) => {if (isSelecting && selectedElement) {console.log("[VSCode跳转插件] 回车键触发跳转");const dataIndex = selectedElement.getAttribute("data-path");const vscodePath = pathIndexMap[dataIndex];if (vscodePath) {fetch(`/jumpToVscode?path=${vscodePath}`);} else {/* 如果没有vscodePath 即没有找到data-path属性 */const dataIndex = findParentDataPath(selectedElement);const vscodePath = pathIndexMap[dataIndex];if (vscodePath) {fetch(`/jumpToVscode?path=${vscodePath}`);}}console.log("[VSCode跳转插件] vscodePath", vscodePath);isSelecting = false;selector.style.display = "none";selectedElement = null;}
});
// 监听快捷键
document.addEventListener("keydown", (e) => {if (e.altKey && e.metaKey) {console.log("[VSCode跳转插件] 选择模式已激活");isSelecting = true;selector.style.display = "block";document.body.style.cursor = "pointer";init();}// 添加回车键触发if (e.key === "Enter" && isSelecting && selectedElement) {console.log("[VSCode跳转插件] 回车键触发跳转");const dataIndex = selectedElement.getAttribute("data-path");const vscodePath = pathIndexMap[dataIndex];if (vscodePath) {fetch(`/jumpToVscode?path=${vscodePath}`);}console.log("[VSCode跳转插件] vscodePath", vscodePath);isSelecting = false;selector.style.display = "none";selectedElement = null;}
});document.addEventListener("keyup", (e) => {if (!e.altKey && !e.metaKey) {console.log("[VSCode跳转插件] 选择模式已关闭");isSelecting = false;selector.style.display = "none";selectedElement = null;}
});// 监听鼠标移动
document.addEventListener("mousemove", (e) => {if (!isSelecting) return;const element = document.elementFromPoint(e.clientX, e.clientY);if (element && element !== selectedElement) {selectedElement = element;const rect = element.getBoundingClientRect();selector.style.left = rect.left + "px";selector.style.top = rect.top + "px";selector.style.width = rect.width + "px";selector.style.height = rect.height + "px";console.log("[VSCode跳转插件] 当前选中元素:", element);}
});
// package.json 对应版本
{"name": "vite","private": true,"version": "0.0.0","type": "module","scripts": {"1": "node ./node/parser.js","2": "node ./node/index.ts","dev": "vite","build": "tsc -b && vite build","lint": "eslint .","preview": "vite preview"},"dependencies": {"@babel/traverse": "^7.28.3","@types/antd": "^0.12.32","antd": "^5.27.2","fs": "^0.0.1-security","path": "^0.12.7","react": "^19.1.1","react-dom": "^19.1.1","url": "^0.11.4"},"devDependencies": {"@babel/generator": "^7.28.3","@babel/parser": "^7.28.3","@eslint/js": "^9.33.0","@rollup/pluginutils": "^5.2.0","@types/node": "^24.3.0","@types/react": "^19.1.10","@types/react-dom": "^19.1.7","@vitejs/plugin-react": "^5.0.0","eslint": "^9.33.0","eslint-plugin-react-hooks": "^5.2.0","eslint-plugin-react-refresh": "^0.4.20","globals": "^16.3.0","typescript": "~5.8.3","typescript-eslint": "^8.39.1","vite": "^7.1.2"}
}
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import vitePluginReactLineColumn from "./plugin/vite-plugin-react-line-column.ts";
export default defineConfig({plugins: [react(), vitePluginReactLineColumn()],
});

实现思路

1. 首先我们可以先练习 怎么样将我们的 jsx 代码插入我们想要的一些属性进去
// 1. 解析我们的代码生成 AST
const ast = parser.parse(code, {sourceType: "module",plugins: ["jsx"],
});
 //  遍历 AST 有一个属性 JSXOpeningElement 就是我们的 jsx 标签(traverse as any).default(ast, {JSXOpeningElement(path: any) {const line = path?.node?.loc?.start?.line;const value = `${filePath}:${line}`;const index = `${Object.keys(filePathIndexMap)?.length || 0}`;filePathIndexMap[index] = value;const pathAttribute = {type: "JSXAttribute",name: { type: "JSXIdentifier", name: "data-path" },value: {type: "StringLiteral",value: index,},};// 检查是否已经存在 path 属性,如果不存在则添加const existingPathAttribute = path.node.attributes.find((attr: any) => {return (attr?.name &&attr?.name.type === "JSXIdentifier" &&attr?.name.name === "data-path");});if (!existingPathAttribute) {path.node.attributes.push(pathAttribute);}},});
//  生成的新代码 再转回去// 生成新代码,设置 retainLines 为 true 避免生成不必要的转义序列const { code: newCode } = (generate as any).default(ast, {retainLines: true,jsescOption: {minimal: true,},});

generate 函数中,我们传入了一个配置对象,其中:
retainLines: true
尽量保留原始代码的行号和格式,减少不必要的换行和格式化。
jsescOption: { minimal: true }
jsesc 是 @babel/generator 内部用于处理字符串转义的工具,
minimal: true 表示只对必要的字符进行转义,避免生成不必要的 Unicode 转义序列。
通过这些配置,可以确保生成的代码中不会出现乱码的 Unicode 转义序列。
请确保已经安装了所需的 Babel 相关依赖,如果没有安装,可以使用以下命令进行安装:

 npm install @babel/parser @babel/traverse @babel/generator
2. 然后我们使用 vite 插件 hook 来进行我们数据处理
    // 转换代码的 hookasync transform(code, id) {const filter = createFilter(/\.(js|jsx|ts|tsx)$/);if (!filter(id)) {return null;}const transformedCode = (await processFile(id, filePathIndexMap)) as any;return {code: transformedCode,map: null,};},

这里可以进行优化,就是已经获取到 code 了 就不需要将这个 path(id)传递给这个函数,可以直接优化这个函数直接接受 code 就行,不需要再读取文件

3. 使用 vite 插件 hook 来提供接口
    1、 收集所有索引和路径的映射接口2、 提供接口给一个路径跳转到 vscode
4. 实现 js 代码注入

使用纯 js 实现事件监听和命令执行

  1. 监听 快捷键 option + command 开启我们的选择模式 并调用接口获取映射关系
  2. 监听 鼠标移动 获取当前元素宽、高设置给这个 createSelector 的样式 让他展示出来
  3. 监听 鼠标点击事件 如果选择模式开启了 切 选中元素 获取这个元素的 data-path 属性然后根据映射关系调用 vscode 跳转接口 跳转到对应的代码即可
优化: 4. 由于antd组件只能给最外层添加data-path属性,所以当选择一些内部元素的时候,这边进行递归找寻他的上层元素是否含有data-path属性,直到找到body结束 待优化: 5. 影响到了打包 6. 如果是公共组件 需要换一个颜色进行区分,因为公共组件就算跳到对应位置也很难定位排查我们的问题

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95990.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python+DRVT 从外部调用 Revit:批量创建梁(2)

接着昨天的示例,继续创建梁,这次展示以椭圆弧、Nurbs为轴线。 创建以椭圆弧为轴线的梁 椭圆弧曲线的创建: # 创建椭圆弧 def CreateEllipse(ctx : MyContext, z: float) -> DB.Curve:"""create a horizontal partial el…

Flutter × 鸿蒙系统:一文搞懂如何将你的 App 移植到 HarmonyOS!

摘要 Flutter 是一个高效的跨平台框架,开发者可以使用同一套代码快速部署到 Android、iOS 等主流平台。随着华为鸿蒙系统(HarmonyOS)的崛起,越来越多开发者希望能将已有的 Flutter 应用迁移到鸿蒙生态中运行。目前,通过…

QML Charts组件之主题与动画

目录前言相关系列ChartView 概述:主题与动画示例一:主题设置(ChartTheme.qml)图表与主题设置主题切换部分示例二:动画设置(ChartAnimation.qml)图表与动画属性部分分类轴与柱状图数据部分交互与…

【论文阅读】Security of Language Models for Code: A Systematic Literature Review

Security of Language Models for Code: A Systematic Literature Review 该论文于2025年被CCF A类期刊TOSEM收录,作者来自南京大学和南洋理工大学。 概述 代码语言模型(CodeLMs)已成为代码相关任务的强大工具,其性能优于传统方法…

[光学原理与应用-422]:非线性光学 - 计算机中的线性与非线性运算

在计算机科学中,线性运算和非线性运算是两类核心的数学操作,它们在算法设计、数据处理、机器学习等领域有广泛应用。两者的核心区别在于是否满足叠加原理(即输入信号的线性组合的输出是否等于输出信号的线性组合)。以下是详细解释…

Day21_【机器学习—决策树(3)—剪枝】

决策树剪枝是一种防止决策树过拟合的一种正则化方法;提高其泛化能力。决策树在训练过程中如果生长过深、过于复杂,会过度拟合训练数据中的噪声和异常值,导致在新数据上表现不佳。剪枝通过简化树结构,去除不必要的分支,…

从零构建企业级LLMOps平台:LMForge——支持多模型、可视化编排、知识库与安全审核的全栈解决方案

🚀 从零构建企业级LLMOps平台:LMForge——支持多模型、可视化编排、知识库与安全审核的全栈解决方案 🔗 项目地址:https://github.com/Haohao-end/LMForge-End-to-End-LLMOps-Platform-for-Multi-Model-Agents ⭐ 欢迎 Star &…

如何使显示器在笔记本盖上盖子时还能正常运转

1、搜索找到控制面板,打开进入 2、找到硬件和声音,进入 3、选择电源选项 4、选择 选择关闭笔记本计算机盖的功能 5、把关闭子盖时,改成不采取任何操作 参考链接:笔记本电脑合上盖子外接显示器依然能够显示设置_笔记本合上外接显示…

FPGA学习笔记——SDR SDRAM的读写(调用IP核版)

目录 一、任务 二、需求分析 三、Visio图 四、具体分析 1.需要注意的问题 (1)器件SDRAM需要的时钟 (2)跨时钟域(异步FIFO) 2.模块分析和调用 (1)SDR SDRAM IP核调用 &…

离散数学学习指导与习题解析

《离散数学学习指导与习题解析(第2版)》是屈婉玲、耿素云、张立昂编著的《离散数学(第2版)》的配套参考书,旨在为学生提供系统的学习指导和丰富的习题解析。本书内容全面,涵盖数理逻辑、集合论、代数结构、…

Qt网络通信服务端与客户端学习

Qt网络通信服务端与客户端学习 一、项目概述 本项目基于Qt框架实现了TCP服务端与客户端的基本通信,涵盖连接、消息收发、断开管理等功能,适合初学者系统学习Qt网络模块的实际用法。 二、项目结构 52/ 服务端:main.cpp、widget.cpp、widget.h5…

神马 M60S++ 238T矿机参数解析:高效SHA-256算法比拼

1. 算法与适用币种神马 M60S 238T采用SHA-256算法,适用于挖掘主流的加密货币,包括比特币(BTC)和比特币现金(BCH)。SHA-256(安全哈希算法256位)是一种广泛应用于比特币等加密货币挖矿…

[特殊字符] 深入理解操作系统核心特性:从并发到分布式,从单核到多核的全面解析

🚀 深入理解操作系统核心特性:从并发到分布式,从单核到多核的全面解析💡 前言:操作系统是计算机的灵魂,它就像一个优秀的管家,协调着硬件和软件之间的关系。今天,我们将深入探讨操作…

人工智能机器学习——聚类

一、无监督学习(Unsupervised Learning)机器学习的一种方法,没有给定事先标记过的训练示例,自动对输入的数据进行分类或分群。优点: 算法不受监督信息(偏见)的约束,可能考虑到新的信息不需要标签数据&#…

优化MySQL分区表备份流程详解

在大型数据驱动应用中,MySQL分区表是优化查询和维护历史的常见选择。但随之而来的数据备份问题却让许多开发者头疼:如何确保分散在不同分区的数据能完整、一致地被备份,并在需要时快速恢复?手动处理不仅繁琐,而且极易出…

用 Go + HTML 实现 OpenHarmony 投屏(hdckit-go + WebSocket + Canvas 实战)

本文带你用 Go HTML/WebSocket 从零实现一个 OpenHarmony 设备投屏 Demo:Go 侧用 hdckit-go 连接设备并抓取屏幕帧(UiDriver),通过 WebSocket 二进制实时推送到浏览器,前端用 Canvas 渲染,并根据设备分辨率…

运筹学——求解线性规划的单纯形法

单纯形法的原理 先来举个例子: 用单纯形法求解下面线性规划问题的最优解:注释:解的过程是反复迭代的过程,如果第一次迭代没有理解也没关系,再继续看第二次迭代,和第三次迭代,每次迭代的流程都是…

Python GUI 框架 -- DearPyGui 简易入门

DearPyGui 关于 DPG 是一个简单且功能强大的 Python 图形用户界面框架。 与其他Python图形用户界面库相比,DPG具有以下独特之处: GPU 渲染多线程高度可定制内置开发人员工具:主题检查、资源检查、运行时指标带有数百种小部件组合的 70 多…

gcloud cli 使用 impersonate模拟 服务帐号

什么是模拟服务帐号 众所周知, gcloud 登陆的方式有两种 使用个人帐号, 通常是1个邮箱地址使用一个service account 通常是1个 json key 文件 所谓模式服务帐号意思就是, 让操作人员用个人帐号登陆, 但是登陆后所有的操作都是基于…

idf--esp32的看门狗menuconfig

1.Interrupt Watchdog Timeout (ms):意思是中断看门狗,也就是专门监管中断响应时间的看门狗,如果某个中断服务程序超过了这个运行时间,就会导致程序重启。2.红框是任务看门狗的最大看门时间,超过时间就会警告&#xff…