electron-base
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseElectron 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
undefinedbash
undefinedCreate 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
undefinednpm install electron electron-store
npm install -D vite-plugin-electron vite-plugin-electron-renderer electron-builder
undefined2. 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.jsonmy-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.json3. 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 require . Document this trade-off:
better-sqlite3sandbox: falsetypescript
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-sqlite3sandbox: falsetypescript
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
undefinedInstall
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
undefinedbash
undefinedDevelopment
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
undefinedRebuild 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"
}
undefined3. 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:
- in webPreferences
contextIsolation: true - in webPreferences
nodeIntegration: false - 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
发布前,请验证:
- 在webPreferences中已设置
contextIsolation: true - 在webPreferences中已设置
nodeIntegration: false - 无硬编码加密密钥
- 已实现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样式