drawio-desktop 工程手册:从 Electron 主进程到安全打包的完整技术解析
【免费下载链接】drawio-desktopOfficial electron build of draw.io项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop
本文以 drawio-desktop 仓库的 CLAUDE.md 为核心,系统梳理这个基于 Electron 的 draw.io 桌面应用的工程结构、版本同步机制、手写 CLI 参数解析器、安全模型与 IPC 设计,以及面向 Windows/Linux/macOS 的完整打包、签名与自动更新流程。读完本文,你可以独立完成从递归克隆、npm run sync版本同步到各平台发行包构建的全链路操作,并能理解主进程中 CSP、contextBridge 与validateSender三道安全防线在源码中的真实落点。
一、项目定位:一个"安全优先"的 Electron 封装层
drawio-desktop 是 draw.io 的官方 Electron 桌面版本,其核心设计可以用一句话概括:用 Electron 把 draw.io 核心绘图编辑器(以 git 子模块形式包含在仓库中)包装成桌面应用,支持流程图、UML 等图形的离线创建,并采用安全优先的设计,将图纸数据与互联网隔离。
仓库的关键元信息(来自 CLAUDE.md 与 package.json):
| 项 | 值 |
|---|---|
| 许可证 | Apache 2.0 |
| 版本 | 由drawio/VERSION经npm run sync写入package.json(当前仓库为31.4.2) |
| Node 要求 | engines声明>=22.12.0,CI 构建使用 Node 24.x(见 doc/RELEASE_PROCESS.md 的工具链锁定表) |
| 框架 | Electron^44.1.1(devDependencies 中锁定) |
| 构建工具 | electron-builder^26.15.7 |
| 模块规范 | "type": "module",ES6 模块 |
| 贡献模式 | 闭源维护(Closed to contributions),不接受外部 PR;Apache 2.0 许可允许个人 fork 构建 |
.gitmodules中声明了核心编辑器子模块:路径drawio,指向 JGraph 的 drawio 仓库dev分支。这正是后文"必须递归克隆"约束的根源——如果子模块未初始化,drawio/VERSION不存在,版本同步脚本会直接报错退出。
二、快速参考:环境准备与日常命令
CLAUDE.md 给出的标准操作序列(结合 package.json 的scripts字段验证):
# 1. 克隆(必须 --recursive,否则 drawio 子模块为空) git clone --recursive <drawio-desktop 仓库地址> # 2. 安装依赖 npm install # 3. 启动应用(等价于 electron .) npm start # 4. 以 DevTools 打开 DRAWIO_ENV=dev npm start # 5. 构建前版本同步(必需) npm run sync # 6. 构建指定平台 npm run release-win # Windows x64(NSIS + MSI) npm run release-linux # Linux(AppImage、deb、rpm) npm run release-appx # Windows Store完整的平台构建命令对照表:
| 命令 | 目标 |
|---|---|
npm run release-win | Windows x64(NSIS + MSI) |
npm run release-win-arm64 | Windows ARM64 |
npm run release-linux | Linux(AppImage、deb、rpm) |
npm run release-appx | Windows Store |
npm run release-snap | Snap 包 |
这五条命令在 package.json 中分别对应electron-builder --config electron-builder-*.json --publish always|never,即每个平台使用独立的 electron-builder 配置文件,release-snap是唯一使用--publish never的脚本。
三、版本同步机制:sync.cjs 源码级解析
npm run sync是整个构建流程的第一道关卡。sync.cjs 全文仅约 30 行,但承担了两项关键职责:
- 版本号唯一事实来源(Source of Truth):读取
drawio/VERSION,先校验文件存在,再用正则/^\d+\.\d+\.\d+$/校验格式,最后把版本号写回package.json的version字段; - 生成自动更新开关:向
src/main/disableUpdate.js写入一个只有单个函数的模块。
逐段看它的防御逻辑(sync.cjs):
// 子模块未初始化时 fail fast,并提示 --recursive if (!fs.existsSync(versionPath)) { console.error('Error: drawio/VERSION not found. Did you clone with --recursive or run git submodule update --init?') process.exit(1) } // 语义化版本格式校验 if (!/^\d+\.\d+\.\d+$/.test(ver)) { console.error('Error: drawio/VERSION contains invalid version: "' + ver + '"') process.exit(1) } // 写入 package.json,并生成 disableUpdate.js fs.writeFileSync(appjsonpath, JSON.stringify(pj, null, 2), 'utf8') fs.writeFileSync(disableUpdatePath, 'export function disableUpdate() { return ' + (process.argv[2] == 'disableUpdate'? 'true' : 'false') + ';}', 'utf8');几个值得注意的细节:
src/main/disableUpdate.js是生成文件(当前仓库快照中的内容为export function disableUpdate() { return false;})。主进程 src/main/electron.js 中的判定链是:disUpPkg()(生成文件的返回值)|| process.env.DRAWIO_DISABLE_UPDATE === 'true'等条件共同决定disableUpdate常量。因此个人构建时执行npm run sync -- disableUpdate会生成返回true的文件,从编译产物层面关闭自动更新,防止更新机制用官方包替换你的自定义构建。- 构建流程四步走(CLAUDE.md "Build Process" 一节):①
npm run sync同步版本;②npm ci干净安装;③ electron-builder 按平台配置打包;④ 后处理——安全熔丝(fuses)、macOS Quick Look 扩展装配、签名与公证。 - CI 覆盖机制:正式发布流水线会检出私有的
jgraph/drawio-dev仓库的release分支,把其中构建好的*.min.js和VERSION复制进公开的drawio/子模块树,再照常执行npm run sync。这让 CI 可以基于领先于公开子模块标签的内部版本发布,而无需改动sync.cjs一行代码;没有drawio-dev访问权的外部分发者则回退到公开子模块的VERSION。
四、项目结构与核心文件职责
CLAUDE.md 的目录树与实际仓库一一对应(已核实 src/main、build、.github/workflows 目录):
drawio-desktop/ ├── src/main/ │ ├── electron.js # Electron 主进程(3700+ 行) │ ├── electron-preload.js # 基于 contextBridge 的 IPC 桥 │ ├── args.js # CLI 参数定义与解析器 │ ├── progress-bar.js # 长耗时操作进度条 │ └── disableUpdate.js # 由 sync 脚本生成 ├── src/test/ │ ├── cli-args.test.js # CLI 参数解析测试(npm test) │ ├── msi-project-created.test.js # MSI 快捷方式图标钩子测试 │ └── window-bounds.test.js # 窗口边界测试 ├── drawio/ # git 子模块 - draw.io 核心编辑器 │ └── src/main/webapp/ # 被 Electron 加载的 Web 应用 ├── build/ # 构建资源 │ ├── notarize.mjs # macOS 签名 + 公证、Quick Look 装配 │ ├── sign-trusted.mjs # Windows Azure Trusted Signing 钩子 │ ├── fuses.mjs # Electron 安全熔丝 │ ├── msi-project-created.mjs # msiProjectCreated 钩子:MSI 快捷方式用 exe 图标 │ ├── dmg-hidden-files.mjs # beforePack 钩子:DMG 隐藏支持文件 │ ├── quicklook-preview.html # Quick Look 预览页 │ ├── quicklook-entitlements.plist # .appex 沙箱权限 │ └── entitlements.mac.plist ├── doc/ │ ├── RELEASE_PROCESS.md # 发布流程文档 │ └── BUILDING_FOR_PERSONAL_USE.md # 个人/未签名 fork 构建指南 ├── electron-builder-*.json # 各平台构建配置 ├── sync.cjs # 版本同步脚本 └── package.json核心文件职责表:
| 文件 | 用途 |
|---|---|
| src/main/electron.js | 主进程:窗口管理、IPC 处理器、菜单、自动更新 |
| src/main/electron-preload.js | 渲染进程与主进程之间的安全 IPC 桥 |
| src/main/args.js | CLI 选项定义与参数解析器(用于 CLI 导出) |
| sync.cjs | 构建前脚本,从drawio/VERSION同步版本 |
| electron-builder-win.json 等 | 各平台构建配置 |
| build/sign-trusted.mjs | electron-builder 的 Windows 签名钩子(Azure Trusted Signing) |
五、CLI 参数体系:手写解析器而非 commander
CLAUDE.md 明确指出:CLI 参数解析在src/main/args.js中手写实现(未使用commander)。这份实现是 drawio 命令行导出能力(drawio -x -f pdf input.drawio)的核心,也是仓库单元测试覆盖的重点(src/test/cli-args.test.js)。
5.1 选项定义表(OPTION_DEFS)
args.js 用一张数据表OPTION_DEFS描述全部选项,每条目结构为{ short?, long, key, valueLabel?, takesValue?, parse?, default?, desc },其中parse(rawString)返回null/undefined表示"回退到默认值"。主要选项与默认值如下:
| 短选项 | 长选项 | 键 | 取值 | 说明 |
|---|---|---|---|---|
-c | --create | create | — | 未传文件时创建新空文件 |
-k | --check | check | — | 不覆盖已存在的输出文件 |
-x | --export | export | — | 按选项导出输入文件/文件夹,除 draw.io 外支持 vsdx、csv、Mermaid 输入 |
-r | --recursive | recursive | — | 文件夹输入时递归转换子文件夹 |
-o | --output | output | <file/folder> | 输出文件/文件夹;省略时用输入文件名 + 指定格式扩展名 |
-f | --format | format | <format> | 默认pdf;若输出文件名带扩展名则此选项被忽略;合法值pdf\|png\|jpg\|svg\|xml\|html |
| — | --layout | layout | <name\|json> | 打开后对图执行布局(verticalFlow、horizontalFlow、verticalTree、horizontalTree、radialTree、organic 或自定义 JSON 序列) |
| — | --mermaid-image | mermaidImage | <true/false> | Mermaid 文件以静态图片而非可编辑图形打开,默认false |
-q | --quality | quality | <quality> | JPEG 输出质量,默认 90 |
-t | --transparent | transparent | — | PNG/SVG 透明背景 |
-e | --embed-diagram | embedDiagram | — | 输出中嵌入图纸副本(仅 PNG、SVG、PDF) |
| — | --embed-svg-images | embedSvgImages | — | SVG 输出内嵌图片 |
| — | --embed-svg-fonts | embedSvgFonts | <true/false> | SVG 内嵌字体,默认true |
-b | --border | border | <border> | 图纸周围边框宽度,默认 0 |
-s | --scale | scale | <scale> | 缩放图纸尺寸 |
| — | --width/--height | width / height | <px> | 按宽/高适配输出,保持宽高比 |
| — | --crop | crop | — | PDF 裁剪到图纸尺寸 |
| — | --size | size | <diagram\|page> | 默认diagram;page导出完整页面 |
-a | --all-pages | allPages | — | 导出所有页(PDF/HTML) |
-p | --page-index | pageIndex | <n> | 1 起始页码,内部换算为 0 起始 |
-l | --layers | layers | <idx,...> | 选择导出哪些图层 |
-g | --page-range | pageRange | <from>..<to> | 页码范围(1 起始,仅 PDF),经argsRange解析为[from-1, to-1] |
-u | --uncompressed | uncompressed | — | XML/SVG 不压缩输出 |
-z | --zoom | zoom | <zoom> | 缩放应用界面 |
| — | --theme | theme | dark\|light\|auto | 默认auto;SVG 保持自适应查看器,其他格式渲染为浅色 |
| — | --svg-links-target | svgLinksTarget | auto\|new-win\|same-win | 默认auto |
| — | --html-theme/--html-zoom/--html-lightbox/--html-layers/--html-tags/--html-fit/--html-link-target/--html-link-color/--html-edit-link | … | 各 HTML 查看器选项 | 默认值见 args.js |
另有--disable-update与--no-silent-update两个helpOnly选项:它们在解析器运行前直接通过process.argv被主进程消费(对应DRAWIO_DISABLE_UPDATE环境变量与 electron.js 中的判定),表中仅用于帮助文本展示。
5.2 解析器的三个实现要点
阅读 parseDrawioArgs 可以归纳出该手写解析器的设计要点:
- 默认值先铺底:进入循环前先把所有带
default的键写入opts,parse失败(返回null)时再回落到默认值(见 applyValue),保证--format xyz这类非法值静默回退为pdf而非抛错; - 兼容 Electron 打包应用的 argv 怪癖:注释明确指出
argv可能带有 Electron 打包应用注入的null前缀(electron/electron#4690),用户 token 恒从索引 2 开始,tokens = argv.slice(2).filter(t => t != null); - 短选项组合与粘连值:为兼容 30.0.0 之前依赖 commander 行为的存量脚本(如 Makefile 里的
drawio -xa --crop ...),解析器实现了-abc → -a -b -c的组合展开,以及-fpng → -f png的粘连值形式;组合簇中一旦遇到未知字母则整 token 丢弃,保持旧行为。
支持--flag=value内联语法与--分隔符(其后全部视为位置参数)。帮助文本由 formatHelp 按最长 flag 宽度对齐生成,即drawio -h的输出。
六、安全模型:CSP、contextBridge 与 validateSender
CLAUDE.md "Security Model" 一节列出了五条安全约束,每一条都能在 src/main/electron.js 中找到对应实现:
| 声明 | 源码证据 |
|---|---|
| CSP 阻止远程脚本执行 | 主进程通过session.defaultSession.webRequest.onHeadersReceived向所有响应强制注入 CSP 头:default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; connect-src 'self'...(electron.js,并跳过 config-editor iframe 的特殊处理) |
| contextBridge 仅暴露特定 API | 主窗口与 BrowserView 的webPreferences均设contextIsolation: true(electron.js 等多处),渲染进程只能看到 preload 桥接出的白名单 API |
| validateSender() 校验 IPC 来源 | 工具函数定义于 electron.js,每个ipcMain处理器入口先if (!validateSender(e.senderFrame)) return null(全文出现 10+ 处,如 electron.js) |
| 图纸数据不出本地 | onBeforeRequest对file://*之外的请求做拦截处理(electron.js),配合 CSP 的connect-src 'self'从网络层切断数据外传 |
| 仅内置插件 | 2026 年 7 月移除外挂/第三方插件;isPluginsEnabledIPC action 被保留但硬编码返回false,使旧版捆绑 webapp 优雅降级为"插件已禁用"对话框而非直接失败 |
IPC 请求/响应模式
preload 脚本采用带唯一 ID 的请求/响应模式(src/main/electron-preload.js),CLAUDE.md 给出的核心范式:
// 渲染进程发起请求 electron.request({action: 'save', data: ...}, callbackId); // 主进程处理并通过 IPC 回包 ipcMain.on('request', (e, data) => { ... });主进程侧对每个异步 IPC 还会校验uniqueId与发送方(如 electron.js 的!validateSender(e.senderFrame) || uniqueIsModifiedId != data.uniqueId),防止伪造或过期的回包被误接受。这套模式的实际价值在于:渲染进程侧不持有ipcRenderer的任意通信能力,只能按 action 白名单调用,且每个回包都能与请求一一配对。
七、构建流程与平台打包
7.1 electron-builder 配置
每个平台对应一个 JSON 配置。以 electron-builder-win.json 为例,可提取的关键字段:
appId: com.jgraph.drawio.desktop,输出目录./dist/,asar: true,npmRebuild: false;- 打包文件排除
**/WEB-INF{,/**}; win.signtoolOptions:sign钩子指向build/sign-trusted.mjs,signingHashAlgorithms: ["sha256"],signExts: [".dll"];- 目标为 NSIS(x64)与 MSI(x64),NSIS 配置
perMachine: true、oneClick: false; afterPack: build/fuses.mjs(安全熔丝)、msiProjectCreated: build/msi-project-created.mjs(MSI 快捷方式图标修正钩子,对应测试 src/test/msi-project-created.test.js);- 文件关联:
.drawio(application/vnd.jgraph.mxfile)、.vsdx、.mmd/.mermaid,说明桌面版可直接打开这三类图纸文件。
7.2 代码签名:Windows 与 macOS 两条路线
Windows —— Azure Trusted Signing(CLAUDE.md "Code Signing" 一节,已对照 build/sign-trusted.mjs 与 electron-builder-win.json 确认):
- 不走
CSC_LINK证书,而是 electron-builder 的signtoolOptions.sign钩子 build/sign-trusted.mjs; - CI 工作流
electron-builder-win.yml负责下载签名 dlib、定位signtool.exe,并用AZURE_TENANT_ID/AZURE_CLIENT_ID/AZURE_CLIENT_SECRET三个 secret 完成认证; win.signExts: [".dll"]会把捆绑的 Electron DLL 一并签名——文档特别指出:未签名的ffmpeg.dll等会在每次发版后触发 Defender 的 ASR 勒索软件规则(上游 issue #2509);- 可移植 zip 步骤必须传
--config electron-builder-win.json:裸跑--dir不加载任何配置,会产出未签名、未烧熔丝的二进制。
macOS:Apple 开发者证书 + 公证,逻辑集中在 build/notarize.mjs,Quick Look 扩展装配也在其中(见下节)。
未签名个人构建:DRAWIO_UNSIGNED=true可跳过 Windows 签名与 macOS 公证。doc/BUILDING_FOR_PERSONAL_USE.md 记录了一套完整流程:设置DRAWIO_UNSIGNED=true、直接运行electron-builder --publish never、并执行npm run sync -- disableUpdate关闭自动更新(原因见第三节:生成文件会从产物层面禁用更新)。此外 .github/workflows/personal-build.yml 是一个workflow_dispatch手动工作流:在 fork 上无需任何 secret地构建未签名安装包,并把产物作为 run artifacts 附出。
八、macOS Quick Look 预览:.appex 的装配时序
这是 CLAUDE.md 中最具工程细节的架构说明之一,在 Finder 中按空格即可预览.drawio文件:
- 使用
quicklookjs(devDependency)把 Quick Look App Extension(.appex)嵌入 app bundle; .appex在 WKWebView 中加载viewer-static.min.js(内嵌图形资源);- 构建时序:
afterPack(build/fuses.mjs)先烧安全熔丝,afterSign(build/notarize.mjs)再装配.appex、以沙箱权限(build/quicklook-entitlements.plist)对其签名、重新签名外层.app并公证。.appex之所以放在afterSign而非afterPack插入,是为了确保它在 electron-builder 的签名校验期间从未以未签名状态存在; - 权限差异:Quick Look 扩展要求
app-sandbox,但 Electron 自身的 helper 进程不能沙箱化——因此.appex与主应用的entitlementsInherit(build/entitlements.mac.plist)使用不同的 entitlements; - UTI
com.jgraph.drawio通过 electron-builder-linux-mac.json 的extendInfo声明; - 资源来源:
viewer-static.min.js在 CI 中先被保存到build/目录,以便后续清理步骤删除 drawio 子模块中的该文件;本地开发则直接从子模块读取。
九、自动更新与数据存储
自动更新
- 启动时检查 GitHub releases(基于
electron-updater,devDependency 中的sumchecker用于校验); - 关闭方式:
DRAWIO_DISABLE_UPDATE=true或--disable-update标志;--no-silent-update则改为下载前提示而非静默更新(electron.js 的silentUpdate判定与此对应); - Flatpak 环境下自动禁用更新;
- 构建期关闭:
npm run sync -- disableUpdate生成恒为true的 disableUpdate.js,这是个人构建不被官方包覆盖的关键。
数据存储
- macOS:
~/Library/Application Support/draw.io - Windows:
%APPDATA%\draw.io\ - 持久化设置使用
electron-store,日志使用electron-log(均见 package.json 依赖)。
十、测试策略与 CI/CD 工作流
单元测试
CLAUDE.md 说明:npm test运行 src/test/ 下的单元测试(Node.js 内置 test runner),覆盖 CLI 参数解析与 MSI 快捷方式图标钩子;其余为手工测试。package.json 的实际 test 脚本还包含第三个文件:
node --test src/test/cli-args.test.js src/test/msi-project-created.test.js src/test/window-bounds.test.js其余质量保障依赖手工回归,清单记录在 doc/RELEASE_PROCESS.md:启动 → 创建图纸 → 添加图形 → 保存 → 打开;导出(PNG/PDF/SVG);撤销/重做;关于对话框校验。
CI/CD 工作流对照表
以下表来自 CLAUDE.md,工作流文件均在 .github/workflows/ 下核实存在:
| 工作流 | 触发条件 | 用途 |
|---|---|---|
electron-builder.yml | 版本 tag | macOS/Linux 构建 |
electron-builder-win.yml | 版本 tag | Windows 构建(Azure Trusted Signing) |
prepare-release.yml | 手动 | 自动化发布准备(更新子模块、改版本号、npm audit、打 tag) |
hash-gen.yml | Release 发布 / 每日 cron / 手动 | 为缺少校验和的 release 补传 hash(GitHub 自 2024 年 12 月起会丢失 release 事件,故需 cron 兜底) |
personal-build.yml | 手动 | 未签名 fork 构建,仅 artifacts(无 secret、不发布) |
stale.yml | 定时 | 标记陈旧 issue/PR |
十一、工程约定与硬性约束
代码风格
CLAUDE.md 定义的风格约定(可在 src/main/args.js 中逐条验证):ES6 模块import/export;Tab 缩进;Allman 大括号风格(左括号另起一行);变量camelCase、类PascalCase;不使用 ESLint/Prettier,靠人工保持一致;注释从简、以代码自明为优先。
Git 约定
- 分支:
dev(主开发分支、PR 目标)、release(生产发布)、releases/v*.*.*(版本发布分支); - 提交信息:小写句式、句末不加句号;issue 引用格式
[jgraph/drawio-desktop#XXXX];示例:Fixes paste error、Adds buffer as dependency [jgraph/drawio-desktop#2301]、Prepare release v29.3.0; - 版本 tag:
v{MAJOR}.{MINOR}.{PATCH}(如v29.3.0),tag 触发 CI/CD 构建工作流。
硬性约束(务必遵守)
- 必须递归克隆—— drawio 子模块必须初始化(否则
npm run sync报错退出); - 构建前执行
npm run sync—— 从子模块同步版本到package.json; - 版本事实来源:公开构建以
drawio/VERSION为准;CI 时由drawio-dev/VERSION覆盖,内部发布号在打包构建中胜出; - 不接受外部贡献—— PR 不合并,JGraph 团队维护;个人用途 fork 可行(参见 doc/BUILDING_FOR_PERSONAL_USE.md);
- Node 22.12+——
package.json的engines字段约束; - 平台下限:仅 Windows x64/arm64 与 macOS 13+ —— Electron 44 移除了 ia32 二进制与 macOS 12 支持,32 位 Windows 构建已于 2026 年 9 月移除。
开发小贴士
DRAWIO_ENV=dev自动打开 DevTools;npm start --enable-logging获取冗长输出;- 若以 symlink 替代子模块,需同时 symlink
node_modules; - 主进程日志直接输出到控制台,错误先看终端。
关键依赖速查
| 包 | 用途 |
|---|---|
electron | 桌面应用框架(^44.1.1) |
electron-builder | 构建/打包工具(^26.15.7) |
electron-updater | 自动更新机制 |
electron-store | 持久化设置存储 |
electron-log | 日志 |
@cantoo/pdf-lib | PDF 导出 |
quicklookjs | macOS Quick Look 预览扩展(devDependency) |
@electron/fuses | 安全熔丝烧写(devDependency) |
十二、小结
drawio-desktop 的工程价值在于它把"打包一个网页应用"这件事做成了有完整安全边界的系统工程:sync.cjs保证版本单一事实来源,手写 CLI 解析器在兼容历史脚本的前提下零依赖地支撑命令行导出,CSP + contextBridge + validateSender 三层防线把渲染进程关在"本地文件"的安全笼中,而 electron-builder 的多钩子流水线(afterPack烧熔丝、afterSign装配 Quick Look、signtoolOptions.sign接 Azure 签名)则解释了每个build/*.mjs文件为什么必须存在。若你需要在本仓库上开展二次开发或维护个人 fork,建议从 doc/BUILDING_FOR_PERSONAL_USE.md 与 doc/RELEASE_PROCESS.md 两篇姊妹文档继续深入。
【免费下载链接】drawio-desktopOfficial electron build of draw.io项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考