- 开发工具
- 桌面应用
【免费下载链接】desktop
Fork of GitHub Desktop to support various Linux distributions
导读
本文基于 GitHub Desktop 仓库中一份仍在演进中的规划文档 docs/technical/future-repository-structure.md,梳理该项目对app/src源码目录的目标组织方案:哪些模块应当跨 Webpack bundle 共享、哪些逻辑应当归属到哪个进程或入口,以及 renderer 内部各个子目录的职责边界。文章会逐条对照文档中规划的目录与实际仓库现状,并结合 app/webpack.common.ts、main-process、cli、highlighter等入口源码给出可验证的落地证据,帮助新贡献者快速理解"代码该放哪里、谁能消费它"这一核心问题。
一、文档背景与定位:一份"进行中"的结构蓝图
文档开篇即声明这是一份work in progress(进行中)的文档,它回答的问题是:
在 GitHub Desktop 源码中,事物应该在哪里被发现、代码将如何被组织。
同时作者给出了两条重要约束:
- 该文档会随时间持续更新,因为这是一次渐进式的重构(incremental process),存在大量未知数;
- 重构期间不能停止发布功能("we want to continue to ship features while doing this work"),即目录调整必须与持续交付并行推进。
因此,本文档描述的是一种目标态而非现状。读者在对照代码时应以"文档规划 + 仓库现状"双重视角阅读:规划用于指导新代码的落位,现状则反映迁移尚未完成的部分。
从文档结构看,规划分两大层次:
- 共享模块(Shared Modules):可被 Desktop 生成的任何 Webpack bundle 复用的代码;
- 应用 Bundle(Application Bundles):每个 bundle 目录下会有一个特定入口文件,由 Webpack 配置转译打包。
二、共享模块:models与lib的职责划分
文档将可跨 bundle 共享的模块限定在两个目录:
app/src/models—— 纯数据结构
包含代码库中用于表示常见对象的"形状"(shapes)。它们应当是immutable(不可变)且 plain(朴素/无副作用)的。
从 app/src/models 目录的现状来看,这一规划已经落地:branch.ts、commit.ts、repository.ts、pull-request.ts、merge.ts、rebase.ts、tip.ts、status.ts等文件全部是领域对象的类型定义与轻量构造逻辑,不依赖具体运行环境。以commit.ts中的提交对象、account.ts中的账户模型为例,它们既被主进程、渲染进程引用,也被 CLI 和测试引用,因此必须保持"纯"——只描述数据,不携带平台相关行为。
app/src/lib—— 环境无关的函数
包含不依赖在特定环境中执行的函数。
这里的"特定环境"主要指 Electron 主进程、渲染进程、Web Worker、Node CLI 等执行上下文。app/src/lib 中大量工具函数符合该约束,例如:
fuzzy-find.ts(模糊匹配算法)format-date.ts、format-duration.ts、format-relative.ts(格式化工具)parse-app-url.ts、remote-parsing.ts、sanitize-ref-name.tspath.ts、clamp.ts、promise.ts等纯工具
这些模块被各 bundle 共享时不会引入 DOM、window、process.platform等环境依赖(平台相关的判断通过 Webpack 替换在构建期完成,详见后文对globals.d.ts的说明)。
文档补充约定:bundle 内部也需要models/lib
文档特别指出:
对于与某个特定 bundle 关联、且不打算跨 bundle 共享的逻辑或功能,应当遵循同样的模式,放在该 bundle 目录内部的
models或lib子目录中。
也就是说,"共享"是分层决策:全局共享放app/src/models与app/src/lib;局部共享/专属逻辑放各 bundle 内的models/lib。这一约定直接体现在下面的 renderer 目录规划中。
三、应用 Bundle:Webpack 配置视角下的五个入口
文档称这些目录为bundles(束),因为 Webpack 配置会对每个目录中的特定文件做转译,生成打包与运行应用所需的内容。
我们之所以称这些文件夹为 "bundles",是因为我们的 webpack 配置会对每个目录中的一个特定文件进行转译,以生成打包和运行应用所需的内容。
对照 app/webpack.common.ts 可以精确验证这一定义——每个 bundle 对应一个entry,并注入一个区分进程种类的替换变量__PROCESS_KIND__:
| Bundle | 目录规划(文档) | 实际入口(webpack.common.ts) | target |
|---|---|---|---|
| 主进程 | app/src/main | src/main-process/main | electron-main |
| 用户界面 | app/src/renderer | src/ui/index | electron-renderer |
| 高亮 Worker | app/src/highlighter | src/highlighter/index | webworker |
| 崩溃窗口 | app/src/crash | src/crash/index | electron-renderer |
| 命令行接口 | app/src/cli | src/cli/main | node |
这里有一个值得注意的细节:文档规划中的目录名(main、renderer)与仓库实际目录名(main-process、ui)并不完全一致。这恰恰印证了文档"进行中、会随实现调整"的定位——规划给出的是目标态命名,而当前仓库仍沿用旧命名(如 app/src/main-process/main.ts、app/src/ui/index.tsx)。本文后续引用一律以仓库现状路径为准。
下面逐一解读五个 bundle。
四、主进程 Bundle:app/src/main(现状为app/src/main-process)
文档定位:
为主进程打包的模块与逻辑,是用户启动 Desktop 的入口点。
主进程是 Electron 应用中唯一拥有操作系统级能力的进程:负责创建窗口、菜单、系统托盘、原生通知、协议处理、自动更新等。从 app/src/main-process/main.ts 的导入清单可以看到典型的主进程职责:
- 创建应用窗口(
AppWindow)、构建默认菜单(buildDefaultMenu) - 处理 Squirrel 更新事件(
handleSquirrelEvent,Windows 安装/卸载钩子) - 安装 IPC 处理器(
ipc-main) - 安装全局异常上报(
exception-reporting)、未捕获异常展示(show-uncaught-exception) - 读取标题栏配置(
readTitleBarConfigFileSync)等平台相关操作
主进程目录下的子模块(如 app/src/main-process/menu)也遵循"bundle 专属逻辑就近存放"的约定:菜单构建、上下文菜单等逻辑只服务于主进程,不进入共享的lib。
五、用户界面 Bundle:app/src/renderer(现状为app/src/ui)——文档着墨最多的部分
文档明确指出:
渲染进程负责显示用户界面并处理 Desktop 中的大部分数据管理。
由于这是当前代码库中体量最大的部分("the largest part of the current codebase"),文档专门为其勾画了目标目录结构:
app └── src └── renderer ├── components │ ├── dialogs │ ├── primitives │ └── text ├── lib │ └── git ├── models ├── stores └── views作者坦承"关于如何组织 React 项目有大量观点存在",因此这份规划刻意聚焦于解决以下四个实际问题:
- 更好地组织 React 组件(
components) - 更好地组织渲染进程所需模块(
lib) - 厘清哪些模块可在应用各部分之间共享、哪些应保持局部专属
- 反映当前真实的使用模式——Git 操作发生在渲染进程、store 在渲染进程创建与管理
5.1components—— React 组件
包含应用中使用的 React 组件。组织方式上没有强烈意见,但更好的组织能简化其他地方的 import。文档还提到了基于现有组件可能形成的子目录分组:
dialogs、primitives、text。
对照现状 app/src/ui,该规划在仓库中体现为大量按业务域划分的组件目录:changes/、history/、branches/、diff/、banners/、toolbar/、dialog/等;而文档提到的三类"新分组"在现有代码中也有对应物,例如dialog/(对话框基座)、octicons/等基础组件、commit-message/、text类展示组件。规划的本质是让目录名自解释,从而简化import路径并降低新贡献者的定位成本。
5.2lib与lib/git—— 渲染进程专属逻辑与 Git 功能
lib:渲染进程专属的函数与逻辑lib/git:当前的 Git 功能,本地化用于渲染进程
这一规划在现状中的对应物是 app/src/ui/lib 与 app/src/lib/git。注意这里存在文档规划与现状的错位:Git 模块当前位于共享层app/src/lib/git,而文档希望它未来归入 renderer 的lib/git。原因是 Git 操作目前在 Desktop 中实际由渲染进程发起执行(文档"reflect our current usage patterns"的第一条即"Git operations performed in the renderer")。
可以用引用关系验证这一点:app/src/ui/app.tsx、app/src/ui/app-error.tsx、app/src/ui/missing-repository.tsx 等渲染进程组件都直接from '../lib/git'引入 Git 工具,说明 Git 逻辑的主要消费方就是 UI 层。规划希望把这种"事实上的归属"显式化。
5.3models—— 渲染进程专属的数据类型
渲染进程专属的接口与类。
与共享层app/src/models的"全局可复用形状"不同,这里的models只服务渲染进程。现状中 UI 层没有独立的models目录,相关类型大多仍集中在共享层或各组件目录内——这属于文档描述的"渐进迁移"尚未完成的部分。
5.4stores—— 从lib/stores迁移而来的状态管理层
现有的 store 集合,来自
lib/stores。
现状中 store 确实位于共享层 app/src/lib/stores:app-store.ts、repositories-store.ts、accounts-store.ts、sign-in-store.ts、pull-request-store.ts、git-store.ts、commit-status-store.ts、notifications-store.ts等一应俱全。文档的目标是把它们迁入 renderer 的stores目录——这与"stores created and managed in the renderer"(store 在渲染进程创建与管理)的现状模式一致。
5.5views—— 基于仓库状态渲染的顶层组件
文档给出了非常具体的清单:
views是顶层组件,我们根据仓库的状态渲染它们 ——repository.tsx、cloning-repository.tsx和missing-repository.tsx。
对照现状可以精确验证:
- app/src/ui/repository.tsx —— 仓库主视图(常规状态)
- app/src/ui/cloning-repository.tsx —— 克隆进行中的视图
- app/src/ui/missing-repository.tsx —— 仓库缺失/无法访问时的视图
这印证了"视图由仓库状态驱动"的设计:Desktop 根据当前选中的仓库处于"正常 / 克隆中 / 缺失"哪种状态,决定渲染哪一个顶层组件。
5.6 入口文件约定
文档最后补充:
入口
index.tsx应保留在根目录,其他所有文件都应移动到磁盘上更合适的位置。
现状中 app/src/ui/index.tsx 正是渲染进程的 Webpack 入口(对应webpack.common.ts中entry: { renderer: path.resolve(__dirname, 'src/ui/index') }),其余组件/工具均已下沉到各自业务目录——这一约定已基本实现。
六、高亮 Worker Bundle:app/src/highlighter
文档定位:
Desktop 初始化该 Web Worker,用于对 diff 中的代码进行异步语法高亮计算。
对照 app/src/highlighter/index.ts,其实现细节完全吻合:它不导入完整的 CodeMirror,而是只引入codemirror/addon/runmode/runmode.node.js这一最小子集("This hack is brought to you by webpack"),通过getMode/innerMode/StringStream在 Worker 上下文中运行 CodeMirror 的 mode 完成分词。
这与 app/webpack.common.ts 中 highlighter 的专门配置互为印证:
target: 'webworker'—— 明确打包为 Worker;- 通过
resolve.alias将codemirror替换为runmode.node.js精简版; - 使用独立的 app/src/highlighter/tsconfig.json 编译;
- 按 CodeMirror mode 拆分 chunk(
splitChunks.cacheGroups.modes),实现按需加载各语言模式。
可见,"独立目录 + 独立入口 + 独立打包配置"正是文档所定义的 bundle 形态的典型样本。
七、崩溃窗口 Bundle:app/src/crash
文档定位:
Desktop 用于在未处理错误导致主应用崩溃时展示默认 UI 的模块与逻辑。
现状中 app/src/crash 包含crash-app.tsx、index.tsx、shared.ts以及样式 app/src/crash/styles/crash.scss。Webpack 侧对应 app/webpack.common.ts 中crash配置:以src/crash/index为入口、输出crash.html、注入__PROCESS_KIND__ = 'crash'。它被设计成一个独立、轻量的渲染进程 bundle,确保主进程异常时仍能拉起一个可用的错误提示界面,而不是黑屏。
八、命令行接口 Bundle:app/src/cli
文档定位:
为
github命令行接口打包的模块与逻辑,用户可以为 Desktop 启用该命令。
现状中 app/src/cli 的结构清晰对应文档规划:
- app/src/cli/main.ts —— CLI 入口,使用
mri解析参数,默认命令为open; - app/src/cli/commands —— 子命令实现:
clone.ts、open.ts、help.ts; - app/src/cli/load-commands.ts —— 命令注册表;
- app/src/cli/util.ts —— 命令错误与参数整理工具。
Webpack 侧对应cli配置:以src/cli/main为入口、target: 'node',因此 CLI 被打包为独立的 Node 程序而非 Electron 应用——这与文档"为github命令打包"的定位一致。
九、为什么要做这些迁移?——文档给出的三个理由
在读完目录规划后,文档以"Why move all this stuff around?"为题,正面回应"现有代码能正常工作,为什么要折腾"的质疑,给出三条理由:
- 代码库在交付压力下有机生长("grown organically over time amid the pressures of shipping"),现在是最好的时机去重新审视并质疑项目早期的假设;
- 代码库已足够复杂,关于"东西该放哪里"的困惑正在蔓延,团队已积累足够经验来建立结构;
- 随着新贡献者不断加入,需要让"代码该放哪里、哪些模块可被谁消费"比今天更显而易见,并构建工具来保证代码组织与打包方式在逻辑上一致("build tooling to ensure things are logically organized for how we build and package Desktop")。
第 3 条中的"tooling"在仓库中已有部分实现痕迹:例如 docs/technical/placeholders.md 与 app/src/lib/globals.d.ts 通过全局占位符约束"哪些标识符可以在构建期被替换",app/webpack.common.ts 通过getReplacements()统一注入各 bundle。这些机制保证了即便目录继续演进,打包产物仍保持一致。
十、从规划到现状:一份"对照清单"总结
结合全文,将文档规划与当前仓库现状整理为对照表,方便新贡献者"按图索骥":
| 文档规划目录 | 当前仓库路径(实际) | 职责 | 关键文件 |
|---|---|---|---|
app/src/models(共享) | app/src/models | 不可变、朴素的数据形状 | repository.ts、commit.ts、branch.ts |
app/src/lib(共享) | app/src/lib | 环境无关的函数 | fuzzy-find.ts、format-date.ts |
app/src/main | app/src/main-process | 主进程入口与专属逻辑 | main.ts、app-window.ts |
app/src/renderer | app/src/ui | 渲染进程 UI 与数据管理 | index.tsx、app.tsx |
| renderer/components | app/src/ui 下的业务目录 | React 组件 | changes/、history/、dialog/ |
| renderer/lib/git | app/src/lib/git(待迁移) | 渲染进程使用的 Git 操作 | core.ts、checkout.ts、log.ts |
| renderer/stores | app/src/lib/stores(待迁移) | 状态管理 store | app-store.ts、repositories-store.ts |
| renderer/views | app/src/ui/repository.tsx 等 | 按仓库状态渲染的顶层组件 | repository.tsx、cloning-repository.tsx、missing-repository.tsx |
app/src/highlighter | app/src/highlighter | diff 语法高亮 Web Worker | index.ts |
app/src/crash | app/src/crash | 崩溃兜底 UI | crash-app.tsx、index.tsx |
app/src/cli | app/src/cli | github命令行接口 | main.ts、commands/ |
两点阅读提醒:
- 目录命名存在差异:文档中的
main/renderer对应现状的main-process/ui,规划是目标态,现状仍用旧名; - 部分模块仍在共享层:Git 工具与 store 当前位于
app/src/lib/git与app/src/lib/stores,文档规划它们迁入 renderer 内部,迁移尚未完成——这正是文档反复强调"incremental process"的原因。
理解了这张对照表,就掌握了 GitHub Desktop 代码组织的核心心智模型:共享层放纯数据与环境无关逻辑,bundle 层按进程/入口划分专属代码,renderer 内部再按组件、工具、模型、store、视图分层。无论是定位某个功能的实现、判断新代码应该放哪里,还是理解 Webpack 打包边界,都可以从这份规划与现状的对照中快速找到答案。
- 开发工具
- 桌面应用
【免费下载链接】desktop
Fork of GitHub Desktop to support various Linux distributions
相关推荐
GitHub Desktop 源码仓库结构演进:从 `app/src` 到未来目录规划与 Webpack Bundle 架构
GitHub Desktop 源码仓库结构演进:从 app/src 到未来目录规划与 Webpack Bundle 架构 本篇技术指南围绕 docs/techn
桌面应用版本控制开发工具Elementor Editor Styles Repository 源码解析:编辑器样式仓库架构与演进
Elementor Editor Styles Repository 源码解析:编辑器样式仓库架构与演进 导读 @elementor/editor styles
CMS前端后端低代码nhost 仓库中的 safeexec 模块:规避 Windows 下 exec.LookPath 当前目录查找漏洞的实现解析
nhost 仓库中的 safeexec 模块:规避 Windows 下 exec.LookPath 当前目录查找漏洞的实现解析 本篇技术指南围绕 nhost 仓
后端认证鉴权数据库无服务开发工具云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考