Unverified 提交 d9b6b548 authored 作者: Nour Zakhma's avatar Nour Zakhma 提交者: GitHub

feat: Add React DevTools in development (#3112)

probleme : Before this change, developers couldn't inspect React components in the Electron app's DevTools. The DevTools only showed the Elements tab (DOM), not the ️ React Components tab. This made debugging React components much harder. solution: We added automatic loading of React DevTools extension when running in development mode, so the React Components tab is available in Electron DevTools. Two simple files changed: [main.ts]→ Added React DevTools extension loading code [package.json] → Added npm run dev script New script: "dev": "cross-env NODE_ENV=development npm start" This sets [NODE_ENV=development]( so React DevTools loads 📝 Steps to Enable This Feature For Developers Using Dyad Step 1: Install React DevTools Extension in Chrome Open Google Chrome Go to: [https://chromewebstore.google.com/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi] Click "Add to Chrome" Confirm the installation Step 2: Run the App in Dev Mode expected behavior <img width="1591" height="870" alt="Capture d&#39;écran 2026-04-01 152857" src="https://github.com/user-attachments/assets/341130e8-38ac-4d61-84cb-5415b248278d" /> <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3112" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end -->
上级 1e56c111
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
"bump": "node scripts/bump-version.mjs", "bump": "node scripts/bump-version.mjs",
"clean": "rimraf out scaffold/node_modules", "clean": "rimraf out scaffold/node_modules",
"start": "electron-forge start", "start": "electron-forge start",
"dev": "cross-env NODE_ENV=development npm start",
"dev:engine": "cross-env DYAD_ENGINE_URL=http://localhost:8080/v1 npm start", "dev:engine": "cross-env DYAD_ENGINE_URL=http://localhost:8080/v1 npm start",
"staging:engine": "cross-env DYAD_ENGINE_URL=https://staging---dyad-llm-engine-kq7pivehnq-uc.a.run.app/v1 npm start", "staging:engine": "cross-env DYAD_ENGINE_URL=https://staging---dyad-llm-engine-kq7pivehnq-uc.a.run.app/v1 npm start",
"package": "npm run clean && electron-forge package", "package": "npm run clean && electron-forge package",
......
import { app, BrowserWindow, dialog, Menu, protocol, net } from "electron"; import {
app,
BrowserWindow,
dialog,
Menu,
protocol,
net,
session,
} from "electron";
import * as path from "node:path"; import * as path from "node:path";
import { registerIpcHandlers } from "./ipc/ipc_host"; import { registerIpcHandlers } from "./ipc/ipc_host";
import dotenv from "dotenv"; import dotenv from "dotenv";
...@@ -87,6 +95,73 @@ if (process.defaultApp) { ...@@ -87,6 +95,73 @@ if (process.defaultApp) {
} }
export async function onReady() { export async function onReady() {
// Load React DevTools extension in development
if (process.env.NODE_ENV === "development") {
let chromeUserData: string;
// Determine Chrome extensions path based on platform
if (process.platform === "win32") {
chromeUserData = path.join(
process.env.LOCALAPPDATA || "",
"Google",
"Chrome",
"User Data",
"Default",
"Extensions",
);
} else if (process.platform === "darwin") {
// macOS
chromeUserData = path.join(
process.env.HOME || "",
"Library",
"Application Support",
"Google",
"Chrome",
"Default",
"Extensions",
);
} else {
// Linux
chromeUserData = path.join(
process.env.HOME || "",
".config",
"google-chrome",
"Default",
"Extensions",
);
}
// React DevTools extension ID
const reactDevToolsId = "fmkadmapgofadopljbjfkapdkoienihi";
const extensionsDir = path.join(chromeUserData, reactDevToolsId);
if (fs.existsSync(extensionsDir)) {
try {
const versions = fs.readdirSync(extensionsDir);
if (versions.length > 0) {
// Get the latest version using numeric sort to handle version boundaries (e.g., 9.0.0 vs 10.0.0)
const latestVersion = versions
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }))
.reverse()[0];
const extensionPath = path.join(extensionsDir, latestVersion);
await session.defaultSession.loadExtension(extensionPath, {
allowFileAccess: true,
});
logger.info("React DevTools loaded successfully");
} else {
logger.warn(
"React DevTools extension directory is empty. Install it in Chrome first.",
);
}
} catch (err) {
logger.error("Failed to load React DevTools:", err);
}
} else {
logger.warn(
"React DevTools extension not found. Install it in Chrome first.",
);
}
}
try { try {
const backupManager = new BackupManager({ const backupManager = new BackupManager({
settingsFile: getSettingsFilePath(), settingsFile: getSettingsFilePath(),
...@@ -275,6 +350,19 @@ const createWindow = () => { ...@@ -275,6 +350,19 @@ const createWindow = () => {
// backgroundColor: "#00000001", // backgroundColor: "#00000001",
// frame: false, // frame: false,
}); });
// In development, wait for DevTools to open, then reload the page once so React DevTools initializes correctly
if (process.env.NODE_ENV === "development") {
mainWindow.webContents.once("devtools-opened", () => {
setTimeout(() => {
const windowRef = mainWindow;
if (!windowRef?.isDestroyed()) {
windowRef?.webContents.reloadIgnoringCache();
}
}, 300);
});
mainWindow.webContents.openDevTools();
}
// and load the index.html of the app. // and load the index.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL); mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
...@@ -283,20 +371,32 @@ const createWindow = () => { ...@@ -283,20 +371,32 @@ const createWindow = () => {
path.join(__dirname, "../renderer/main_window/index.html"), path.join(__dirname, "../renderer/main_window/index.html"),
); );
} }
// Handle force-close message and development reload coordination
let forceCloseMessageSent = false;
let devToolsReloadedCount = 0;
mainWindow.webContents.on("did-finish-load", () => {
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
// Open the DevTools. // In dev, wait until AFTER the DevTools-triggered reload before sending the message
mainWindow.webContents.openDevTools(); if (devToolsReloadedCount === 0) {
devToolsReloadedCount++;
return; // Ignore first load, we will reload momentarily
}
} }
// Send force-close event if it was detected // Send force-close once after the correct load
if (pendingForceCloseData) { if (pendingForceCloseData && !forceCloseMessageSent) {
mainWindow.webContents.once("did-finish-load", () => { forceCloseMessageSent = true;
mainWindow?.webContents.send("force-close-detected", { const windowRef = mainWindow;
if (!windowRef?.isDestroyed()) {
windowRef?.webContents.send("force-close-detected", {
performanceData: pendingForceCloseData, performanceData: pendingForceCloseData,
}); });
}
pendingForceCloseData = null; pendingForceCloseData = null;
});
} }
});
// Enable native context menu on right-click // Enable native context menu on right-click
mainWindow.webContents.on("context-menu", (event, params) => { mainWindow.webContents.on("context-menu", (event, params) => {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论