electron-base

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Electron Base Skill

Electron基础技能

Build secure, modern desktop applications with Electron 33, Vite, React, and TypeScript.
使用Electron 33、Vite、React和TypeScript构建安全、现代的桌面应用程序。

Quick Start

快速开始

1. Initialize Project

1. 初始化项目

bash
undefined
bash
undefined

Create Vite project

Create Vite project

npm create vite@latest my-app -- --template react-ts cd my-app
npm create vite@latest my-app -- --template react-ts cd my-app

Install Electron dependencies

Install Electron dependencies

npm install electron electron-store npm install -D vite-plugin-electron vite-plugin-electron-renderer electron-builder
undefined
npm install electron electron-store npm install -D vite-plugin-electron vite-plugin-electron-renderer electron-builder
undefined

2. Project Structure

2. 项目结构

my-app/
├── electron/
│   ├── main.ts           # Main process entry
│   ├── preload.ts        # Preload script (contextBridge)
│   └── ipc-handlers/     # Modular IPC handlers
│       ├── auth.ts
│       └── store.ts
├── src/                  # React app (renderer)
├── vite.config.ts        # Dual-entry Vite config
├── electron-builder.json # Build config
└── package.json
my-app/
├── electron/
│   ├── main.ts           # Main process entry
│   ├── preload.ts        # Preload script (contextBridge)
│   └── ipc-handlers/     # Modular IPC handlers
│       ├── auth.ts
│       └── store.ts
├── src/                  # React app (renderer)
├── vite.config.ts        # Dual-entry Vite config
├── electron-builder.json # Build config
└── package.json

3. Package.json Updates

3. 更新Package.json

json
{
  "main": "dist-electron/main.mjs",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "electron .",
    "package": "electron-builder"
  }
}

json
{
  "main": "dist-electron/main.mjs",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "electron .",
    "package": "electron-builder"
  }
}

Architecture Patterns

架构模式

Main vs Renderer Process Separation

主进程与渲染进程分离

┌─────────────────────────────────────────────────────────────┐
│                     MAIN PROCESS                            │
│  (Node.js + Electron APIs)                                  │
│  - File system access                                       │
│  - Native modules (better-sqlite3)                          │
│  - System dialogs                                           │
│  - Protocol handlers                                        │
└─────────────────────┬───────────────────────────────────────┘
                      │ IPC (invoke/handle)
                      │ Events (send/on)
┌─────────────────────▼───────────────────────────────────────┐
│                   PRELOAD SCRIPT                            │
│  (contextBridge.exposeInMainWorld)                          │
│  - Type-safe API exposed to renderer                        │
│  - No direct ipcRenderer exposure                           │
└─────────────────────┬───────────────────────────────────────┘
                      │ window.electron.*
┌─────────────────────▼───────────────────────────────────────┐
│                  RENDERER PROCESS                           │
│  (Browser context - React app)                              │
│  - No Node.js APIs                                          │
│  - Uses window.electron.* for IPC                           │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│                     MAIN PROCESS                            │
│  (Node.js + Electron APIs)                                  │
│  - File system access                                       │
│  - Native modules (better-sqlite3)                          │
│  - System dialogs                                           │
│  - Protocol handlers                                        │
└─────────────────────┬───────────────────────────────────────┘
                      │ IPC (invoke/handle)
                      │ Events (send/on)
┌─────────────────────▼───────────────────────────────────────┐
│                   PRELOAD SCRIPT                            │
│  (contextBridge.exposeInMainWorld)                          │
│  - Type-safe API exposed to renderer                        │
│  - No direct ipcRenderer exposure                           │
└─────────────────────┬───────────────────────────────────────┘
                      │ window.electron.*
┌─────────────────────▼───────────────────────────────────────┐
│                  RENDERER PROCESS                           │
│  (Browser context - React app)                              │
│  - No Node.js APIs                                          │
│  - Uses window.electron.* for IPC                           │
└─────────────────────────────────────────────────────────────┘

Type-Safe IPC Pattern

类型安全的IPC模式

The preload script exposes a typed API to the renderer:
typescript
// electron/preload.ts
export interface ElectronAPI {
  auth: {
    startOAuth: (provider: 'google' | 'github') => Promise<void>;
    getSession: () => Promise<Session | null>;
    logout: () => Promise<void>;
    onSuccess: (callback: (session: Session) => void) => () => void;
    onError: (callback: (error: string) => void) => () => void;
  };
  app: {
    getVersion: () => Promise<string>;
    openExternal: (url: string) => Promise<void>;
  };
}

// Expose to renderer
contextBridge.exposeInMainWorld('electron', electronAPI);

// Global type declaration
declare global {
  interface Window {
    electron: ElectronAPI;
  }
}

预加载脚本向渲染进程暴露一个类型化的API:
typescript
// electron/preload.ts
export interface ElectronAPI {
  auth: {
    startOAuth: (provider: 'google' | 'github') => Promise<void>;
    getSession: () => Promise<Session | null>;
    logout: () => Promise<void>;
    onSuccess: (callback: (session: Session) => void) => () => void;
    onError: (callback: (error: string) => void) => () => void;
  };
  app: {
    getVersion: () => Promise<string>;
    openExternal: (url: string) => Promise<void>;
  };
}

// Expose to renderer
contextBridge.exposeInMainWorld('electron', electronAPI);

// Global type declaration
declare global {
  interface Window {
    electron: ElectronAPI;
  }
}

Security Best Practices

安全最佳实践

REQUIRED: Context Isolation

必须:上下文隔离

Always enable context isolation and disable node integration:
typescript
// electron/main.ts
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: join(__dirname, 'preload.cjs'),
    contextIsolation: true,    // REQUIRED - isolates preload from renderer
    nodeIntegration: false,    // REQUIRED - no Node.js in renderer
    sandbox: false,            // May need to disable for native modules
  },
});
始终启用上下文隔离并禁用Node集成:
typescript
// electron/main.ts
const mainWindow = new BrowserWindow({
  webPreferences: {
    preload: join(__dirname, 'preload.cjs'),
    contextIsolation: true,    // REQUIRED - isolates preload from renderer
    nodeIntegration: false,    // REQUIRED - no Node.js in renderer
    sandbox: false,            // May need to disable for native modules
  },
});

NEVER: Hardcode Encryption Keys

严禁:硬编码加密密钥

typescript
// WRONG - hardcoded key is a security vulnerability
const store = new Store({
  encryptionKey: 'my-secret-key',  // DO NOT DO THIS
});

// CORRECT - derive from machine ID
import { machineIdSync } from 'node-machine-id';

const store = new Store({
  encryptionKey: machineIdSync().slice(0, 32),  // Machine-unique key
});
typescript
// WRONG - hardcoded key is a security vulnerability
const store = new Store({
  encryptionKey: 'my-secret-key',  // DO NOT DO THIS
});

// CORRECT - derive from machine ID
import { machineIdSync } from 'node-machine-id';

const store = new Store({
  encryptionKey: machineIdSync().slice(0, 32),  // Machine-unique key
});

Sandbox Trade-offs

沙箱权衡

Native modules like
better-sqlite3
require
sandbox: false
. Document this trade-off:
typescript
webPreferences: {
  sandbox: false, // Required for better-sqlite3 - document security trade-off
}
Modules requiring sandbox: false:
  • better-sqlite3
  • node-pty
  • native-keymap
Modules working with sandbox: true:
  • electron-store (pure JS)
  • keytar (uses Electron's safeStorage)

better-sqlite3
这样的原生模块需要设置
sandbox: false
。请记录此权衡:
typescript
webPreferences: {
  sandbox: false, // Required for better-sqlite3 - document security trade-off
}
需要禁用沙箱的模块:
  • better-sqlite3
  • node-pty
  • native-keymap
支持沙箱的模块:
  • electron-store (纯JS)
  • keytar (使用Electron的safeStorage)

OAuth with Custom Protocol Handlers

基于自定义协议处理器的OAuth

1. Register Protocol (main.ts)

1. 注册协议(main.ts)

typescript
// In development, need to pass executable path
if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient('myapp', process.execPath, [process.argv[1]]);
  }
} else {
  app.setAsDefaultProtocolClient('myapp');
}
typescript
// In development, need to pass executable path
if (process.defaultApp) {
  if (process.argv.length >= 2) {
    app.setAsDefaultProtocolClient('myapp', process.execPath, [process.argv[1]]);
  }
} else {
  app.setAsDefaultProtocolClient('myapp');
}

2. Handle Protocol URL

2. 处理协议URL

typescript
// Single instance lock (required for reliable protocol handling)
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (_event, commandLine) => {
    const url = commandLine.find((arg) => arg.startsWith('myapp://'));
    if (url) handleProtocolUrl(url);
    if (mainWindow?.isMinimized()) mainWindow.restore();
    mainWindow?.focus();
  });
}

// macOS handles protocol differently
app.on('open-url', (_event, url) => {
  handleProtocolUrl(url);
});

function handleProtocolUrl(url: string) {
  const parsedUrl = new URL(url);

  if (parsedUrl.pathname.includes('/auth/callback')) {
    const token = parsedUrl.searchParams.get('token');
    const state = parsedUrl.searchParams.get('state');
    const error = parsedUrl.searchParams.get('error');

    if (error) {
      mainWindow?.webContents.send('auth:error', error);
    } else if (token && state) {
      handleAuthCallback(token, state)
        .then((session) => mainWindow?.webContents.send('auth:success', session))
        .catch((err) => mainWindow?.webContents.send('auth:error', err.message));
    }
  }
}
typescript
// Single instance lock (required for reliable protocol handling)
const gotTheLock = app.requestSingleInstanceLock();

if (!gotTheLock) {
  app.quit();
} else {
  app.on('second-instance', (_event, commandLine) => {
    const url = commandLine.find((arg) => arg.startsWith('myapp://'));
    if (url) handleProtocolUrl(url);
    if (mainWindow?.isMinimized()) mainWindow.restore();
    mainWindow?.focus();
  });
}

// macOS handles protocol differently
app.on('open-url', (_event, url) => {
  handleProtocolUrl(url);
});

function handleProtocolUrl(url: string) {
  const parsedUrl = new URL(url);

  if (parsedUrl.pathname.includes('/auth/callback')) {
    const token = parsedUrl.searchParams.get('token');
    const state = parsedUrl.searchParams.get('state');
    const error = parsedUrl.searchParams.get('error');

    if (error) {
      mainWindow?.webContents.send('auth:error', error);
    } else if (token && state) {
      handleAuthCallback(token, state)
        .then((session) => mainWindow?.webContents.send('auth:success', session))
        .catch((err) => mainWindow?.webContents.send('auth:error', err.message));
    }
  }
}

3. State Validation for CSRF Protection

3. 用于CSRF防护的状态验证

typescript
// Start OAuth - generate and store state
ipcMain.handle('auth:start-oauth', async (_event, provider) => {
  const state = crypto.randomUUID();
  store.set('pendingState', state);

  const authUrl = `${BACKEND_URL}/api/auth/signin/${provider}?state=${state}`;
  await shell.openExternal(authUrl);
});

// Verify state on callback
export async function handleAuthCallback(token: string, state: string): Promise<Session> {
  const pendingState = store.get('pendingState');

  if (state !== pendingState) {
    throw new Error('State mismatch - possible CSRF attack');
  }

  store.set('pendingState', null);
  // ... rest of auth flow
}

typescript
// Start OAuth - generate and store state
ipcMain.handle('auth:start-oauth', async (_event, provider) => {
  const state = crypto.randomUUID();
  store.set('pendingState', state);

  const authUrl = `${BACKEND_URL}/api/auth/signin/${provider}?state=${state}`;
  await shell.openExternal(authUrl);
});

// Verify state on callback
export async function handleAuthCallback(token: string, state: string): Promise<Session> {
  const pendingState = store.get('pendingState');

  if (state !== pendingState) {
    throw new Error('State mismatch - possible CSRF attack');
  }

  store.set('pendingState', null);
  // ... rest of auth flow
}

Native Module Compatibility

原生模块兼容性

better-sqlite3

better-sqlite3

Requires rebuilding for Electron's Node ABI:
bash
undefined
需要针对Electron的Node ABI重新构建:
bash
undefined

Install

Install

npm install better-sqlite3
npm install better-sqlite3

Rebuild for Electron

Rebuild for Electron

npm install -D electron-rebuild npx electron-rebuild -f -w better-sqlite3

**Vite config - externalize native modules:**

```typescript
// vite.config.ts
electron({
  main: {
    entry: 'electron/main.ts',
    vite: {
      build: {
        rollupOptions: {
          external: ['electron', 'better-sqlite3', 'electron-store'],
        },
      },
    },
  },
});
npm install -D electron-rebuild npx electron-rebuild -f -w better-sqlite3

**Vite配置 - 外部化原生模块:**

```typescript
// vite.config.ts
electron({
  main: {
    entry: 'electron/main.ts',
    vite: {
      build: {
        rollupOptions: {
          external: ['electron', 'better-sqlite3', 'electron-store'],
        },
      },
    },
  },
});

electron-store

electron-store

Works with sandbox enabled, but encryption key should be machine-derived:
typescript
import Store from 'electron-store';
import { machineIdSync } from 'node-machine-id';

interface StoreSchema {
  session: Session | null;
  settings: Settings;
}

const store = new Store<StoreSchema>({
  name: 'myapp-data',
  encryptionKey: machineIdSync().slice(0, 32),
  defaults: {
    session: null,
    settings: { theme: 'system' },
  },
});

支持沙箱启用状态,但加密密钥应从机器信息派生:
typescript
import Store from 'electron-store';
import { machineIdSync } from 'node-machine-id';

interface StoreSchema {
  session: Session | null;
  settings: Settings;
}

const store = new Store<StoreSchema>({
  name: 'myapp-data',
  encryptionKey: machineIdSync().slice(0, 32),
  defaults: {
    session: null,
    settings: { theme: 'system' },
  },
});

Build and Packaging

构建与打包

electron-builder.json

electron-builder.json

json
{
  "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
  "appId": "com.yourcompany.myapp",
  "productName": "MyApp",
  "directories": {
    "output": "release"
  },
  "files": [
    "dist/**/*",
    "dist-electron/**/*"
  ],
  "mac": {
    "category": "public.app-category.productivity",
    "icon": "build/icon.icns",
    "hardenedRuntime": true,
    "gatekeeperAssess": false,
    "entitlements": "build/entitlements.mac.plist",
    "entitlementsInherit": "build/entitlements.mac.plist",
    "target": [
      { "target": "dmg", "arch": ["x64", "arm64"] }
    ],
    "protocols": [
      { "name": "MyApp", "schemes": ["myapp"] }
    ]
  },
  "win": {
    "icon": "build/icon.ico",
    "target": [
      { "target": "nsis", "arch": ["x64"] }
    ]
  },
  "linux": {
    "icon": "build/icons",
    "target": ["AppImage"],
    "category": "Office"
  }
}
json
{
  "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json",
  "appId": "com.yourcompany.myapp",
  "productName": "MyApp",
  "directories": {
    "output": "release"
  },
  "files": [
    "dist/**/*",
    "dist-electron/**/*"
  ],
  "mac": {
    "category": "public.app-category.productivity",
    "icon": "build/icon.icns",
    "hardenedRuntime": true,
    "gatekeeperAssess": false,
    "entitlements": "build/entitlements.mac.plist",
    "entitlementsInherit": "build/entitlements.mac.plist",
    "target": [
      { "target": "dmg", "arch": ["x64", "arm64"] }
    ],
    "protocols": [
      { "name": "MyApp", "schemes": ["myapp"] }
    ]
  },
  "win": {
    "icon": "build/icon.ico",
    "target": [
      { "target": "nsis", "arch": ["x64"] }
    ]
  },
  "linux": {
    "icon": "build/icons",
    "target": ["AppImage"],
    "category": "Office"
  }
}

macOS Entitlements (build/entitlements.mac.plist)

macOS权限配置(build/entitlements.mac.plist)

xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.security.cs.allow-jit</key>
  <true/>
  <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
  <true/>
  <key>com.apple.security.cs.disable-library-validation</key>
  <true/>
</dict>
</plist>
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>com.apple.security.cs.allow-jit</key>
  <true/>
  <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
  <true/>
  <key>com.apple.security.cs.disable-library-validation</key>
  <true/>
</dict>
</plist>

Build Commands

构建命令

bash
undefined
bash
undefined

Development

Development

npm run dev
npm run dev

Production build

Production build

npm run build
npm run build

Package for current platform

Package for current platform

npm run package
npm run package

Package for specific platform

Package for specific platform

npx electron-builder --mac npx electron-builder --win npx electron-builder --linux

---
npx electron-builder --mac npx electron-builder --win npx electron-builder --linux

---

Known Issues and Prevention

已知问题与预防方案

1. "Cannot read properties of undefined" in Preload

1. 预加载脚本中出现"Cannot read properties of undefined"

Cause: Accessing window.electron before preload completes.
Fix: Use optional chaining or check for existence:
typescript
// In React component
useEffect(() => {
  if (!window.electron?.auth) return;

  const unsubscribe = window.electron.auth.onSuccess((session) => {
    setSession(session);
  });

  return unsubscribe;
}, []);
原因: 在预加载完成前访问window.electron。
修复方案: 使用可选链或检查是否存在:
typescript
// In React component
useEffect(() => {
  if (!window.electron?.auth) return;

  const unsubscribe = window.electron.auth.onSuccess((session) => {
    setSession(session);
  });

  return unsubscribe;
}, []);

2. NODE_MODULE_VERSION Mismatch

2. NODE_MODULE_VERSION不匹配

Cause: Native module compiled for different Node.js version than Electron uses.
Fix:
bash
undefined
原因: 原生模块针对与Electron使用的Node.js版本不同的环境编译。
修复方案:
bash
undefined

Rebuild native modules for Electron

Rebuild native modules for Electron

npx electron-rebuild -f -w better-sqlite3
npx electron-rebuild -f -w better-sqlite3

Or add to package.json postinstall

Or add to package.json postinstall

"scripts": { "postinstall": "electron-rebuild" }
undefined
"scripts": { "postinstall": "electron-rebuild" }
undefined

3. OAuth State Mismatch

3. OAuth状态不匹配

Cause: State not persisted or lost between OAuth start and callback.
Fix: Use persistent storage (electron-store) not memory:
typescript
// WRONG - state lost if app restarts
let pendingState: string | null = null;

// CORRECT - persisted storage
const store = new Store({ ... });
store.set('pendingState', state);
原因: OAuth启动与回调之间状态未持久化或丢失。
修复方案: 使用持久化存储(electron-store)而非内存:
typescript
// WRONG - state lost if app restarts
let pendingState: string | null = null;

// CORRECT - persisted storage
const store = new Store({ ... });
store.set('pendingState', state);

4. Sandbox Conflicts with Native Modules

4. 沙箱与原生模块冲突

Cause: Sandbox prevents loading native .node files.
Fix: Disable sandbox (with documented trade-off) or use pure-JS alternatives:
typescript
webPreferences: {
  sandbox: false, // Required for better-sqlite3
  // Alternative: Use sql.js (WASM) if sandbox required
}
原因: 沙箱阻止加载原生.node文件。
修复方案: 禁用沙箱(并记录安全权衡)或使用纯JS替代方案:
typescript
webPreferences: {
  sandbox: false, // Required for better-sqlite3
  // Alternative: Use sql.js (WASM) if sandbox required
}

5. Dual Auth System Maintenance Burden

5. 双重认证系统维护负担

Cause: Configuring better-auth but using manual OAuth creates confusion.
Fix: Choose one approach:
  • Use better-auth fully OR
  • Use manual OAuth only (remove better-auth)
原因: 配置了better-auth但同时使用手动OAuth导致混淆。
修复方案: 选择一种方式:
  • 完全使用better-auth 或
  • 仅使用手动OAuth(移除better-auth)

6. Token Expiration Without Refresh

6. 令牌过期无刷新机制

Cause: Hardcoded expiration with no refresh mechanism.
Fix: Implement token refresh or sliding sessions:
typescript
// Check expiration with buffer
const session = store.get('session');
const expiresAt = new Date(session.expiresAt);
const bufferMs = 5 * 60 * 1000; // 5 minutes

if (Date.now() > expiresAt.getTime() - bufferMs) {
  await refreshToken(session.token);
}
原因: 硬编码过期时间且无刷新机制。
修复方案: 实现令牌刷新或滑动会话:
typescript
// Check expiration with buffer
const session = store.get('session');
const expiresAt = new Date(session.expiresAt);
const bufferMs = 5 * 60 * 1000; // 5 minutes

if (Date.now() > expiresAt.getTime() - bufferMs) {
  await refreshToken(session.token);
}

7. Empty Catch Blocks Masking Failures

7. 空Catch块掩盖错误

Cause: Swallowing errors silently.
Fix: Log errors, distinguish error types:
typescript
// WRONG
try {
  await fetch(url);
} catch {
  // Silent failure - user has no idea what happened
}

// CORRECT
try {
  await fetch(url);
} catch (err) {
  if (err instanceof TypeError) {
    console.error('[Network] Offline or DNS failure:', err.message);
  } else {
    console.error('[Auth] Unexpected error:', err);
  }
  throw err; // Re-throw for caller to handle
}
原因: 静默吞掉错误。
修复方案: 记录错误,区分错误类型:
typescript
// WRONG
try {
  await fetch(url);
} catch {
  // Silent failure - user has no idea what happened
}

// CORRECT
try {
  await fetch(url);
} catch (err) {
  if (err instanceof TypeError) {
    console.error('[Network] Offline or DNS failure:', err.message);
  } else {
    console.error('[Auth] Unexpected error:', err);
  }
  throw err; // Re-throw for caller to handle
}

8. Hardcoded Encryption Key

8. 硬编码加密密钥

Cause: Using string literal for encryption.
Fix: Derive from machine identifier:
typescript
import { machineIdSync } from 'node-machine-id';

const store = new Store({
  encryptionKey: machineIdSync().slice(0, 32),
});

原因: 使用字符串字面量作为加密密钥。
修复方案: 从机器标识符派生:
typescript
import { machineIdSync } from 'node-machine-id';

const store = new Store({
  encryptionKey: machineIdSync().slice(0, 32),
});

Vite Configuration

Vite配置

Full vite.config.ts

完整vite.config.ts

typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import electron from 'vite-plugin-electron/simple';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
    electron({
      main: {
        entry: 'electron/main.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              external: ['electron', 'better-sqlite3', 'electron-store'],
              output: {
                format: 'es',
                entryFileNames: '[name].mjs',
              },
            },
          },
        },
      },
      preload: {
        input: 'electron/preload.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              output: {
                format: 'cjs',
                entryFileNames: '[name].cjs',
              },
            },
          },
        },
      },
      renderer: {},
    }),
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  build: {
    outDir: 'dist',
  },
  optimizeDeps: {
    include: ['react', 'react-dom'],
  },
});

typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import electron from 'vite-plugin-electron/simple';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
    electron({
      main: {
        entry: 'electron/main.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              external: ['electron', 'better-sqlite3', 'electron-store'],
              output: {
                format: 'es',
                entryFileNames: '[name].mjs',
              },
            },
          },
        },
      },
      preload: {
        input: 'electron/preload.ts',
        vite: {
          build: {
            outDir: 'dist-electron',
            rollupOptions: {
              output: {
                format: 'cjs',
                entryFileNames: '[name].cjs',
              },
            },
          },
        },
      },
      renderer: {},
    }),
  ],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
    },
  },
  build: {
    outDir: 'dist',
  },
  optimizeDeps: {
    include: ['react', 'react-dom'],
  },
});

Dependencies Reference

依赖参考

Production Dependencies

生产依赖

json
{
  "dependencies": {
    "electron-store": "^10.0.0",
    "electron-updater": "^6.3.0"
  },
  "optionalDependencies": {
    "better-sqlite3": "^11.0.0",
    "node-machine-id": "^1.1.12"
  }
}
json
{
  "dependencies": {
    "electron-store": "^10.0.0",
    "electron-updater": "^6.3.0"
  },
  "optionalDependencies": {
    "better-sqlite3": "^11.0.0",
    "node-machine-id": "^1.1.12"
  }
}

Development Dependencies

开发依赖

json
{
  "devDependencies": {
    "electron": "^33.0.0",
    "electron-builder": "^25.0.0",
    "electron-rebuild": "^3.2.9",
    "vite-plugin-electron": "^0.28.0",
    "vite-plugin-electron-renderer": "^0.14.0"
  }
}

json
{
  "devDependencies": {
    "electron": "^33.0.0",
    "electron-builder": "^25.0.0",
    "electron-rebuild": "^3.2.9",
    "vite-plugin-electron": "^0.28.0",
    "vite-plugin-electron-renderer": "^0.14.0"
  }
}

Security Checklist

安全检查清单

Before release, verify:
  • contextIsolation: true
    in webPreferences
  • nodeIntegration: false
    in webPreferences
  • No hardcoded encryption keys
  • OAuth state validation implemented
  • No sensitive data in IPC channel names
  • External links open in system browser (
    shell.openExternal
    )
  • CSP headers configured for production
  • macOS hardened runtime enabled
  • Code signing configured (if distributing)
  • No empty catch blocks masking errors

发布前,请验证:
  • contextIsolation: true
    在webPreferences中已设置
  • nodeIntegration: false
    在webPreferences中已设置
  • 无硬编码加密密钥
  • 已实现OAuth状态验证
  • IPC通道名称中无敏感数据
  • 外部链接通过系统浏览器打开(
    shell.openExternal
  • 生产环境已配置CSP头
  • macOS已启用强化运行时
  • 已配置代码签名(如需分发)
  • 无空Catch块掩盖错误

Related Skills

相关技能

  • better-auth - For backend authentication that pairs with Electron OAuth
  • cloudflare-worker-base - For building backend APIs
  • tailwind-v4-shadcn - For styling the renderer UI
  • better-auth - 用于与Electron OAuth配对的后端认证
  • cloudflare-worker-base - 用于构建后端API
  • tailwind-v4-shadcn - 用于渲染进程UI样式