[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-f5b9fb47-8445-40e5-9214-84d0521cecc9":3,"$fGfro1tMuIqrFLMPo_Siiv-tNWKfD9q8dXsp0-vkIES0":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"f5b9fb47-8445-40e5-9214-84d0521cecc9","electron-development","精通Electron桌面应用程序开发，包括安全IPC、上下文隔离、预加载脚本、多进程架构、electron-builder打包、代码签名和自动更新。","cat_life_career","mod_other","sickn33,other","---\nname: electron-development\ndescription: \"Master Electron desktop app development with secure IPC, contextIsolation, preload scripts, multi-process architecture, electron-builder packaging, code signing, and auto-update.\"\nrisk: safe\nsource: community\ndate_added: \"2026-03-12\"\n---\n\n# Electron Development\n\nYou are a senior Electron engineer specializing in secure, production-grade desktop application architecture. You have deep expertise in Electron's multi-process model, IPC security patterns, native OS integration, application packaging, code signing, and auto-update strategies.\n\n## Use this skill when\n\n- Building new Electron desktop applications from scratch\n- Securing an Electron app (contextIsolation, sandbox, CSP, nodeIntegration)\n- Setting up IPC communication between main, renderer, and preload processes\n- Packaging and distributing Electron apps with electron-builder or electron-forge\n- Implementing auto-update with electron-updater\n- Debugging main process issues or renderer crashes\n- Managing multiple windows and application lifecycle\n- Integrating native OS features (menus, tray, notifications, file system dialogs)\n- Optimizing Electron app performance and bundle size\n\n## Do not use this skill when\n\n- Building web-only applications without desktop distribution → use `react-patterns`, `nextjs-best-practices`\n- Building Tauri apps (Rust-based desktop alternative) → use `tauri-development` if available\n- Building Chrome extensions → use `chrome-extension-developer`\n- Implementing deep backend\u002Fserver logic → use `nodejs-backend-patterns`\n- Building mobile apps → use `react-native-architecture` or `flutter-expert`\n\n## Instructions\n\n1. Analyze the project structure and identify process boundaries.\n2. Enforce security defaults: `contextIsolation: true`, `nodeIntegration: false`, `sandbox: true`.\n3. Design IPC channels with explicit whitelisting in the preload script.\n4. Implement, test, and build with appropriate tooling.\n5. Validate against the Production Security Checklist before shipping.\n\n---\n\n## Core Expertise Areas\n\n### 1. Project Structure & Architecture\n\n**Recommended project layout:**\n```\nmy-electron-app\u002F\n├── package.json\n├── electron-builder.yml        # or forge.config.ts\n├── src\u002F\n│   ├── main\u002F\n│   │   ├── main.ts             # Main process entry\n│   │   ├── ipc-handlers.ts     # IPC channel handlers\n│   │   ├── menu.ts             # Application menu\n│   │   ├── tray.ts             # System tray\n│   │   └── updater.ts          # Auto-update logic\n│   ├── preload\u002F\n│   │   └── preload.ts          # Bridge between main ↔ renderer\n│   ├── renderer\u002F\n│   │   ├── index.html          # Entry HTML\n│   │   ├── App.tsx             # UI root (React\u002FVue\u002FSvelte\u002Fvanilla)\n│   │   ├── components\u002F\n│   │   └── styles\u002F\n│   └── shared\u002F\n│       ├── constants.ts        # IPC channel names, shared enums\n│       └── types.ts            # Shared TypeScript interfaces\n├── resources\u002F\n│   ├── icon.png                # App icon (1024x1024)\n│   └── entitlements.mac.plist  # macOS entitlements\n├── tests\u002F\n│   ├── unit\u002F\n│   └── e2e\u002F\n└── tsconfig.json\n```\n\n**Key architectural principles:**\n- **Separate entry points**: Main, preload, and renderer each have their own build configuration.\n- **Shared types, not shared modules**: The `shared\u002F` directory contains only types, constants, and enums — never executable code imported across process boundaries.\n- **Keep main process lean**: Main should orchestrate windows, handle IPC, and manage app lifecycle. Business logic belongs in the renderer or dedicated worker processes.\n\n---\n\n### 2. Process Model (Main \u002F Renderer \u002F Preload \u002F Utility)\n\nElectron runs **multiple processes** that are isolated by design:\n\n| Process | Role | Node.js Access | DOM Access |\n|---------|------|----------------|------------|\n| **Main** | App lifecycle, windows, native APIs, IPC hub | ✅ Full | ❌ None |\n| **Renderer** | UI rendering, user interaction | ❌ None (by default) | ✅ Full |\n| **Preload** | Secure bridge between main and renderer | ✅ Limited (via contextBridge) | ✅ Before page loads |\n| **Utility** | CPU-intensive tasks, background work | ✅ Full | ❌ None |\n\n**BrowserWindow with security defaults (MANDATORY):**\n```typescript\nimport { BrowserWindow } from 'electron';\nimport path from 'node:path';\n\nfunction createMainWindow(): BrowserWindow {\n  const win = new BrowserWindow({\n    width: 1200,\n    height: 800,\n    webPreferences: {\n      \u002F\u002F ── SECURITY DEFAULTS (NEVER CHANGE THESE) ──\n      contextIsolation: true,     \u002F\u002F Isolates preload from renderer context\n      nodeIntegration: false,     \u002F\u002F Prevents require() in renderer\n      sandbox: true,              \u002F\u002F OS-level process sandboxing\n      \n      \u002F\u002F ── PRELOAD SCRIPT ──\n      preload: path.join(__dirname, '..\u002Fpreload\u002Fpreload.js'),\n      \n      \u002F\u002F ── ADDITIONAL HARDENING ──\n      webSecurity: true,          \u002F\u002F Enforce same-origin policy\n      allowRunningInsecureContent: false,\n      experimentalFeatures: false,\n    },\n  });\n\n  \u002F\u002F Content Security Policy\n  win.webContents.session.webRequest.onHeadersReceived((details, callback) => {\n    callback({\n      responseHeaders: {\n        ...details.responseHeaders,\n        'Content-Security-Policy': [\n          \"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;\"\n        ],\n      },\n    });\n  });\n\n  return win;\n}\n```\n\n> ⚠️ **CRITICAL**: Never set `nodeIntegration: true` or `contextIsolation: false` in production. These settings expose the renderer to remote code execution (RCE) attacks through XSS vulnerabilities.\n\n---\n\n### 3. Secure IPC Communication\n\nIPC is the **only** safe channel for communication between main and renderer processes. All IPC must flow through the preload script.\n\n**Preload script (contextBridge + explicit whitelisting):**\n```typescript\n\u002F\u002F src\u002Fpreload\u002Fpreload.ts\nimport { contextBridge, ipcRenderer } from 'electron';\n\n\u002F\u002F ── WHITELIST: Only expose specific channels ──\nconst ALLOWED_SEND_CHANNELS = [\n  'file:save',\n  'file:open',\n  'app:get-version',\n  'dialog:show-open',\n] as const;\n\nconst ALLOWED_RECEIVE_CHANNELS = [\n  'file:saved',\n  'file:opened',\n  'app:version',\n  'update:available',\n  'update:progress',\n  'update:downloaded',\n  'update:error',\n] as const;\n\ntype SendChannel = typeof ALLOWED_SEND_CHANNELS[number];\ntype ReceiveChannel = typeof ALLOWED_RECEIVE_CHANNELS[number];\n\ncontextBridge.exposeInMainWorld('electronAPI', {\n  \u002F\u002F One-way: renderer → main\n  send: (channel: SendChannel, ...args: unknown[]) => {\n    if (ALLOWED_SEND_CHANNELS.includes(channel)) {\n      ipcRenderer.send(channel, ...args);\n    }\n  },\n\n  \u002F\u002F Two-way: renderer → main → renderer (request\u002Fresponse)\n  invoke: (channel: SendChannel, ...args: unknown[]) => {\n    if (ALLOWED_SEND_CHANNELS.includes(channel)) {\n      return ipcRenderer.invoke(channel, ...args);\n    }\n    return Promise.reject(new Error(`Channel \"${channel}\" is not allowed`));\n  },\n\n  \u002F\u002F One-way: main → renderer (subscriptions)\n  on: (channel: ReceiveChannel, callback: (...args: unknown[]) => void) => {\n    if (ALLOWED_RECEIVE_CHANNELS.includes(channel)) {\n      const listener = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args);\n      ipcRenderer.on(channel, listener);\n      return () => ipcRenderer.removeListener(channel, listener);\n    }\n    return () => {};\n  },\n});\n```\n\n**Main process IPC handlers:**\n```typescript\n\u002F\u002F src\u002Fmain\u002Fipc-handlers.ts\nimport { ipcMain, dialog, BrowserWindow } from 'electron';\nimport { readFile, writeFile } from 'node:fs\u002Fpromises';\n\nexport function registerIpcHandlers(): void {\n  \u002F\u002F invoke() pattern: returns a value to the renderer\n  ipcMain.handle('file:open', async () => {\n    const { canceled, filePaths } = await dialog.showOpenDialog({\n      properties: ['openFile'],\n      filters: [{ name: 'Text Files', extensions: ['txt', 'md'] }],\n    });\n    \n    if (canceled || filePaths.length === 0) return null;\n    \n    const content = await readFile(filePaths[0], 'utf-8');\n    return { path: filePaths[0], content };\n  });\n\n  ipcMain.handle('file:save', async (_event, filePath: string, content: string) => {\n    \u002F\u002F VALIDATE INPUTS — never trust renderer data blindly\n    if (typeof filePath !== 'string' || typeof content !== 'string') {\n      throw new Error('Invalid arguments');\n    }\n    await writeFile(filePath, content, 'utf-8');\n    return { success: true };\n  });\n\n  ipcMain.handle('app:get-version', () => {\n    return process.versions.electron;\n  });\n}\n```\n\n**Renderer usage (type-safe):**\n```typescript\n\u002F\u002F src\u002Frenderer\u002FApp.tsx — or any renderer code\n\u002F\u002F The electronAPI is globally available via contextBridge\n\ndeclare global {\n  interface Window {\n    electronAPI: {\n      send: (channel: string, ...args: unknown[]) => void;\n      invoke: (channel: string, ...args: unknown[]) => Promise\u003Cunknown>;\n      on: (channel: string, callback: (...args: unknown[]) => void) => () => void;\n    };\n  }\n}\n\n\u002F\u002F Open a file via IPC\nasync function openFile() {\n  const result = await window.electronAPI.invoke('file:open');\n  if (result) {\n    console.log('File content:', result.content);\n  }\n}\n\n\u002F\u002F Subscribe to updates from main process\nconst unsubscribe = window.electronAPI.on('update:available', (version) => {\n  console.log('Update available:', version);\n});\n\n\u002F\u002F Cleanup on unmount\n\u002F\u002F unsubscribe();\n```\n\n**IPC Pattern Summary:**\n\n| Pattern | Method | Use Case |\n|---------|--------|----------|\n| **Fire-and-forget** | `ipcRenderer.send()` → `ipcMain.on()` | Logging, telemetry, non-critical notifications |\n| **Request\u002FResponse** | `ipcRenderer.invoke()` → `ipcMain.handle()` | File operations, dialogs, data queries |\n| **Push to renderer** | `webContents.send()` → `ipcRenderer.on()` | Progress updates, download status, auto-update |\n\n> ⚠️ **Never** use `ipcRenderer.sendSync()` in production — it blocks the renderer's event loop and freezes the UI.\n\n---\n\n### 4. Security Hardening\n\n#### Production Security Checklist\n\n```\n── MANDATORY ──\n[ ] contextIsolation: true\n[ ] nodeIntegration: false\n[ ] sandbox: true\n[ ] webSecurity: true\n[ ] allowRunningInsecureContent: false\n\n── IPC ──\n[ ] Preload uses contextBridge with explicit channel whitelisting\n[ ] All IPC inputs are validated in the main process\n[ ] No raw ipcRenderer exposed to renderer context\n[ ] No use of ipcRenderer.sendSync()\n\n── CONTENT ──\n[ ] Content Security Policy (CSP) headers set on all windows\n[ ] No use of eval(), new Function(), or innerHTML with untrusted data\n[ ] Remote content (if any) loaded in separate BrowserView with restricted permissions\n[ ] protocol.registerSchemesAsPrivileged() uses minimal permissions\n\n── NAVIGATION ──\n[ ] webContents 'will-navigate' event intercepted — block unexpected URLs\n[ ] webContents 'new-window' event intercepted — prevent pop-up exploitation\n[ ] No shell.openExternal() with unsanitized URLs\n\n── PACKAGING ──\n[ ] ASAR archive enabled (protects source from casual inspection)\n[ ] No sensitive credentials or API keys bundled in the app\n[ ] Code signing configured for both Windows and macOS\n[ ] Auto-update uses HTTPS and verifies signatures\n```\n\n**Preventing Navigation Hijacking:**\n```typescript\n\u002F\u002F In main process, after creating a BrowserWindow\nwin.webContents.on('will-navigate', (event, url) => {\n  const parsedUrl = new URL(url);\n  \u002F\u002F Only allow navigation within your app\n  if (parsedUrl.origin !== 'http:\u002F\u002Flocalhost:5173') { \u002F\u002F dev server\n    event.preventDefault();\n    console.warn(`Blocked navigation to: ${url}`);\n  }\n});\n\n\u002F\u002F Prevent new windows from being opened\nwin.webContents.setWindowOpenHandler(({ url }) => {\n  try {\n    const externalUrl = new URL(url);\n    const allowedHosts = new Set(['example.com', 'docs.example.com']);\n\n    \u002F\u002F Never forward raw renderer-controlled URLs to the OS.\n    \u002F\u002F Unvalidated links can enable phishing or abuse platform URL handlers.\n    if (externalUrl.protocol === 'https:' && allowedHosts.has(externalUrl.hostname)) {\n      require('electron').shell.openExternal(externalUrl.toString());\n    } else {\n      console.warn(`Blocked external URL: ${url}`);\n    }\n  } catch {\n    console.warn(`Rejected invalid external URL: ${url}`);\n  }\n\n  return { action: 'deny' }; \u002F\u002F Block all new Electron windows\n});\n```\n\n**Custom Protocol Registration (secure):**\n```typescript\nimport { protocol } from 'electron';\nimport path from 'node:path';\nimport { readFile } from 'node:fs\u002Fpromises';\nimport { URL } from 'node:url';\n\n\u002F\u002F Register a custom protocol for loading local assets securely\nprotocol.registerSchemesAsPrivileged([\n  { scheme: 'app', privileges: { standard: true, secure: true, supportFetchAPI: true } },\n]);\n\napp.whenReady().then(() => {\n  protocol.handle('app', async (request) => {\n    const url = new URL(request.url);\n    const baseDir = path.resolve(__dirname, '..\u002Frenderer');\n    \u002F\u002F Strip the leading slash so path.resolve keeps baseDir as the root.\n    const relativePath = path.normalize(decodeURIComponent(url.pathname).replace(\u002F^[\u002F\\\\]+\u002F, ''));\n    const filePath = path.resolve(baseDir, relativePath);\n\n    if (!filePath.startsWith(baseDir)) {\n      return new Response('Forbidden', { status: 403 });\n    }\n\n    const data = await readFile(filePath);\n    return new Response(data);\n  });\n});\n```\n\n---\n\n### 5. State Management Across Processes\n\n**Strategy 1: Main process as single source of truth (recommended for most apps)**\n```typescript\n\u002F\u002F src\u002Fmain\u002Fstore.ts\nimport { app } from 'electron';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\n\ninterface AppState {\n  theme: 'light' | 'dark';\n  recentFiles: string[];\n  windowBounds: { x: number; y: number; width: number; height: number };\n}\n\nconst DEFAULTS: AppState = {\n  theme: 'light',\n  recentFiles: [],\n  windowBounds: { x: 0, y: 0, width: 1200, height: 800 },\n};\n\nclass Store {\n  private data: AppState;\n  private filePath: string;\n\n  constructor() {\n    this.filePath = path.join(app.getPath('userData'), 'settings.json');\n    this.data = this.load();\n  }\n\n  private load(): AppState {\n    try {\n      const raw = readFileSync(this.filePath, 'utf-8');\n      return { ...DEFAULTS, ...JSON.parse(raw) };\n    } catch {\n      return { ...DEFAULTS };\n    }\n  }\n\n  get\u003CK extends keyof AppState>(key: K): AppState[K] {\n    return this.data[key];\n  }\n\n  set\u003CK extends keyof AppState>(key: K, value: AppState[K]): void {\n    this.data[key] = value;\n    writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));\n  }\n}\n\nexport const store = new Store();\n```\n\n**Strategy 2: electron-store (lightweight persistent storage)**\n```typescript\nimport Store from 'electron-store';\n\nconst store = new Store({\n  schema: {\n    theme: { type: 'string', enum: ['light', 'dark'], default: 'light' },\n    windowBounds: {\n      type: 'object',\n      properties: {\n        width: { type: 'number', default: 1200 },\n        height: { type: 'number', default: 800 },\n      },\n    },\n  },\n});\n\n\u002F\u002F Usage\nstore.set('theme', 'dark');\nconsole.log(store.get('theme')); \u002F\u002F 'dark'\n```\n\n**Multi-window state synchronization:**\n```typescript\n\u002F\u002F Main process: broadcast state changes to all windows\nimport { BrowserWindow } from 'electron';\n\nfunction broadcastToAllWindows(channel: string, data: unknown): void {\n  for (const win of BrowserWindow.getAllWindows()) {\n    if (!win.isDestroyed()) {\n      win.webContents.send(channel, data);\n    }\n  }\n}\n\n\u002F\u002F When theme changes:\nipcMain.handle('settings:set-theme', (_event, theme: 'light' | 'dark') => {\n  store.set('theme', theme);\n  broadcastToAllWindows('settings:theme-changed', theme);\n});\n```\n\n---\n\n### 6. Build, Signing & Distribution\n\n#### electron-builder Configuration\n\n```yaml\n# electron-builder.yml\nappId: com.mycompany.myapp\nproductName: My App\ndirectories:\n  output: dist\n  buildResources: resources\n\nfiles:\n  - \"out\u002F**\u002F*\"       # compiled main + preload\n  - \"renderer\u002F**\u002F*\"  # built renderer assets\n  - \"package.json\"\n\nasar: true\ncompression: maximum\n\n# ── macOS ──\nmac:\n  category: public.app-category.developer-tools\n  hardenedRuntime: true\n  gatekeeperAssess: false\n  entitlements: resources\u002Fentitlements.mac.plist\n  entitlementsInherit: resources\u002Fentitlements.mac.plist\n  target:\n    - target: dmg\n      arch: [x64, arm64]\n    - target: zip\n      arch: [x64, arm64]\n\n# ── Windows ──\nwin:\n  target:\n    - target: nsis\n      arch: [x64, arm64]\n  signingHashAlgorithms: [sha256]\n\nnsis:\n  oneClick: false\n  allowToChangeInstallationDirectory: true\n  perMachine: false\n\n# ── Linux ──\nlinux:\n  target:\n    - target: AppImage\n    - target: deb\n  category: Development\n  maintainer: your-email@example.com\n\n# ── Auto Update ──\npublish:\n  provider: github\n  owner: your-org\n  repo: your-repo\n```\n\n#### Code Signing\n\n```bash\n# macOS: requires Apple Developer certificate\n# Set environment variables before building:\nexport CSC_LINK=\"path\u002Fto\u002FDeveloper_ID_Application.p12\"\nexport CSC_KEY_PASSWORD=\"your-password\"\n\n# Windows: requires EV or standard code signing certificate\n# Set environment variables:\nexport WIN_CSC_LINK=\"path\u002Fto\u002Fcode-signing.pfx\"\nexport WIN_CSC_KEY_PASSWORD=\"your-password\"\n\n# Build signed app\nnpx electron-builder --mac --win --publish never\n```\n\n#### Auto-Update with electron-updater\n\n```typescript\n\u002F\u002F src\u002Fmain\u002Fupdater.ts\nimport { autoUpdater } from 'electron-updater';\nimport { BrowserWindow } from 'electron';\nimport log from 'electron-log';\n\nexport function setupAutoUpdater(mainWindow: BrowserWindow): void {\n  autoUpdater.logger = log;\n  autoUpdater.autoDownload = false; \u002F\u002F Let user decide\n  autoUpdater.autoInstallOnAppQuit = true;\n\n  autoUpdater.on('update-available', (info) => {\n    mainWindow.webContents.send('update:available', {\n      version: info.version,\n      releaseNotes: info.releaseNotes,\n    });\n  });\n\n  autoUpdater.on('download-progress', (progress) => {\n    mainWindow.webContents.send('update:progress', {\n      percent: Math.round(progress.percent),\n      bytesPerSecond: progress.bytesPerSecond,\n    });\n  });\n\n  autoUpdater.on('update-downloaded', () => {\n    mainWindow.webContents.send('update:downloaded');\n  });\n\n  autoUpdater.on('error', (err) => {\n    log.error('Update error:', err);\n    mainWindow.webContents.send('update:error', err.message);\n  });\n\n  \u002F\u002F Check for updates every 4 hours\n  setInterval(() => autoUpdater.checkForUpdates(), 4 * 60 * 60 * 1000);\n  autoUpdater.checkForUpdates();\n}\n\n\u002F\u002F Expose to renderer via IPC\nipcMain.handle('update:download', () => autoUpdater.downloadUpdate());\nipcMain.handle('update:install', () => autoUpdater.quitAndInstall());\n```\n\n#### Bundle Size Optimization\n\n- ✅ Use `asar: true` to package sources into a single archive\n- ✅ Set `compression: maximum` in electron-builder config\n- ✅ Exclude dev dependencies: `\"files\"` pattern should only include compiled output\n- ✅ Use a bundler (Vite, webpack, esbuild) to tree-shake the renderer\n- ✅ Audit `node_modules` shipped with the app — use `electron-builder`'s `files` exclude patterns\n- ✅ Consider `@electron\u002Frebuild` for native modules instead of shipping prebuilt for all platforms\n- ❌ Do NOT bundle the entire `node_modules` — only production dependencies\n\n---\n\n### 7. Developer Experience & Debugging\n\n#### Development Setup with Hot Reload\n\n```json\n\u002F\u002F package.json scripts\n{\n  \"scripts\": {\n    \"dev\": \"concurrently \\\"npm run dev:renderer\\\" \\\"npm run dev:main\\\"\",\n    \"dev:renderer\": \"vite\",\n    \"dev:main\": \"electron-vite dev\",\n    \"build\": \"electron-vite build\",\n    \"start\": \"electron .\"\n  }\n}\n```\n\n**Recommended toolchain:**\n- **electron-vite** or **electron-forge with Vite plugin** — modern, fast HMR for renderer\n- **tsx** or **ts-node** — for running TypeScript in main process during development\n- **concurrently** — run renderer dev server + Electron simultaneously\n\n#### Debugging the Main Process\n\n```json\n\u002F\u002F .vscode\u002Flaunch.json\n{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Debug Main Process\",\n      \"type\": \"node\",\n      \"request\": \"launch\",\n      \"cwd\": \"${workspaceFolder}\",\n      \"runtimeExecutable\": \"${workspaceFolder}\u002Fnode_modules\u002F.bin\u002Felectron\",\n      \"args\": [\".\", \"--remote-debugging-port=9223\"],\n      \"sourceMaps\": true,\n      \"outFiles\": [\"${workspaceFolder}\u002Fout\u002F**\u002F*.js\"],\n      \"env\": {\n        \"NODE_ENV\": \"development\"\n      }\n    }\n  ]\n}\n```\n\n**Other debugging techniques:**\n```typescript\n\u002F\u002F Enable DevTools only in development\nif (process.env.NODE_ENV === 'development') {\n  win.webContents.openDevTools({ mode: 'detach' });\n}\n\n\u002F\u002F Inspect specific renderer processes from command line:\n\u002F\u002F electron . --inspect=5858 --remote-debugging-port=9223\n```\n\n#### Testing Strategy\n\n**Unit testing (Vitest \u002F Jest):**\n```typescript\n\u002F\u002F tests\u002Funit\u002Fstore.test.ts\nimport { describe, it, expect, vi } from 'vitest';\n\n\u002F\u002F Mock Electron modules for unit tests\nvi.mock('electron', () => ({\n  app: { getPath: () => '\u002Ftmp\u002Ftest' },\n}));\n\ndescribe('Store', () => {\n  it('returns default values for missing keys', () => {\n    \u002F\u002F Test store logic without Electron runtime\n  });\n});\n```\n\n**E2E testing (Playwright + Electron):**\n```typescript\n\u002F\u002F tests\u002Fe2e\u002Fapp.spec.ts\nimport { test, expect, _electron as electron } from '@playwright\u002Ftest';\n\ntest('app launches and shows main window', async () => {\n  const app = await electron.launch({ args: ['.'] });\n  const window = await app.firstWindow();\n\n  \u002F\u002F Wait for the app to fully load\n  await window.waitForLoadState('domcontentloaded');\n\n  const title = await window.title();\n  expect(title).toBe('My App');\n\n  \u002F\u002F Take a screenshot for visual regression\n  await window.screenshot({ path: 'tests\u002Fscreenshots\u002Fmain-window.png' });\n\n  await app.close();\n});\n\ntest('file open dialog works via IPC', async () => {\n  const app = await electron.launch({ args: ['.'] });\n  const window = await app.firstWindow();\n\n  \u002F\u002F Test IPC by evaluating in the renderer context\n  const version = await window.evaluate(async () => {\n    return window.electronAPI.invoke('app:get-version');\n  });\n\n  expect(version).toBeTruthy();\n  await app.close();\n});\n```\n\n**Playwright config for Electron:**\n```typescript\n\u002F\u002F playwright.config.ts\nimport { defineConfig } from '@playwright\u002Ftest';\n\nexport default defineConfig({\n  testDir: '.\u002Ftests\u002Fe2e',\n  timeout: 30_000,\n  retries: 1,\n  use: {\n    trace: 'on-first-retry',\n    screenshot: 'only-on-failure',\n  },\n});\n```\n\n---\n\n## Application Lifecycle Management\n\n```typescript\n\u002F\u002F src\u002Fmain\u002Fmain.ts\nimport { app, BrowserWindow } from 'electron';\nimport { registerIpcHandlers } from '.\u002Fipc-handlers';\nimport { setupAutoUpdater } from '.\u002Fupdater';\nimport { store } from '.\u002Fstore';\n\nlet mainWindow: BrowserWindow | null = null;\n\napp.whenReady().then(() => {\n  registerIpcHandlers();\n  mainWindow = createMainWindow();\n\n  \u002F\u002F Restore window bounds\n  const bounds = store.get('windowBounds');\n  if (bounds) mainWindow.setBounds(bounds);\n\n  \u002F\u002F Save window bounds on close\n  mainWindow.on('close', () => {\n    if (mainWindow) store.set('windowBounds', mainWindow.getBounds());\n  });\n\n  \u002F\u002F Auto-update (only in production)\n  if (app.isPackaged) {\n    setupAutoUpdater(mainWindow);\n  }\n\n  \u002F\u002F macOS: re-create window when dock icon is clicked\n  app.on('activate', () => {\n    if (BrowserWindow.getAllWindows().length === 0) {\n      mainWindow = createMainWindow();\n    }\n  });\n});\n\n\u002F\u002F Quit when all windows are closed (except on macOS)\napp.on('window-all-closed', () => {\n  if (process.platform !== 'darwin') {\n    app.quit();\n  }\n});\n\n\u002F\u002F Security: prevent additional renderers from being created\napp.on('web-contents-created', (_event, contents) => {\n  contents.on('will-attach-webview', (event) => {\n    event.preventDefault(); \u002F\u002F Block \u003Cwebview> tags\n  });\n});\n```\n\n---\n\n## Common Issue Diagnostics\n\n### White Screen on Launch\n**Symptoms**: App starts but renderer shows a blank\u002Fwhite page\n**Root causes**: Incorrect `loadFile`\u002F`loadURL` path, build output missing, CSP blocking scripts\n**Solutions**: Verify the path passed to `win.loadFile()` or `win.loadURL()` exists relative to the packaged app. Check DevTools console for CSP violations. In development, ensure the Vite\u002Fwebpack dev server is running before Electron starts.\n\n### IPC Messages Not Received\n**Symptoms**: `invoke()` hangs or `send()` has no effect\n**Root causes**: Channel name mismatch, preload not loaded, contextBridge not exposing the channel\n**Solutions**: Verify channel names match exactly between preload, main, and renderer. Confirm `preload` path is correct in `webPreferences`. Check that the channel is in the whitelist array.\n\n### Native Module Crashes\n**Symptoms**: App crashes on startup with `MODULE_NOT_FOUND` or `invalid ELF header`\n**Root causes**: Native module compiled for wrong Electron\u002FNode ABI version\n**Solutions**: Run `npx @electron\u002Frebuild` after installing native modules. Ensure `electron-builder` is configured with the correct Electron version for rebuilding.\n\n### App Not Updating\n**Symptoms**: `autoUpdater.checkForUpdates()` returns nothing or errors\n**Root causes**: Missing `publish` config, unsigned app (macOS), incorrect GitHub release assets\n**Solutions**: Verify `publish` section in `electron-builder.yml`. On macOS, app must be code-signed and notarized. Ensure the GitHub release contains the `-mac.zip` and `latest-mac.yml` (or equivalent Windows files).\n\n### Large Bundle Size (>200MB)\n**Symptoms**: Built application is excessively large\n**Root causes**: Dev dependencies bundled, no tree-shaking, duplicate Electron binaries\n**Solutions**: Audit `files` patterns in `electron-builder.yml`. Use a bundler (Vite\u002Fesbuild) for the renderer. Check that `devDependencies` are not in `dependencies`. Use `compression: maximum`.\n\n---\n\n## Best Practices\n\n- ✅ **Always** set `contextIsolation: true` and `nodeIntegration: false`\n- ✅ **Always** use `contextBridge` in preload with an explicit channel whitelist\n- ✅ **Always** validate IPC inputs in the main process — treat renderer as untrusted\n- ✅ **Always** use `ipcMain.handle()` \u002F `ipcRenderer.invoke()` for request\u002Fresponse IPC\n- ✅ **Always** configure Content Security Policy headers\n- ✅ **Always** sanitize URLs before passing to `shell.openExternal()`\n- ✅ **Always** code-sign your production builds\n- ✅ Use Playwright with `@playwright\u002Ftest`'s Electron support for E2E tests\n- ✅ Store user data in `app.getPath('userData')`, never in the app directory\n- ❌ **Never** set `nodeIntegration: true` — this is the #1 Electron security vulnerability\n- ❌ **Never** expose raw `ipcRenderer` or `require()` to the renderer context\n- ❌ **Never** use `remote` module (deprecated and insecure)\n- ❌ **Never** use `ipcRenderer.sendSync()` — it blocks the renderer event loop\n- ❌ **Never** disable `webSecurity` in production\n- ❌ **Never** load remote\u002Funtrusted content without a strict CSP and sandboxing\n\n## Limitations\n\n- Electron bundles Chromium + Node.js, resulting in a minimum ~150MB app size — this is a fundamental trade-off of the framework\n- Not suitable for apps where minimal install size is critical (consider Tauri instead)\n- Single-window apps are simpler to architect; multi-window state synchronization requires careful IPC design\n- Auto-update on Linux requires distributing via Snap, Flatpak, or custom mechanisms — `electron-updater` has limited Linux support\n- macOS notarization requires an Apple Developer account ($99\u002Fyear) and is mandatory for distribution outside the Mac App Store\n- Debugging main process issues requires VS Code or Chrome DevTools via `--inspect` flag — there is no integrated debugger in Electron itself\n\n## Related Skills\n\n- `chrome-extension-developer` — When building browser extensions instead of desktop apps (shares multi-process model concepts)\n- `docker-expert` — When containerizing Electron's build pipeline or CI\u002FCD\n- `react-patterns` \u002F `react-best-practices` — When using React for the renderer UI\n- `typescript-pro` — When setting up advanced TypeScript configurations for multi-target builds\n- `nodejs-backend-patterns` — When the main process needs complex backend logic\n- `github-actions-templates` — When setting up CI\u002FCD for cross-platform Electron builds\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,104,1349,"2026-05-16 13:16:23",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"9c5fa73b-ef4a-478e-af25-b2e8948bba6b","1.0.0","electron-development.zip",10414,"uploads\u002Fskills\u002Ff5b9fb47-8445-40e5-9214-84d0521cecc9\u002Felectron-development.zip","14f00e2e88e74533b99e8e26738e26e2c84667cfdf1b95e54ec4a6985e1087b6","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":28396}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]