{T}

Electron 项目结构

良好的项目结构是开发可维护、可扩展 Electron 应用的基础。本文将介绍 Electron 项目的标准结构、文件组织方式以及最佳实践。

项目结构概述

Electron 项目通常包含主进程代码、渲染进程代码、预加载脚本、静态资源等。合理的文件组织可以提升开发效率和代码可维护性。

基础项目结构

适用于小型项目或快速原型开发:

code
my-electron-app/
├── package.json              # 项目配置和依赖
├── main.js                   # 主进程入口文件
├── preload.js                # 预加载脚本
├── index.html                # 渲染进程 HTML
├── renderer.js               # 渲染进程 JavaScript
├── styles.css                # 样式文件
├── assets/                   # 静态资源
│   ├── icon.png              # 应用图标
│   └── images/               # 图片资源
├── .gitignore                # Git 忽略配置
├── .npmrc                    # npm 配置(可选)
└── README.md                 # 项目说明文档

文件职责说明

文件/目录类型职责
package.json配置定义项目元数据、依赖、脚本命令
main.js主进程应用入口、窗口管理、系统交互
preload.js预加载安全桥接主进程和渲染进程
index.html渲染进程应用界面结构
renderer.js渲染进程页面交互逻辑
styles.css渲染进程界面样式
assets/静态资源图片、图标、字体等资源

标准项目结构

适用于中大型项目,支持模块化和团队协作:

code
my-electron-app/
├── package.json              # 项目配置
├── package-lock.json         # 依赖锁定文件
├── 
├── src/                      # 源代码目录
│   ├── main/                 # 主进程代码
│   │   ├── index.js          # 主进程入口
│   │   ├── window.js         # 窗口管理模块
│   │   ├── menu.js           # 应用菜单配置
│   │   ├── tray.js           # 系统托盘模块
│   │   ├── ipc/              # IPC 通信模块
│   │   │   ├── index.js      # IPC 注册入口
│   │   │   ├── handlers.js   # IPC 处理函数
│   │   │   └── channels.js   # IPC 通道常量
│   │   └── utils/            # 主进程工具函数
│   │       ├── store.js      # 数据存储
│   │       └── shortcut.js   # 快捷键管理
│   │
│   ├── renderer/             # 渲染进程代码
│   │   ├── index.html        # 入口 HTML
│   │   ├── renderer.js       # 渲染进程入口
│   │   ├── pages/            # 页面组件
│   │   │   ├── home/         # 首页
│   │   │   ├── settings/     # 设置页
│   │   │   └── about/        # 关于页
│   │   ├── components/       # UI 组件
│   │   │   ├── Header/
│   │   │   ├── Sidebar/
│   │   │   └── Footer/
│   │   ├── styles/           # 样式文件
│   │   │   ├── main.css
│   │   │   └── variables.css
│   │   └── utils/            # 渲染进程工具
│   │       ├── api.js        # API 封装
│   │       └── helpers.js    # 辅助函数
│   │
│   └── preload/              # 预加载脚本
│       ├── index.js          # 预加载入口
│       └── api.js            # 暴露给渲染进程的 API
│
├── resources/                # 应用资源
│   ├── icon.png              # 应用图标
│   ├── icon.icns             # macOS 图标
│   ├── icon.ico              # Windows 图标
│   ├── tray/                 # 托盘图标
│   │   ├── normal.png
│   │   └── pressed.png
│   └── installer/            # 安装程序资源
│       └── background.png
│
├── build/                    # 构建配置
│   ├── webpack.main.config.js    # 主进程构建配置
│   ├── webpack.renderer.config.js # 渲染进程构建配置
│   └── webpack.rules.js          # 公共构建规则
│
├── tests/                    # 测试文件
│   ├── unit/                 # 单元测试
│   ├── integration/          # 集成测试
│   └── e2e/                  # 端到端测试
│
├── docs/                     # 项目文档
│   ├── API.md
│   └── CHANGELOG.md
│
├── scripts/                  # 构建脚本
│   ├── build.js              # 构建脚本
│   └── release.js            # 发布脚本
│
├── .gitignore                # Git 忽略配置
├── .npmrc                    # npm 配置
├── .eslintrc.js              # ESLint 配置
├── .prettierrc               # Prettier 配置
├── README.md                 # 项目说明
└── LICENSE                   # 开源许可证

大型项目结构

适用于企业级应用,支持多窗口、多团队协作:

code
enterprise-electron-app/
├── package.json              # 根项目配置(Monorepo)
├── lerna.json                # Lerna 配置(可选)
├── 
├── packages/                 # 多包目录
│   ├── main/                 # 主进程包
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── index.js
│   │   │   ├── services/     # 服务模块
│   │   │   ├── database/     # 数据库模块
│   │   │   └── network/      # 网络模块
│   │   └── tests/
│   │
│   ├── renderer/             # 渲染进程包
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── pages/        # 页面
│   │   │   ├── components/   # 组件
│   │   │   ├── store/        # 状态管理
│   │   │   └── router/       # 路由配置
│   │   └── public/
│   │
│   ├── preload/              # 预加载脚本包
│   │   ├── package.json
│   │   └── src/
│   │
│   ├── shared/               # 共享代码
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── types/        # TypeScript 类型
│   │   │   ├── constants/    # 常量定义
│   │   │   └── utils/        # 工具函数
│   │   └── index.js
│   │
│   └── ui/                   # UI 组件库
│       ├── package.json
│       └── src/
│
├── resources/                # 共享资源
├── scripts/                  # 构建脚本
├── docs/                     # 项目文档
└── config/                   # 配置文件
    ├── webpack.base.js
    ├── webpack.main.js
    └── webpack.renderer.js

配置文件详解

1. package.json

json
{
  "name": "my-electron-app",
  "version": "1.0.0",
  "description": "Electron 应用描述",
  "main": "./src/main/index.js",
  "author": "Your Name",
  "license": "MIT",
  
  "scripts": {
    "start": "electron .",
    "dev": "NODE_ENV=development electron .",
    "build": "npm run build:main && npm run build:renderer",
    "build:main": "webpack --config build/webpack.main.config.js",
    "build:renderer": "webpack --config build/webpack.renderer.config.js",
    "package": "electron-builder",
    "package:win": "electron-builder --win",
    "package:mac": "electron-builder --mac",
    "package:linux": "electron-builder --linux",
    "lint": "eslint src/",
    "test": "jest",
    "test:e2e": "playwright test"
  },
  
  "dependencies": {
    "electron-updater": "^6.0.0",
    "electron-store": "^8.0.0"
  },
  
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.0.0",
    "webpack": "^5.0.0",
    "eslint": "^8.0.0",
    "jest": "^29.0.0"
  },
  
  "build": {
    "appId": "com.example.myapp",
    "productName": "My Electron App",
    "directories": {
      "output": "dist",
      "buildResources": "resources"
    },
    "files": [
      "src/main/**/*",
      "src/preload/**/*",
      "dist/renderer/**/*",
      "package.json"
    ],
    "mac": {
      "category": "public.app-category.utilities",
      "icon": "resources/icon.icns"
    },
    "win": {
      "icon": "resources/icon.ico",
      "target": "nsis"
    },
    "linux": {
      "icon": "resources/icon.png",
      "target": "AppImage"
    }
  }
}

关键字段说明:

  • main: 主进程入口文件路径
  • scripts.build: 构建命令
  • scripts.package: 打包命令
  • build: electron-builder 配置
  • build.files: 打包时包含的文件
  • build.directories.output: 打包输出目录

2. .gitignore

gitignore
# 依赖目录
node_modules/

# 构建输出
dist/
out/
build/

# 日志文件
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# 操作系统文件
.DS_Store
Thumbs.db

# IDE 配置
.idea/
.vscode/
*.swp
*.swo

# 环境变量
.env
.env.local
.env.production

# 打包文件
*.dmg
*.app
*.exe
*.zip

# 测试覆盖率
coverage/

# 临时文件
tmp/
temp/

3. .npmrc (可选)

ini
# 使用淘宝镜像加速下载
registry=https://registry.npmmirror.com

# Electron 二进制包镜像
electron_mirror=https://npmmirror.com/mirrors/electron/

# Electron Builder 镜像
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/

# 严格依赖版本
save-exact=true

# 自动生成 package-lock.json
package-lock=true

4. .eslintrc.js (可选)

javascript
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true
  },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended'
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
  }
}

关键文件模板

主进程入口 (src/main/index.js)

javascript
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { registerIPC } = require('./ipc')
const { createMenu } = require('./menu')

// 开发环境检测
const isDev = process.env.NODE_ENV === 'development'

// 窗口引用
let mainWindow = null

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  })

  // 开发环境加载开发服务器
  if (isDev) {
    mainWindow.loadURL('http://localhost:3000')
    mainWindow.webContents.openDevTools()
  } else {
    // 生产环境加载打包后的文件
    mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'))
  }

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

// 应用初始化
app.whenReady().then(() => {
  createWindow()
  createMenu()
  registerIPC()
  
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

IPC 通信模块 (src/main/ipc/index.js)

javascript
const { ipcMain } = require('electron')
const handlers = require('./handlers')

function registerIPC() {
  // 注册所有 IPC 处理器
  Object.keys(handlers).forEach(channel => {
    ipcMain.handle(channel, handlers[channel])
  })
  
  console.log('IPC 处理器注册完成')
}

module.exports = { registerIPC }

预加载脚本 (src/preload/index.js)

javascript
const { contextBridge, ipcRenderer } = require('electron')

// 有效的 IPC 通道白名单
const validChannels = {
  invoke: ['dialog:open', 'file:read', 'file:write'],
  send: ['app:log', 'window:minimize'],
  receive: ['update:available', 'notification:show']
}

// 安全暴露 API 到渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
  // 双向通信
  invoke: async (channel, ...args) => {
    if (validChannels.invoke.includes(channel)) {
      return await ipcRenderer.invoke(channel, ...args)
    }
    throw new Error(`Invalid invoke channel: ${channel}`)
  },
  
  // 单向通信:发送
  send: (channel, ...args) => {
    if (validChannels.send.includes(channel)) {
      ipcRenderer.send(channel, ...args)
    }
  },
  
  // 单向通信:接收
  receive: (channel, callback) => {
    if (validChannels.receive.includes(channel)) {
      const subscription = (event, ...args) => callback(...args)
      ipcRenderer.on(channel, subscription)
      
      // 返回取消订阅函数
      return () => ipcRenderer.removeListener(channel, subscription)
    }
  }
})

构建配置

Webpack 配置示例

主进程构建配置 (build/webpack.main.config.js):

javascript
const path = require('path')

module.exports = {
  entry: './src/main/index.js',
  target: 'electron-main',
  output: {
    path: path.resolve(__dirname, '../dist/main'),
    filename: 'index.js'
  },
  node: {
    __dirname: false,
    __filename: false
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js'],
    alias: {
      '@main': path.resolve(__dirname, '../src/main'),
      '@shared': path.resolve(__dirname, '../src/shared')
    }
  }
}

渲染进程构建配置 (build/webpack.renderer.config.js):

javascript
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  entry: './src/renderer/renderer.js',
  target: 'electron-renderer',
  output: {
    path: path.resolve(__dirname, '../dist/renderer'),
    filename: 'renderer.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/renderer/index.html',
      filename: 'index.html'
    })
  ],
  resolve: {
    extensions: ['.js', '.css'],
    alias: {
      '@renderer': path.resolve(__dirname, '../src/renderer'),
      '@shared': path.resolve(__dirname, '../src/shared')
    }
  }
}

资源文件管理

应用图标

不同平台需要不同格式的图标:

平台图标格式推荐尺寸
macOS.icns512x512, 256x256, 128x128, 64x64, 32x32, 16x16
Windows.ico256x256, 128x128, 64x64, 48x48, 32x32, 16x16
Linux.png512x512

生成工具:

bash
# 安装工具
npm install -D electron-icon-builder

# 从源图生成各平台图标
npx electron-icon-builder --input=./assets/icon.png --output=./resources

ASAR 打包

ASAR 是一种简单的归档格式,可以减少文件数量,提升应用启动速度:

json
// package.json
{
  "build": {
    "asar": true,
    "asarUnpack": [
      "**/*.node"  // 不打包原生模块
    ]
  }
}

最佳实践

1. 代码分离

javascript
// ✅ 推荐:按功能模块组织
src/main/
├── services/
│   ├── DatabaseService.js
│   └── FileService.js
├── utils/
│   ├── logger.js
│   └── validator.js
└── ipc/
    └── handlers.js

// ❌ 不推荐:所有代码写在一个文件
src/main/index.js  // 包含所有逻辑

2. 环境变量管理

javascript
// config/env.js
const path = require('path')

const env = process.env.NODE_ENV || 'development'

require('dotenv').config({
  path: path.resolve(__dirname, `../.env.${env}`)
})

module.exports = {
  isDev: env === 'development',
  isProd: env === 'production',
  apiBaseUrl: process.env.API_BASE_URL
}

3. 类型安全 (TypeScript)

code
src/
├── main/
│   ├── index.ts
│   └── types.ts       # 主进程类型定义
├── renderer/
│   ├── index.ts
│   └── types.ts       # 渲染进程类型定义
├── preload/
│   └── index.ts
└── shared/
    └── types.ts       # 共享类型定义

4. 测试文件组织

code
tests/
├── unit/                    # 单元测试
│   ├── main/
│   │   └── services/
│   └── renderer/
│       └── components/
├── integration/             # 集成测试
│   └── ipc.test.js
└── e2e/                     # 端到端测试
    └── app.test.js

常见问题

Q: 如何选择项目结构?

A: 根据项目规模选择:

  • 小型项目(< 5000 行代码):使用基础结构
  • 中型项目(5000-20000 行代码):使用标准结构
  • 大型项目(> 20000 行代码):使用企业级结构或 Monorepo

Q: 主进程和渲染进程代码能共用吗?

A: 可以。将共享代码放在 src/shared/ 目录,通过构建工具的 alias 配置引用:

javascript
// 主进程引用共享模块
const { formatDate } = require('@shared/utils')

// 渲染进程引用共享模块
import { formatDate } from '@shared/utils'

Q: 如何处理静态资源路径?

A: 使用 __dirnamepath.join

javascript
// 主进程
const iconPath = path.join(__dirname, '../resources/icon.png')

// 渲染进程(HTML)
<img src="./assets/images/logo.png">

// 渲染进程(JavaScript)
const logoUrl = new URL('./assets/images/logo.png', import.meta.url).href

Q: 如何优化打包体积?

A: 策略包括:

  1. 使用 ASAR 打包
  2. 排除不必要的文件(配置 build.files
  3. 代码分割和 Tree Shaking
  4. 压缩资源文件
  5. 使用 externals 排除大型依赖

Q: 如何管理多窗口项目?

A: 为每个窗口创建独立的目录:

code
src/renderer/
├── main-window/       # 主窗口
│   ├── index.html
│   └── renderer.js
├── settings-window/   # 设置窗口
│   ├── index.html
│   └── renderer.js
└── about-window/      # 关于窗口
    ├── index.html
    └── renderer.js

相关资源