开发工具桌面应用【免费下载链接】desktopFork of GitHub Desktop to support various Linux distributions项目地址https://gitcode.com/gh_mirrors/des/desktop点击查看免费下载GitHub Desktop 是一个基于 Electron 的跨平台 Git 客户端代码需要同时面向 macOS、Windows 与 Linux 构建。为了让同一份 TypeScript 源码在不同平台上产出体积更小、执行更快的产物项目在构建阶段使用 Webpack 的DefinePlugin将一批占位符placeholders直接替换为编译期的常量值再借助 minification 消除死代码。本文基于仓库文档 docs/technical/placeholders.md结合 app/app-info.ts、app/webpack.common.ts 与 app/src/lib/globals.d.ts 等源码完整剖析这套替换机制的配置项、类型声明、命名约定及其在真实代码中的落地效果。为什么需要编译期替换运行时判断的代价GitHub Desktop 使用 Webpack 将 TypeScript 源码转译、压缩并合并为每个构建配置对应的统一脚本。面对跨平台逻辑最常见的写法是在运行时判断process.platformif (process.platform darwin) { windowOptions.titleBarStyle hidden } else if (process.platform win32) { windowOptions.frame false }这段代码语义正确但存在两个问题打包体积浪费每个平台的产物都会携带完整的条件分支代码运行时开销每次执行到该分支都要在运行时读取并比较平台字符串。仓库文档明确指出虽然两种写法语义等价但按照 GitHub Desktop 的打包方式使用编译期替换的写法能获得显著收益。这正是文档开篇示例的初衷——你会在 app-window.ts 中看到这样的代码if (__DARWIN__) { windowOptions.titleBarStyle hidden } else if (__WIN32__) { windowOptions.frame false }__DARWIN__、__WIN32__并不是运行时变量而是构建时就被求值并替换的占位符。Replacements替换表定义在哪里所有替换项集中定义在 app/app-info.ts 的getReplacements()函数中以一个键值对哈希表返回。以下是仓库中的完整实现const s JSON.stringify export function getReplacements() { const isDevBuild channel development return { __OAUTH_CLIENT_ID__: s(process.env.DESKTOP_OAUTH_CLIENT_ID || devClientId), __OAUTH_SECRET__: s( process.env.DESKTOP_OAUTH_CLIENT_SECRET || devClientSecret ), __DARWIN__: process.platform darwin, __WIN32__: process.platform win32, __LINUX__: process.platform linux, __APP_NAME__: s(productName), __APP_VERSION__: s(version), __DEV__: isDevBuild, __RELEASE_CHANNEL__: s(channel), __UPDATES_URL__: s(getUpdatesURL()), __SHA__: s(getSHA()), __CLI_COMMANDS__: s(getCLICommands()), process.platform: s(process.platform), process.env.NODE_ENV: s(process.env.NODE_ENV || development), process.env.TEST_ENV: s(process.env.TEST_ENV), } }每个替换项的含义与取值来源如下占位符类型取值来源用途__OAUTH_CLIENT_ID__string环境变量DESKTOP_OAUTH_CLIENT_ID缺省用开发用 devClientId注入 GitHub OAuth 客户端 ID__OAUTH_SECRET__string环境变量DESKTOP_OAUTH_CLIENT_SECRET缺省用 devClientSecret注入 GitHub OAuth 客户端密钥__DARWIN__boolean构建时process.platform darwinmacOS 平台分支__WIN32__boolean构建时process.platform win32Windows 平台分支__LINUX__boolean构建时process.platform linuxLinux 平台分支__APP_NAME__stringpackage.json中的productName替代运行时的app.getName()__APP_VERSION__stringpackage.json中的version替代运行时的app.getVersion()__DEV__boolean发布通道是否为development区分开发/生产构建__RELEASE_CHANNEL__stringgetChannel()当前发布通道__UPDATES_URL__stringdistInfo.getUpdatesURL()Squirrel 更新服务的 URL__SHA__stringgitInfo.getSHA()构建时仓库 HEAD 的 40 位 SHA-1__CLI_COMMANDS__string[]扫描app/src/cli/commands目录下的.ts文件动态生成 CLI 子命令清单process.platformstring构建时的平台字符串全局替换所有运行时平台判断process.env.NODE_ENVstring环境变量缺省development全局替换 Node 环境判断process.env.TEST_ENVstring环境变量测试环境判断需要留意的是文档中给出的替换表与当前仓库源码略有出入仓库中额外包含了__APP_NAME__、__APP_VERSION__与__PROCESS_KIND__后者在 webpack 配置中按进程分别注入。这正是以当前仓库实际内容为准的体现。s JSON.stringify的作用注意getReplacements()中对字符串值统一调用const s JSON.stringify。这是因为DefinePlugin的替换是字面量级的——占位符出现处会被替换为该值在代码中的字面表示。对字符串调用JSON.stringify可以保证替换后的值带有正确的引号与转义例如s(darwin)得到darwin从而使替换结果成为合法的字符串字面量。编译期替换的执行效果由于__DARWIN__、__WIN32__在构建时即被求值为布尔常量开头的app-window.ts示例在 macOS 构建中会变成if (true) { windowOptions.titleBarStyle hidden } else if (false) { windowOptions.frame false }而在 Windows 构建中则变成if (false) { windowOptions.titleBarStyle hidden } else if (true) { windowOptions.frame false }这些if (true)/if (false)属于 Webpack 压缩阶段可识别的死代码路径最终被消除。于是 macOS 产物只留下windowOptions.titleBarStyle hiddenWindows 产物只留下windowOptions.frame false由此带来两方面的收益更少的产物代码打包进应用的 JavaScript 不包含其他平台的无关分支更少的运行时解释执行JavaScript 引擎无需解释和运行被剔除的代码路径。Webpack 侧的注入DefinePlugin 与五种构建配置替换表本身只是普通对象真正把它注入到打包产物中的是 Webpack 的DefinePlugin全部定义在 app/webpack.common.ts 中。文件顶部先求值替换表import { getReplacements } from ./app-info export const replacements getReplacements()随后为每种构建目标配置一个DefinePlugin并且每个目标额外注入一个标识当前进程类型的__PROCESS_KIND__new webpack.DefinePlugin( Object.assign({}, replacements, { __PROCESS_KIND__: JSON.stringify(main), }) )从 webpack.common.ts 的源码结构可以看到GitHub Desktop 共有五种构建配置分别对应五个进程/入口配置导出名入口target__PROCESS_KIND__mainsrc/main-process/mainelectron-mainmainrenderersrc/ui/indexelectron-rendereruicrashsrc/crash/indexelectron-renderercrashclisrc/cli/mainnodeclihighlightersrc/highlighter/indexwebworkerhighlighter也就是说同一份替换表会面向全部五种目标分别注入而__PROCESS_KIND__则随目标不同而变化。渲染进程日志模块正是用它来标识消息来源进程例如 app/src/lib/logging/renderer/install.ts 中的ipcLog(level, formatLogMessage([${__PROCESS_KIND__}] ${message}, error))从源码结构可以推断这套多进程标识机制用于区分主进程、UI 渲染进程、崩溃窗口进程、CLI 进程与语法高亮 Worker 的日志来源。PlaceholdersTypeScript 侧的类型声明在 TypeScript 源码中使用这些全局变量之前必须先为它们提供类型信息否则编译器会报找不到名称的错误。全部声明集中在 app/src/lib/globals.d.ts 中例如/** Is the app being built to run on Darwin? */ declare const __DARWIN__: boolean /** Is the app being built to run on Win32? */ declare const __WIN32__: boolean该文件完整声明了文档提到的占位符以及仓库中实际存在的全部占位符类型__DEV__: boolean——应用是否处于开发模式__OAUTH_CLIENT_ID__: string | undefined——OAuth 客户端 ID注意这里允许undefined对应 app-info 中环境变量缺省时的兜底逻辑__OAUTH_SECRET__: string | undefined——OAuth 客户端密钥__DARWIN__ / __WIN32__ / __LINUX__: boolean——三个平台布尔量__APP_NAME__: string——产品名文档注释说明它是对app.getName的编译期替代__APP_VERSION__: string——应用版本是对app.getVersion的编译期替代__SHA__: string——构建时仓库 HEAD 的 40 位 SHA-1 十六进制摘要__RELEASE_CHANNEL__: production \| beta \| test \| development——发布通道联合类型__CLI_COMMANDS__: ReadonlyArraystring——CLI 子命令名的只读数组__UPDATES_URL__: string——Squirrel 更新服务 URL__PROCESS_KIND__: main \| ui \| crash \| highlighter——当前执行进程类型且是 GitHub Desktop 特有的概念。值得注意的一个细节是原文档中引用的路径写为app/src/lib/globals.ts而仓库中实际文件名为app/src/lib/globals.d.ts声明文件阅读源码时需以实际路径为准。命名约定双下划线规则文档明确给出了一条约定凡是要被 Webpack 替换的全局占位符应当以双下划线作为前缀和后缀例如__DEV__。这条约定带来的好处是一眼即可区分编译期常量与普通全局变量/运行时 API避免与process、window等真实运行时全局对象混淆降低误将占位符当作可赋值变量的风险。对照 globals.d.ts 中的声明仓库中所有 Webpack 替换项都严格遵守这一命名规范。占位符在真实代码中的落地场景除了文档中的app-window.ts示例这套机制在仓库中遍布各个模块以下是几个有代表性的佐证主进程协议与平台分支app/src/main-process/main.ts开发构建额外注册x-github-desktop-dev-auth协议macOS 构建支持 Desktop Classic 的github-mac协议Windows 构建支持github-windows协议而 Linux 与 Windows 构建各自处理协议启动参数const possibleProtocols new Set([x-github-client]) if (__DEV__) { possibleProtocols.add(x-github-desktop-dev-auth) } else { possibleProtocols.add(x-github-desktop-auth) } if (__DARWIN__) { possibleProtocols.add(github-mac) } else if (__WIN32__) { possibleProtocols.add(github-windows) }菜单文案与快捷键的平台差异app/src/main-process/menu/build-default-menu.ts大量菜单项用__DARWIN__三元表达式区分 macOS 与 Windows/Linux 的文案与助记符例如退出快捷键__WIN32__ ? AltF4 : CmdOrCtrlQ以及仅在开发通道显示开发者菜单项visible: __RELEASE_CHANNEL__ development。OAuth 客户端 ID 注入app/src/lib/api.tsAPI 客户端直接使用编译期注入的 ID测试环境则置空const ClientID process.env.TEST_ENV ? : __OAUTH_CLIENT_ID__更新 URL 注入app/src/ui/lib/update-store.tsSquirrel 更新检查直接读取编译期注入的__UPDATES_URL__。用户代理与版本指纹app/src/lib/trampoline/trampoline-environment.ts将__APP_VERSION__、__SHA__与__DEV__组合成构建版本串开发构建会在版本号后附加 SHA 前 10 位作为后缀const suffix __DEV__ ? -${__SHA__.substring(0, 10)} : const ghdVersion GitHub Desktop/${__APP_VERSION__}${suffix}CLI 子命令动态注册app/src/cli/load-commands.ts__CLI_COMMANDS__在构建时通过读取 app/src/cli/commands 目录下的.ts文件生成见 app/app-info.ts 中的getCLICommands()运行时据此循环加载各子命令模块for (const fileName of __CLI_COMMANDS__) { // 动态加载并注册命令 }由此可见这套占位符机制不仅服务于平台分支还承担了密钥注入、版本指纹、更新地址、命令清单等大量构建期配置的职责。何时应该使用编译期占位符结合文档与源码可以总结出适用这套机制的判断标准值在构建时即可确定如平台、发布通道、构建 SHA、版本号、OAuth 凭据希望消除死代码平台专属的窗口选项、协议注册、菜单文案等分支替换后能被压缩器安全剔除需要防止敏感信息进入源码OAuth 客户端 ID/密钥通过环境变量注入避免硬编码在共享源码中仓库中的devClientId/devClientSecret是开发用兜底值需要稳定的类型保障配合globals.d.ts声明占位符在编辑器中拥有完整的类型提示与编译检查。反之如果某个值在运行时才会确定例如用户配置、网络返回结果就不属于占位符机制的适用范围应当继续使用运行时 API。总结GitHub Desktop 的占位符替换机制本质上是构建期求值 死代码消除的组合拳app/app-info.ts集中定义替换表app/webpack.common.ts通过DefinePlugin将其注入五种构建目标app/src/lib/globals.d.ts为占位符提供类型声明双下划线命名约定保证可读性。这套机制让同一份源码在不同平台上产出差异化的最小化产物同时把密钥、版本、更新地址等构建期信息安全地嵌入应用中是 Electron 应用工程化中值得借鉴的构建期优化实践。赞分享开发工具桌面应用【免费下载链接】desktopFork of GitHub Desktop to support various Linux distributions项目地址https://gitcode.com/gh_mirrors/des/desktop点击查看免费下载相关推荐GitHub Desktop 源码中的构建期占位符Placeholders与替换机制深度解析GitHub Desktop 源码中的构建期占位符Placeholders与替换机制深度解析 导读 本文深入剖析 GitHub Desktop 项目中一套精桌面应用版本控制开发工具WePY 小程序 DefinePlugin 实战用 wepy/plugin-define 实现编译期常量注入与条件编译WePY 小程序 DefinePlugin 实战用 wepy/plugin define 实现编译期常量注入与条件编译 本文基于 WePY 仓库 packa前端小程序开发工具pg-sql2 的 sql.placeholder()在编译期注入 SQL 片段的占位符机制pg sql2 的 sql.placeholder 在编译期注入 SQL 片段的占位符机制 sql.placeholder 是 pg sql2PostGra后端API网关上一篇3步免费实现Windows电脑变身AirPlay接收器airplay2-win完整指南下一篇三步免费实现Windows电脑变身AirPlay接收器airplay2-win完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
