Zum Hauptinhalt springen

Electron Desktop-App-Entwicklung in Cursor: IPC, Context Isolation und Auto-Update

Electron Desktop-App-Entwicklung in Cursor

Electron ermöglicht die Erstellung plattformübergreifender Desktop-Anwendungen mit Web-Technologien, die Sie bereits kennen: HTML, CSS und JavaScript. Cursor, ein KI-gestützter IDE-Editor, optimiert die Electron-Entwicklung durch Projekt-Scaffolding, Generierung von IPC-Handlern, Schreiben von Context-Isolation-Preload-Skripten und Automatisierung der Verpackungs- und Auto-Update-Pipeline. Dieser Leitfaden behandelt Main- und Renderer-Prozesse, sichere IPC-Kommunikation, Context Isolation, Verpackung mit electron-builder, Auto-Update-Strategien und KI-gestützte Desktop-UI-Workflows.

Voraussetzungen​

Stellen Sie vor dem Start sicher, dass Sie Folgendes haben:

  • Cursor (aktuellste Version empfohlen) als IDE-Editor
  • Node.js 18 oder höher
  • Grundlegende Vertrautheit mit JavaScript, TypeScript und npm
  • Ein Code-Signaturzertifikat (optional, aber für Auto-Update unter macOS und Windows erforderlich)
tipp

Wenn Sie neu bei Electron sind, bitten Sie Cursor, das Main- und Renderer-Prozessmodell zu erklären, bevor Sie mit dem Programmieren beginnen. Das Verständnis der Prozessgrenze verhindert die häufigsten Electron-Sicherheits- und Architekturfehler.

Das Prozessmodell von Electron verstehen​

Electron-Anwendungen bestehen aus zwei Arten von Prozessen:

  • Main-Prozess: Läuft in Node.js, hat vollen Zugriff auf das Betriebssystem, verwaltet den Anwendungslebenszyklus und erstellt Browser-Fenster.
  • Renderer-Prozess: Läuft in einem Chromium-Renderer, rendert Ihre Web-UI und hat standardmäßig eingeschränkten Zugriff auf native APIs.

Die beiden Prozesse kommunizieren über Inter-Process Communication (IPC). Diese Trennung ist beabsichtigt: Sie hält gefährliche native APIs von nicht vertrauenswürdigen Web-Inhalten fern.

Projekt-Scaffolding in Cursor​

Bitten Sie Cursor, ein TypeScript-Electron-Projekt zu scaffolden:

Create a minimal Electron + TypeScript + Vite project.
Include a main process file, a preload script with contextBridge,
and a renderer entry point. Use electron-builder for packaging.

Cursor generiert die Verzeichnisstruktur und Konfigurationsdateien:

my-electron-app/
electron/
main.ts
preload.ts
src/
index.html
renderer.ts
package.json
electron-builder.yml
tsconfig.json

Die package.json enthält die wesentlichen Skripte und Abhängigkeiten:

{
"name": "my-electron-app",
"version": "1.0.0",
"main": "dist/electron/main.js",
"scripts": {
"dev": "concurrently \"vite\" \"wait-on tcp:5173 && cross-env NODE_ENV=development electron .\"",
"build": "tsc && vite build",
"package": "npm run build && electron-builder",
"package:mac": "npm run build && electron-builder --mac",
"package:win": "npm run build && electron-builder --win"
},
"devDependencies": {
"electron": "^31.0.0",
"electron-builder": "^24.13.3",
"typescript": "^5.4.0",
"vite": "^5.3.0",
"vite-plugin-electron": "^0.28.0"
}
}
warnung

Geben Sie immer das main-Feld in package.json an. Wenn es fehlt, verwendet Electron standardmäßig index.js im Projektstammverzeichnis, was während der Entwicklung unerwarteten Code laden kann. Deklarieren Sie explizit den kompilierten Einstiegspunkt, um Verwirrung zu vermeiden.

Der Main-Prozess​

Der Main-Prozess ist der Einstiegspunkt Ihrer Electron-Anwendung. Er erstellt Fenster, verarbeitet den App-Lebenszyklus und registriert IPC-Handler.

Erstellen von Browser-Fenstern​

// electron/main.ts
import { app, BrowserWindow, ipcMain, shell } from 'electron';
import * as path from 'path';
import { isDev } from './utils/env';

let mainWindow: BrowserWindow | null = null;

function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
show: false,
backgroundColor: '#1e1e2e',
titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});

mainWindow.once('ready-to-show', () => {
mainWindow?.show();
});

if (isDev) {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}

// Handle external links in the default browser, not a new Electron window
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
}

app.whenReady().then(() => {
createWindow();

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
tipp

Bitten Sie Cursor, Ihre BrowserWindow-Konfiguration auf Sicherheitsprobleme zu überprüfen. Ein Prompt wie „Überprüfe meine Electron webPreferences auf Sicherheits-Best-Practices" erkennt gefährliche Einstellungen wie nodeIntegration: true oder ein fehlendes Preload-Skript.

Registrieren von IPC-Handlern​

IPC-Handler im Main-Prozess empfangen Nachrichten vom Renderer und geben Ergebnisse zurück. Definieren Sie einen typisierten Vertrag, damit sich beide Seiten auf die API-Oberfläche einigen.

// electron/ipc-handlers.ts
import { ipcMain, dialog, BrowserWindow } from 'electron';
import * as fs from 'fs/promises';

export function registerIpcHandlers(): void {
ipcMain.handle('dialog:openFile', async () => {
const result = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [{ name: 'Text', extensions: ['txt', 'md', 'json'] }],
});
if (result.canceled) return null;

const content = await fs.readFile(result.filePaths[0], 'utf-8');
return { path: result.filePaths[0], content };
});

ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {
await fs.writeFile(filePath, content, 'utf-8');
return { success: true };
});

ipcMain.handle('window:minimize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
win?.minimize();
});

ipcMain.handle('window:toggleMaximize', (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win?.isMaximized()) {
win.unmaximize();
} else {
win?.maximize();
}
});
}
warnung

Übergeben Sie niemals das BrowserWindow-Objekt oder andere Electron-APIs direkt über IPC an den Renderer. Wickeln Sie native Operationen immer in typisierte Handler ein und geben Sie einfache Daten zurück. Das Übergeben von Live-Objekten über die Prozessgrenze hinweg schafft erhebliche Sicherheits- und Speicherleck-Risiken.

Context Isolation und Preload-Skripte​

Context Isolation ist die primäre Sicherheitsgrenze von Electron. Wenn aktiviert, ist das window-Objekt des Renderers vollständig vom Scope des Preload-Skripts getrennt. Der einzige Weg, native Funktionalität dem Renderer verfügbar zu machen, ist über die contextBridge-API.

Schreiben eines sicheren Preload-Skripts​

// electron/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

// Define the API surface exposed to the renderer
const api = {
// File operations
openFile: () => ipcRenderer.invoke('dialog:openFile'),
saveFile: (path: string, content: string) =>
ipcRenderer.invoke('file:save', path, content),

// Window controls
minimizeWindow: () => ipcRenderer.invoke('window:minimize'),
toggleMaximize: () => ipcRenderer.invoke('window:toggleMaximize'),

// Event subscriptions with cleanup
onUpdateAvailable: (callback: (info: { version: string }) => void) => {
const handler = (_event: unknown, info: { version: string }) => callback(info);
ipcRenderer.on('update-available', handler);
return () => ipcRenderer.removeListener('update-available', handler);
},
} as const;

// Expose a frozen, read-only API to the renderer
contextBridge.exposeInMainWorld('electronAPI', api);

Verbrauchen der API im Renderer​

// src/renderer.ts
// TypeScript declaration so the renderer knows about window.electronAPI
declare global {
interface Window {
electronAPI: {
openFile: () => Promise<{ path: string; content: string } | null>;
saveFile: (path: string, content: string) => Promise<{ success: boolean }>;
minimizeWindow: () => Promise<void>;
toggleMaximize: () => Promise<void>;
onUpdateAvailable: (callback: (info: { version: string }) => void) => () => void;
};
}
}

const openButton = document.getElementById('open-btn');
const editor = document.getElementById('editor') as HTMLTextAreaElement;
let currentFilePath: string | null = null;

openButton?.addEventListener('click', async () => {
const result = await window.electronAPI.openFile();
if (result) {
currentFilePath = result.path;
editor.value = result.content;
}
});

document.getElementById('save-btn')?.addEventListener('click', async () => {
if (currentFilePath) {
await window.electronAPI.saveFile(currentFilePath, editor.value);
}
});
tipp

Generieren Sie den gesamten Preload- und Renderer-Typvertrag auf einmal, indem Sie Cursor fragen: „Erstelle ein TypeScript-Interface, das zwischen meinem Electron-Preload-Skript und dem Renderer geteilt wird und jeden IPC-Kanal auflistet, den ich verwende. Lege es in einer gemeinsamen Typdatei ab." Dies hält beide Seiten synchron und erkennt Missverhältnisse zur Kompilierzeit.

warnung

Verwenden Sie niemals nodeIntegration: true oder contextIsolation: false in einer Electron-Produktions-App. Diese Einstellungen setzen Node.js-APIs direkt dem Web-Inhalt aus. Dies ist eine kritische Sicherheitslücke, wenn Ihre App jemals Remote-URLs lädt oder nicht vertrauenswürdige Benutzereingaben verarbeitet.

Verpackung mit electron-builder​

electron-builder kompiliert Ihre App in Installationsprogramme für macOS (.dmg), Windows (.exe, .nsis) und Linux (.AppImage, .deb). Die Konfiguration befindet sich in einer einzigen YAML- oder JSON-Datei.

Konfigurationsdatei​

# electron-builder.yml
appId: com.example.my-electron-app
productName: My Electron App
directories:
output: release
buildResources: build
files:
- dist/**/*
- dist/electron/**/*
extraMetadata:
main: dist/electron/main.js
mac:
category: public.app-category.productivity
target:
- dmg
- zip
hardenedRuntime: true
gatekeeperAssess: false
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
win:
target:
- nsis
artifactName: ${productName}-${version}-${arch}.${ext}
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
createDesktopShortcut: true
linux:
target:
- AppImage
- deb
category: Office

Build für alle Plattformen​

# Build for current platform
npm run package

# Build for macOS (requires macOS host)
npm run package:mac

# Build for Windows
npm run package:win
tipp

Bitten Sie Cursor, einen GitHub Actions-Workflow zu generieren, der Ihre Electron-App bei jedem Tag-Push für macOS, Windows und Linux baut. Ein Prompt wie „Erstelle einen CI-Workflow, der electron-builder auf drei Plattformen ausführt und Artefakte in ein GitHub-Release hochlädt" erzeugt eine einsatzbereite Workflow-Datei.

warnung

Das Erstellen von macOS-Installationsprogrammen erfordert einen macOS-Rechner mit einem gültigen Apple Developer-Zertifikat. Cross-Kompilierung von Linux oder Windows wird für signierte macOS-Builds nicht unterstützt. Planen Sie Ihre CI-Infrastruktur entsprechend.

Auto-Update mit electron-updater​

Auto-Update ermöglicht es Benutzern, neue Versionen zu erhalten, ohne Installationsprogramme manuell herunterzuladen. Das Paket electron-updater integriert sich mit GitHub-Releases oder einem beliebigen statischen Dateiserver.

Auto-Update einrichten​

// electron/updater.ts
import { autoUpdater } from 'electron-updater';
import { BrowserWindow } from 'electron';

export function setupAutoUpdater(mainWindow: BrowserWindow): void {
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;

autoUpdater.on('update-available', (info) => {
// Notify the renderer that an update is available
mainWindow.webContents.send('update-available', { version: info.version });
});

autoUpdater.on('update-not-available', () => {
mainWindow.webContents.send('update-not-available');
});

autoUpdater.on('download-progress', (progress) => {
mainWindow.webContents.send('download-progress', {
percent: Math.round(progress.percent),
});
});

autoUpdater.on('update-downloaded', () => {
// Ask the user, then quit and install
mainWindow.webContents.send('update-downloaded');
});

// Check for updates after startup
app.whenReady().then(() => {
autoUpdater.checkForUpdates();
});
}

// Expose control methods through IPC
ipcMain.handle('update:download', () => {
autoUpdater.downloadUpdate();
});

ipcMain.handle('update:install', () => {
setImmediate(() => {
app.isQuiting = true;
autoUpdater.quitAndInstall();
});
});

Renderer-seitige Update-UI​

// src/components/UpdateNotification.tsx
import { useEffect, useState } from 'react';

export function UpdateNotification() {
const [updateInfo, setUpdateInfo] = useState<{ version: string } | null>(null);
const [progress, setProgress] = useState(0);
const [downloaded, setDownloaded] = useState(false);

useEffect(() => {
const cleanup = window.electronAPI.onUpdateAvailable((info) => {
setUpdateInfo(info);
});

const progressHandler = (_event: unknown, data: { percent: number }) => {
setProgress(data.percent);
};
window.electronAPI.onDownloadProgress?.(progressHandler);

return cleanup;
}, []);

if (!updateInfo) return null;

return (
<div className="fixed bottom-4 right-4 rounded-lg border bg-white p-4 shadow-lg">
<p className="font-semibold">Version {updateInfo.version} is available</p>
{downloaded ? (
<button onClick={() => window.electronAPI.installUpdate?.()}>
Restart and Install
</button>
) : progress > 0 ? (
<div className="mt-2 h-2 rounded bg-gray-200">
<div className="h-2 rounded bg-blue-500" style={{ width: `${progress}%` }} />
</div>
) : (
<button onClick={() => window.electronAPI.downloadUpdate?.()}>
Download Update
</button>
)}
</div>
);
}
tipp

Verwenden Sie autoUpdater.autoDownload = false, um Benutzern die Kontrolle über Downloads zu geben. Erzwungene Hintergrund-Downloads können Bandbreite auf getakteten Verbindungen verbrauchen und Benutzer frustrieren. Zeigen Sie immer eine Benachrichtigung und lassen Sie den Benutzer wählen, wann heruntergeladen wird.

warnung

Auto-Update erfordert Code-Signierung unter macOS. Nicht signierte Apps schlagen bei der Update-Verifizierung stillschweigend fehl und electron-updater meldet einen Fehler. Unter Windows können nicht signierte Apps Updates durchführen, lösen jedoch SmartScreen-Warnungen aus, die das Vertrauen der Benutzer untergraben.

KI-gestützte Desktop-UI-Generierung​

Cursor zeichnet sich durch die Generierung von Desktop-qualitäts UI-Komponenten aus. Beschreiben Sie die gewünschte Oberfläche und Cursor produziert zugängliche, tastaturnavigierbare React-Komponenten, gestylt für einen Desktop-Kontext.

Generieren einer benutzerdefinierten Titelleiste​

Create a React component for a custom Electron title bar on Windows.
Include minimize, maximize/restore, and close buttons. Use inline SVG
icons. Wire each button to the window.electronAPI IPC methods.
Support keyboard shortcuts (Alt+F4 to close, Win+Up to maximize).

Cursor generiert:

// src/components/TitleBar.tsx
import { useEffect, useState } from 'react';

export function TitleBar({ title }: { title: string }) {
const [isMaximized, setIsMaximized] = useState(false);

useEffect(() => {
const checkMax = () => setIsMaximized(window.electronAPI.isMaximized());
checkMax();
const interval = setInterval(checkMax, 200);
return () => clearInterval(interval);
}, []);

return (
<div
className="flex h-8 items-center justify-between select-none"
style={{ WebkitAppRegion: 'drag' } as React.CSSProperties}
>
<span className="ml-3 text-sm font-medium text-gray-200">{title}</span>
<div
className="flex h-full"
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
>
<button
onClick={() => window.electronAPI.minimizeWindow()}
className="flex h-full w-12 items-center justify-center hover:bg-white/10"
aria-label="Minimize"
>
<svg width="12" height="12" viewBox="0 0 12 12">
<rect y="5" width="12" height="2" fill="currentColor" />
</svg>
</button>
<button
onClick={() => window.electronAPI.toggleMaximize()}
className="flex h-full w-12 items-center justify-center hover:bg-white/10"
aria-label={isMaximized ? 'Restore' : 'Maximize'}
>
{isMaximized ? (
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="3" width="8" height="8" stroke="currentColor" fill="none" />
<rect x="3" y="1" width="8" height="2" fill="currentColor" />
</svg>
) : (
<svg width="12" height="12" viewBox="0 0 12 12">
<rect x="1" y="1" width="10" height="10" stroke="currentColor" fill="none" />
</svg>
)}
</button>
<button
onClick={() => window.electronAPI.closeWindow()}
className="flex h-full w-12 items-center justify-center hover:bg-red-600"
aria-label="Close"
>
<svg width="12" height="12" viewBox="0 0 12 12">
<path d="M1 1 L11 11 M11 1 L1 11" stroke="currentColor" strokeWidth="2" />
</svg>
</button>
</div>
</div>
);
}
tipp

Erwähnen Sie beim Generieren von Desktop-UI in Cursor immer die Plattform. Titelleisten verhalten sich unterschiedlich unter macOS (Ampel-Buttons links) versus Windows (System-Buttons rechts). Das Angeben der Plattform erzeugt korrekte, plattformgerechte Layouts.

Performance-Optimierung​

Electron-Apps können schwerfällig wirken, wenn Sie Ressourcen nicht sorgfältig verwalten. Cursor hilft Ihnen, Performance-Engpässe zu identifizieren und zu beheben.

Reduzierung von Speicher- und CPU-Nutzung​

// electron/utils/performance.ts
import { app, BrowserWindow } from 'electron';

export function optimizePerformance(): void {
// Limit the number of background processes
app.commandLine.appendSwitch('disable-background-networking');
app.commandLine.appendSwitch('disable-renderer-backgrounding');

// Reduce GPU overhead for non-accelerated content
app.commandLine.appendSwitch('disable-gpu-compositing');

// Suspend background timers when the window is hidden
app.on('browser-window-blur', () => {
// Pause expensive operations
});

// Aggressively release memory when the app is idle
setInterval(() => {
const windows = BrowserWindow.getAllWindows();
if (windows.length === 0) {
// Trigger garbage collection if running with --inspect
if (global.gc) global.gc();
}
}, 60000);
}
warnung

Wenden Sie nicht blind alle commandLine-Schalter an. Das Deaktivieren von GPU-Compositing kann CSS-Animationen und WebGL-Canvas-Rendering brechen. Testen Sie Ihre vollständige UI nach jeder Performance-Änderung und machen Sie Schalter rückgängig, die visuelle Regressionen verursachen.

Sicherheits-Checkliste​

Führen Sie diesen Prompt in Cursor aus, um Ihr gesamtes Electron-Projekt auf Sicherheitsprobleme zu prüfen:

Review my Electron app for security vulnerabilities. Check for:
1. contextIsolation enabled
2. nodeIntegration disabled
3. sandbox enabled
4. CSP headers set
5. No remote modules loaded
6. preload script uses contextBridge only
7. All IPC handlers validate input
8. Webview tags disabled or sandboxed

Cursor scannt Ihre Codebasis und gibt eine priorisierte Liste von Problemen mit spezifischen Dateiverweisen und vorgeschlagenen Korrekturen zurück.

tipp

Fügen Sie die Electron-Sicherheits-Checkliste als .cursorrules-Datei zu Ihrem Projektstammverzeichnis hinzu. Cursor wird diese Regeln automatisch anwenden, wenn neuer Electron-Code generiert wird, und Sicherheitsrückgänge über die Zeit verhindern.

Zusammenfassung​

Die Entwicklung von Electron-Desktop-Anwendungen in Cursor kombiniert die plattformübergreifende Kraft von Web-Technologien mit KI-gestützter Entwicklungsgeschwindigkeit. Mit den Mustern dieses Leitfadens — sichere Context-Isolation-IPC, typisierte Preload-Verträge, electron-builder-Verpackung und benutzergesteuertes Auto-Update — können Sie professionelle Desktop-Apps schneller denn je ausliefern. Halten Sie contextIsolation an, nodeIntegration aus, und lassen Sie Cursor Ihren Code bei jedem Schritt auf Sicherheit und Performance prüfen.