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
英文地址,請注明出處:http://en.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…