人人都会AI编程

2.3 标准项目目录结构与职责划分

更新时间:2026-07-11

当你用脚手架创建完第一个 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 里定义 devbuild 等命令,以及通过 engineselectron 字段声明支持的 Electron 版本。
  • electron-builder.yml:打包配置文件,指定应用 ID、产品名称、目标系统、文件包含规则、自动更新渠道等。和 Web 项目的 webpack/vite 配置一样,它是决定最终安装包形态的核心文件。
  • tsconfig.json:TypeScript 配置。因为主进程、预加载和渲染进程的运行环境不同(Node.js vs 浏览器),实际项目中通常会使用多个配置文件(如 tsconfig.main.jsontsconfig.renderer.json)来分别指定 moduletargetlib
  • vite.config.ts:仅负责渲染进程的构建。Electron 项目大多选择将渲染进程作为一个独立的 Vite 项目处理,主进程的编译则可能直接用 tscesbuild。这种解耦避免了把主进程代码也塞进浏览器的打包逻辑。

2. src/main/ — 主进程代码

主进程是 Electron 应用的大脑,所有与操作系统交互的“硬活”都在这里完成。

  • index.ts:应用的入口文件(对应 package.json 中的 main 字段)。它的典型逻辑是监听 appready 事件,创建主窗口,注册全局快捷键或托盘,以及处理 before-quit 等生命周期事件。这里不应写具体的业务逻辑,而是充当“调度中心”。
  • window.ts:封装 BrowserWindow 的创建,负责窗口尺寸、位置、预加载脚本路径、安全设置(contextIsolationnodeIntegration)等。当应用支持多窗口时,这个模块会提供统一的工厂方法。
  • ipc/:集中管理所有 IPC(进程间通信)的监听器。按照功能拆分为多个文件(如 file.tsdialog.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.tsxmain.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 协作流程示例

用一个真实场景来串起这个结构的工作方式:用户在界面上点击“打开文件”按钮。

  1. 渲染进程中的按钮组件调用 window.electronAPI.openFile()
  2. window.electronAPI.openFile 是预加载脚本通过 contextBridge 暴露的方法,它内部调用 ipcRenderer.invoke('file:open')
  3. 主进程在 ipc/ 目录中注册了对应的 file:open 处理器,该处理器接收到请求后,调用 dialog.showOpenDialog 显示原生文件选择框,然后用 fs.readFile 读取文件内容,最后将结果返回。
  4. 渲染进程获得返回的文件内容后更新界面。

这个过程中,代码的组织完全映射了流程的每个阶段:渲染层 → 预加载桥接 → IPC 分发 → 主进程业务处理。任何一个环节出了问题,开发者都可以快速定位到对应的目录和文件。

2.3.4 目录结构的选择依据

为什么推荐这种主进程/预加载/渲染进程三层物理分离的结构,而不是把所有代码都塞进一个 src 平铺?

  • 安全审查方便:预加载脚本独立成目录,可以清晰看到到底暴露了哪些接口给前端,避免一些开发者图省事直接在渲染进程开启 nodeIntegration
  • 编译流程独立:主进程和渲染进程的编译目标、环境配置完全不同,分开管理可以避免构建工具混乱,同时也减少了“浏览器环境引用了 Node 模块”这类构建错误。
  • 测试范围明确:单元测试可以只针对 main/ 进行纯 Node 环境测试,e2e 测试可以启动整个应用并操作渲染界面,职责清晰。
  • 团队协作边界:如果一个团队同时有前端和 Node.js 背景的开发者,这种结构天然对应了他们的技能领域:前端负责 renderer/,Node.js 开发者负责 main/preload/,共享类型由技术主管把控。

对于非常小的工具(比如只有一个窗口、两三个按钮),你当然可以把主进程代码放在根目录下的 main.js,渲染进程直接用原生 HTML 写在一个文件夹内。但在任何稍具规模的商业项目中,上面这种结构已经是被广泛验证的最佳实践,可以直接采用、按需调整。