electron
This guide provides opinionated, actionable best practices for building secure, performant, and maintainable Electron applications using modern patterns and consistent tooling.
المهارة نفسها
# Electron Best Practices
Electron development demands a disciplined approach to security, performance, and maintainability. This guide outlines the definitive best practices for our team, leveraging modern Electron features and tooling (targeting Electron 28+).
## 1. Project Setup & Structure
Always start with **Electron Forge** to standardize project structure, build pipelines, and stay aligned with the latest Electron APIs.
- **Scaffolding:** Use a modern template like Vite + TypeScript.
❌ BAD: Manual setup, outdated CLIs.
✅ GOOD:
```bash
npx create-electron-app@latest my-app --template=vite-typescript
```
- **File Naming:** Adhere to Electron's coding style for JavaScript files.
❌ BAD: `my_module.js`
✅ GOOD: `my-module.js`
## 2. Security Fundamentals (Non-Negotiable)
Security is paramount. Always enable context isolation and expose APIs safely.
- **Context Isolation (Mandatory):** Keep `contextIsolation` enabled. It's on by default since Electron 12.
❌ BAD: `new BrowserWindow({ webPreferences: { contextIsolation: false } })`
✅ GOOD: (Default behavior, no explicit setting needed unless overriding)
```javascript
// main.mjs
new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.mjs'),
sandbox: true // Strongly recommended
}
})
```
- **Safe API Exposure with `contextBridge`:** Never mutate the global `window` object directly. Use `contextBridge.exposeInMainWorld` and filter arguments.
❌ BAD: Exposing `ipcRenderer.send` directly.
```javascript
// preload.mjs
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('myAPI', {
send: ipcRenderer.send // ❌ Allows renderer to send arbitrary IPC messages
});
```
✅ GOOD: Expose specific, argument-filtered functions.
```javascript
// preload.mjs
import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electronAPI', {
loadPreferences: () => ipcRenderer.invoke('load-prefs'),
saveSettings: (settings) => {
// ✅ Validate and filter arguments before sending
if (typeof settings === 'object' && settings !== null) {
ipcRenderer.send('save-settings', settings);
} else {
console.error('Invalid settings object provided.');
}
}
});
// interface.d.ts (for TypeScript)
declare global {
interface Window {
electronAPI: {
loadPreferences: () => Promise<any>;
saveSettings: (settings: object) => void;
};
}
}
```
- **Content Security Policy (CSP):** Implement a strict CSP in your `index.html` or via `webRequest.onHeadersReceived`.
✅ GOOD:
```html
<!-- index.html -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'">
```
## 3. ES Modules (ESM) Adoption (Electron 28+)
Leverage native ESM for cleaner, more modern code.
- **Main Process:** Use `.mjs` extension or `"type": "module"` in `package.json`.
- **`await` for Pre-Ready APIs:** ESM imports are asynchronous. Ensure critical APIs (e.g., `app.setPath`) are `await`ed before `app.whenReady()`.
❌ BAD:
```javascript
// main.mjs
import './setup-paths.mjs'; // May resolve after app is ready
app.whenReady().then(() => { /* ... */ });
```
✅ GOOD:
```javascript
// main.mjs
import { app } from 'electron';
await import('./setup-paths.mjs'); // Guarantees execution before ready
app.whenReady().then(() => { /* ... */ });
```
- **Preload Scripts:** Always use the `.mjs` extension for ESM preload scripts.
- **Sandboxed Preloads:** Cannot use ESM imports. Bundle them if external modules are needed.
- **Context Isolation:** Required for dynamic Node.js ESM imports in unsandboxed preloads.
❌ BAD: `preload.js` with `import` statements.
✅ GOOD: `preload.mjs`
## 4. IPC Communication
Use `ipcMain.handle` and `ipcRenderer.invoke` for explicit request-response patterns.
- **Request-Response:**
❌ BAD: Using `ipcRenderer.send` for requests that expect a response.
```javascript
// renderer.js
ipcRenderer.send('get-data', someId);
ipcRenderer.on('data-response', (event, data) => { /* ... */ }); // Race condition prone
```
✅ GOOD:
```javascript
// main.mjs
ipcMain.handle('get-data', async (event, someId) => {
// ✅ Perform validation, access native APIs
return await fetchData(someId);
});
// renderer.js
const data = await window.electronAPI.getData(someId); // Assuming exposed via contextBridge
```
## 5. System Path Handling
Always use Node.js `path` and `os` modules for cross-platform compatibility.
- **File Paths:** Use `path.join()` for concatenation.
❌ BAD: `app.getPath('userData') + '/config.json'`
✅ GOOD:
```javascript
import path from 'node:path';
import { app } from 'electron';
const configPath = path.join(app.getPath('userData'), 'config.json');
```
- **Temporary Directories:** Use `os.tmpdir()`.
❌ BAD: `'/tmp/my-app-data'`
✅ GOOD:
```javascript
import os from 'node:os';
const tempDir = os.tmpdir();
```
## 6. Testing & Linting
Integrate Electron's built-in tooling for consistent code quality.
- **Linting:** Run `npm run lint` regularly and integrate into pre-commit hooks.
✅ GOOD: Ensure your `package.json` includes:
```json
"scripts": {
"lint": "electron-builder lint" // Or specific linter like 'eslint .'
}
```
- **Unit Tests:** Add new tests for any changes or new features.
✅ GOOD: `npm run test`
```json
"scripts": {
"test": "electron-mocha spec" // Example with electron-mocha
}
```
## 7. Staying Current
Electron evolves rapidly. Proactively manage updates and breaking changes.
- **Official Documentation:** Always consult the version-specific official documentation.
- **Breaking Changes:** Regularly review the "Breaking Changes" page for each major Electron release to anticipate necessary updates.
كيف تثبتها
1. Open the tool and start a new conversation, project or template. 2. Paste the text below wherever the tool keeps standing instructions — usually called system prompt, instructions, or persona. 3. Save it so it applies to every run rather than a single message.