当你用脚手架创建完第一个 Electron 项目后,接下来要面对的就是如何组织代码。一个清晰、合理的目录结构不仅能降低团队协作的摩擦,也会直接影响后续的打包、测试和维护效率。这一节会给出一个经过大量实际项目验证的推荐结构,并逐一说明每个部分的职责。
2.3.1 推荐的项目结构
以下是一个典型的中型 Electron 项目(假设渲染进程使用 React + TypeScript,打包工具为 Vite)的目录布局:
my-electron-app/
├── package.json
├── electron-builder.yml
├── tsconfig.json
├── vite.config.ts # Vite 配置(渲染进程)
├── index.html # 渲染进程入口 HTML
├── src/
│ ├── main/ # 主进程代码
│ │ ├── index.ts # 主进程入口,应用生命周期管理
│ │ ├── window.ts # 窗口创建与管理
│ │ ├── ipc/ # IPC 通信处理模块
│ │ │ ├── index.ts # 注册所有 IPC 处理器
│ │ │ └── file.ts # 文件操作相关 IPC
│ │ ├── native/ # 系统原生能力封装(托盘、菜单、通知等)
│ │ │ ├── tray.ts
│ │ │ ├── menu.ts
│ │ │ └── notification.ts
│ │ └── utils/ # 主进程工具函数
│ │ └── platform.ts # 平台判断、路径处理
│ ├── preload/ # 预加载脚本
│ │ ├── index.ts # 预加载入口,暴露安全的 API 给渲染进程
│ │ └── api/ # 按功能拆分的 API
│ │ └── file.ts # 文件相关 API 暴露
│ ├── renderer/ # 渲染进程(前端代码)
│ │ ├── App.tsx
│ │ ├── main.tsx # 渲染进程入口
│ │ ├── components/ # 通用组件
│ │ ├── pages/ # 页面视图
│ │ ├── hooks/ # 自定义 Hook
│ │ └── assets/ # 样式、图片等静态资源
│ └── shared/ # 主进程与渲染进程共享的类型/常量
│ ├── types.ts # IPC 通道名称、接口定义
│ └── constants.ts
├── resources/ # 应用图标等静态资源(打包用)
│ ├── icon.ico
│ ├── icon.icns
│ └── icon.png
└── tests/ # 测试(可根据需要细分 e2e/unit)
├── e2e/
└── unit/
这个结构不是 Electron 官方强制要求的,但它的核心思想是 严格区分主进程、预加载脚本和渲染进程,并通过一个共享目录约定接口。如果你的项目很小,可以适当扁平化一些,但基本的三层分离建议保留。
2.3.2 各部分职责说明
1. 根目录配置文件
- package.json:定义项目依赖、脚本命令和基本元信息。关键字段除了
main指向主进程入口外,还会在scripts里定义dev、build等命令,以及通过engines或electron字段声明支持的 Electron 版本。 - electron-builder.yml:打包配置文件,指定应用 ID、产品名称、目标系统、文件包含规则、自动更新渠道等。和 Web 项目的 webpack/vite 配置一样,它是决定最终安装包形态的核心文件。
- tsconfig.json:TypeScript 配置。因为主进程、预加载和渲染进程的运行环境不同(Node.js vs 浏览器),实际项目中通常会使用多个配置文件(如
tsconfig.main.json、tsconfig.renderer.json)来分别指定module、target和lib。 - vite.config.ts:仅负责渲染进程的构建。Electron 项目大多选择将渲染进程作为一个独立的 Vite 项目处理,主进程的编译则可能直接用
tsc或esbuild。这种解耦避免了把主进程代码也塞进浏览器的打包逻辑。
2. src/main/ — 主进程代码
主进程是 Electron 应用的大脑,所有与操作系统交互的“硬活”都在这里完成。
- index.ts:应用的入口文件(对应
package.json中的main字段)。它的典型逻辑是监听app的ready事件,创建主窗口,注册全局快捷键或托盘,以及处理before-quit等生命周期事件。这里不应写具体的业务逻辑,而是充当“调度中心”。 - window.ts:封装
BrowserWindow的创建,负责窗口尺寸、位置、预加载脚本路径、安全设置(contextIsolation、nodeIntegration)等。当应用支持多窗口时,这个模块会提供统一的工厂方法。 - ipc/:集中管理所有 IPC(进程间通信)的监听器。按照功能拆分为多个文件(如
file.ts、dialog.ts),然后在index.ts中统一注册。这样做的好处是,任何渲染进程发起的请求入口都清晰可查,避免在主进程入口文件里堆积大量ipcMain.handle。 - native/:系统原生能力的封装层。把托盘图标、菜单模板、系统通知等功能变成可复用的模块,供主进程调用。这一层让业务代码与底层 API 解耦,以后如果需要修改菜单结构,只需要改这个目录。
- utils/:主进程专用的工具函数,比如平台检测、路径规范化、日志记录等。由于主进程运行在 Node.js 环境,这些工具可以直接使用 Node.js 核心模块。
3. src/preload/ — 预加载脚本
预加载脚本是 Electron 安全模型的核心。它运行在一个拥有部分 Node.js 和 Electron API 访问权的上下文里,但又不隶属于任何网页的全局作用域。
- index.ts:预加载脚本的入口。在这里实例化
contextBridge,将主进程能力安全地暴露给渲染进程。通常只会暴露一个全局对象(如window.electronAPI),里面包含经过封装的方法。 - api/:按功能拆分的暴露接口。例如
file.ts可以暴露openFile()、saveFile()方法,它们内部使用ipcRenderer.invoke与主进程通信。所有暴露的方法都应该是白名单化的,避免直接将ipcRenderer或 Node.js 模块丢给渲染进程。
4. src/renderer/ — 渲染进程(前端部分)
这一层就是常规的前端项目结构,可以使用任何你喜欢的框架。
- 入口文件(
main.tsx或main.js)挂载 React/Vue 应用到index.html的#root节点。 - 组件、页面、路由、状态管理等完全参照 Web 项目的最佳实践,不作特殊限制。
- 与 Web 项目的唯一区别:渲染进程不直接调用任何 Node.js 或系统 API,而是通过预加载脚本暴露的
window.electronAPI来间接触发主进程操作。这保证了界面层和安全层的隔离。
5. src/shared/ — 共享类型
主进程和渲染进程之间需要约定一致的消息通道名、请求/响应数据结构。把这些定义放在 shared 目录中,两边共同引用,可以有效避免“通道名字拼错导致运行时莫名失效”的困扰。例如:
// src/shared/types.ts
export const IPC_CHANNELS = {
FILE_OPEN: 'file:open',
FILE_SAVE: 'file:save',
} as const;
export interface FileOpenRequest { path: string; }
export interface FileOpenResponse { content: string; }
主进程的 IPC 处理器和渲染进程的调用代码都导入这些常量,实现了端到端的类型安全。
2.3.3 协作流程示例
用一个真实场景来串起这个结构的工作方式:用户在界面上点击“打开文件”按钮。
- 渲染进程中的按钮组件调用
window.electronAPI.openFile()。 window.electronAPI.openFile是预加载脚本通过contextBridge暴露的方法,它内部调用ipcRenderer.invoke('file:open')。- 主进程在
ipc/目录中注册了对应的file:open处理器,该处理器接收到请求后,调用dialog.showOpenDialog显示原生文件选择框,然后用fs.readFile读取文件内容,最后将结果返回。 - 渲染进程获得返回的文件内容后更新界面。
这个过程中,代码的组织完全映射了流程的每个阶段:渲染层 → 预加载桥接 → IPC 分发 → 主进程业务处理。任何一个环节出了问题,开发者都可以快速定位到对应的目录和文件。
2.3.4 目录结构的选择依据
为什么推荐这种主进程/预加载/渲染进程三层物理分离的结构,而不是把所有代码都塞进一个 src 平铺?
- 安全审查方便:预加载脚本独立成目录,可以清晰看到到底暴露了哪些接口给前端,避免一些开发者图省事直接在渲染进程开启
nodeIntegration。 - 编译流程独立:主进程和渲染进程的编译目标、环境配置完全不同,分开管理可以避免构建工具混乱,同时也减少了“浏览器环境引用了 Node 模块”这类构建错误。
- 测试范围明确:单元测试可以只针对
main/进行纯 Node 环境测试,e2e 测试可以启动整个应用并操作渲染界面,职责清晰。 - 团队协作边界:如果一个团队同时有前端和 Node.js 背景的开发者,这种结构天然对应了他们的技能领域:前端负责
renderer/,Node.js 开发者负责main/和preload/,共享类型由技术主管把控。
对于非常小的工具(比如只有一个窗口、两三个按钮),你当然可以把主进程代码放在根目录下的 main.js,渲染进程直接用原生 HTML 写在一个文件夹内。但在任何稍具规模的商业项目中,上面这种结构已经是被广泛验证的最佳实践,可以直接采用、按需调整。