Développement d'applications de bureau Electron dans Cursor : IPC, isolation de contexte et mise à jour automatique

Electron vous permet de créer des applications de bureau multiplateformes en utilisant les technologies web que vous connaissez déjà : HTML, CSS et JavaScript. Cursor, un éditeur IDE alimenté par IA, simplifie le développement Electron en générant l'échafaudage de projets, les gestionnaires IPC, les scripts de préchargement avec isolation de contexte, et en automatisant le pipeline d'empaquetage et de mise à jour automatique. Ce guide couvre les processus principal et de rendu, la communication IPC sécurisée, l'isolation de contexte, l'empaquetage avec electron-builder, les stratégies de mise à jour automatique, et les flux de travail d'interface utilisateur de bureau assistée par IA.
Prérequis
Avant de commencer, assurez-vous de disposer des éléments suivants :
- Cursor (version la plus récente recommandée) comme éditeur IDE
- Node.js 18 ou supérieur
- Connaissance de base de JavaScript, TypeScript et npm
- Un certificat de signature de code (optionnel, mais requis pour la mise à jour automatique sur macOS et Windows)
Si vous débutez avec Electron, demandez à Cursor d'expliquer le modèle de processus principal et de rendu avant de commencer à coder. Comprendre la frontière de processus dès le départ évite les erreurs de sécurité et d'architecture Electron les plus courantes.
Comprendre le modèle de processus d'Electron
Les applications Electron consistent en deux types de processus :
- Processus principal : S'exécute dans Node.js, a un accès complet au système d'exploitation, gère le cycle de vie de l'application et crée les fenêtres du navigateur.
- Processus de rendu : S'exécute dans un moteur de rendu Chromium, affiche votre interface utilisateur web et a un accès restreint aux API natives par défaut.
Les deux processus communiquent via la communication inter-processus (IPC). Cette séparation est délibérée : elle maintient les API natives dangereuses à l'écart du contenu web non fiable.
Échafaudage de projet dans Cursor
Demandez à Cursor d'échafauder un projet Electron TypeScript :
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 génère la structure de répertoire et les fichiers de configuration :
my-electron-app/
electron/
main.ts
preload.ts
src/
index.html
renderer.ts
package.json
electron-builder.yml
tsconfig.json
Le package.json inclut les scripts et dépendances essentiels :
{
"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"
}
}
Spécifiez toujours le champ main dans package.json. S'il manque, Electron utilise par défaut index.js à la racine du projet, ce qui peut charger du code inattendu pendant le développement. Déclarez explicitement le point d'entrée compilé pour éviter toute confusion.
Le processus principal
Le processus principal est le point d'entrée de votre application Electron. Il crée les fenêtres, gère le cycle de vie de l'application et enregistre les gestionnaires IPC.
Création de fenêtres de navigateur
// 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();
}
});
Demandez à Cursor de réviser votre configuration BrowserWindow pour les problèmes de sécurité. Une invite comme « Auditez mes webPreferences Electron pour les bonnes pratiques de sécurité » détectera les paramètres dangereux comme nodeIntegration: true ou un script de préchargement manquant.
Enregistrement des gestionnaires IPC
Les gestionnaires IPC dans le processus principal reçoivent les messages du rendu et renvoient les résultats. Définissez un contrat typé pour que les deux côtés s'accordent sur l'interface API.
// 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();
}
});
}
Ne passez jamais l'objet BrowserWindow ou d'autres API Electron directement via IPC au rendu. Encapsulez toujours les opérations natives dans des gestionnaires typés et renvoyez des données simples. Passer des objets vivants à travers la frontière de processus crée des risques graves de sécurité et de fuite de mémoire.
Isolation de contexte et scripts de préchargement
L'isolation de contexte est la principale frontière de sécurité d'Electron. Lorsqu'elle est activée, l'objet window du rendu est complètement séparé du scope du script de préchargement. Le seul moyen d'exposer des fonctionnalités natives au rendu est via l'API contextBridge.
Écriture d'un script de préchargement sécurisé
// 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);
Utilisation de l'API dans le rendu
// 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);
}
});
Générez le contrat de type complet de préchargement et de rendu en une seule fois en demandant à Cursor : « Créez une interface TypeScript partagée entre mon script de préchargement Electron et le rendu qui liste tous les canaux IPC que j'utilise. Placez-la dans un fichier de types partagés. » Cela garde les deux côtés synchronisés et détecte les inadéquations à la compilation.
N'utilisez jamais nodeIntegration: true ou contextIsolation: false dans une application Electron en production. Ces paramètres exposent les API Node.js directement au contenu web, ce qui est une vulnérabilité de sécurité critique si votre application charge des URL distantes ou traite des entrées utilisateur non fiables.
Empaquetage avec electron-builder
electron-builder compile votre application en installateurs pour macOS (.dmg), Windows (.exe, .nsis) et Linux (.AppImage, .deb). La configuration se trouve dans un seul fichier YAML ou JSON.
Fichier de configuration
# 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
Construction pour toutes les plateformes
# Build for current platform
npm run package
# Build for macOS (requires macOS host)
npm run package:mac
# Build for Windows
npm run package:win
Demandez à Cursor de générer un flux de travail GitHub Actions qui construit votre application Electron pour macOS, Windows et Linux à chaque push de tag. Une invite comme « Créez un flux de travail CI qui exécute electron-builder sur trois plateformes et télécharge les artefacts vers une release GitHub » produit un fichier de flux de travail prêt à l'emploi.
La construction d'installateurs macOS nécessite une machine macOS avec un certificat Apple Developer valide. La compilation croisée depuis Linux ou Windows n'est pas prise en charge pour les builds macOS signés. Planifiez votre infrastructure CI en conséquence.
Mise à jour automatique avec electron-updater
La mise à jour automatique permet aux utilisateurs de recevoir de nouvelles versions sans télécharger manuellement les installateurs. Le package electron-updater s'intègre aux releases GitHub ou à n'importe quel serveur de fichiers statique.
Configuration de la mise à jour automatique
// 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();
});
});
Interface de mise à jour côté rendu
// 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>
);
}
Utilisez autoUpdater.autoDownload = false pour donner aux utilisateurs le contrôle sur les téléchargements. Les téléchargements en arrière-plan forcés peuvent consommer de la bande passante sur les connexions limitées et frustrer les utilisateurs. Affichez toujours une notification et laissez l'utilisateur choisir quand télécharger.
La mise à jour automatique nécessite la signature de code sur macOS. Les applications non signées échoueront silencieusement à l'étape de vérification de mise à jour et electron-updater signalera une erreur. Sur Windows, les applications non signées peuvent se mettre à jour mais déclenchent des avertissements SmartScreen qui érodent la confiance des utilisateurs.
Génération d'interface utilisateur de bureau assistée par IA
Cursor excelle dans la génération de composants d'interface utilisateur de qualité bureau. Décrivez l'interface souhaitée et Cursor produit des composants React accessibles et navigables au clavier, stylés pour un contexte de bureau.
Génération d'une barre de titre personnalisée
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 génère :
// 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>
);
}
Lors de la génération d'interface utilisateur de bureau dans Cursor, mentionnez toujours la plateforme. Les barres de titre se comportent différemment sur macOS (boutons feux tricolores à gauche) et sur Windows (boutons système à droite). Spécifier la plateforme produit des mises en page correctes et appropriées.
Optimisation des performances
Les applications Electron peuvent sembler lourdes si vous ne gérez pas soigneusement les ressources. Cursor vous aide à identifier et corriger les goulets d'étranglement de performances.
Réduction de l'utilisation de la mémoire et du CPU
// 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);
}
N'appliquez pas aveuglément tous les commutateurs commandLine. La désactivation de la composition GPU peut casser les animations CSS et le rendu canvas WebGL. Testez votre interface utilisateur complète après chaque changement de performance et révoquez les commutateurs qui causent des régressions visuelles.
Liste de contrôle de sécurité
Exécutez cette invite dans Cursor pour auditer l'ensemble de votre projet Electron pour les problèmes de sécurité :
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 scanne votre base de code et renvoie une liste de problèmes priorisée avec des références de fichiers spécifiques et des correctifs suggérés.
Ajoutez la liste de contrôle de sécurité Electron comme fichier .cursorrules à la racine de votre projet. Cursor appliquera automatiquement ces règles lors de la génération de nouveau code Electron, empêchant les régressions de sécurité au fil du temps.
Résumé
La création d'applications de bureau Electron dans Cursor combine la puissance multiplateforme des technologies web avec la vitesse de développement assistée par IA. En suivant les modèles de ce guide — IPC à isolation de contexte sécurisé, contrats de préchargement typés, empaquetage electron-builder et mise à jour automatique contrôlée par l'utilisateur — vous pouvez livrer des applications de bureau professionnelles plus rapidement que jamais. Gardez contextIsolation activé, nodeIntegration désactivé, et laissez Cursor auditer votre code pour la sécurité et les performances à chaque étape.