diff --git a/docs/electron-packaging.md b/docs/electron-packaging.md new file mode 100644 index 0000000..c9db68b --- /dev/null +++ b/docs/electron-packaging.md @@ -0,0 +1,142 @@ +# Electron 单文件打包指南 + +## 概述 + +将 OpenChamber 打包成单个可执行文件 (exe),用户双击即可运行服务器。 + +## 核心挑战 + +Electron 打包后面临的最大问题:**如何启动服务器?** + +- 开发环境:直接用 `spawn('node', ...)` 即可,因为系统有 node +- 打包后:electron 内置的是 chromium + node 融合体,没有独立的 `node` 命令 +- 解决:把 node.exe 打包进 resources,用批处理脚本启动 + +## 实现步骤 + +### 1. 目录结构 + +``` +web/electron/ +├── main.js # electron 入口 +├── package.json # 打包配置 +└── release/ # 输出目录 + └── OpenChamber.exe +``` + +### 2. main.js 关键代码 + +```javascript +const { app, Tray, Menu, nativeImage } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const { spawn } = require('child_process'); + +function startServer() { + // 打包后路径 + const serverPath = path.join(process.resourcesPath, 'server', 'index.js'); + const cwdPath = path.join(process.resourcesPath); + const execPath = path.join(process.resourcesPath, 'nodejs', 'node.exe'); + + // 创建批处理脚本 + const batContent = `"${execPath}" "${serverPath}"`; + const batPath = path.join(cwdPath, 'start-server.bat'); + fs.writeFileSync(batPath, batContent); + + // 启动服务器 + spawn('cmd.exe', ['/c', batPath], { + cwd: cwdPath, + stdio: 'inherit', + env: { ...process.env, OPENCHAMBER_PORT: '3000', OPENCODE_SKIP_START: 'true' } + }); +} + +function createTray() { + // 系统托盘图标 + const iconPath = path.join(process.resourcesPath, 'public', 'favicon.png'); + const tray = new Tray(nativeImage.createFromPath(iconPath)); + tray.setContextMenu(Menu.buildFromTemplate([ + { label: 'Open http://localhost:3000', click: () => { + require('electron').shell.openExternal('http://localhost:3000'); + }}, + { label: 'Quit', click: () => app.quit() } + ])); +} + +app.whenReady().then(() => { + startServer(); + createTray(); +}); +``` + +### 3. package.json 打包配置 + +```json +{ + "name": "openchamber", + "main": "main.js", + "build": { + "appId": "com.openchamber.app", + "productName": "OpenChamber", + "directories": { + "output": "release" + }, + "files": [ + "main.js", + "package.json" + ], + "extraResources": [ + { "from": "../dist", "to": "dist" }, + { "from": "../server", "to": "server" }, + { "from": "../bin", "to": "bin" }, + { "from": "../public", "to": "public" }, + { "from": "D:\\Program Files\\nodejs", "to": "nodejs", "filter": ["node.exe"] } + ], + "win": { "target": "portable" }, + "portable": { "artifactName": "OpenChamber.exe" }, + "asar": true + } +} +``` + +关键点: +- `extraResources` 把 dist、server、bin、public、node.exe 打包进去 +- `win.target: portable` 生成单个 exe + +### 4. 打包命令 + +```bash +cd web/electron +npx electron-builder --win --x64 +``` + +输出:`web/electron/release/OpenChamber.exe` (~260MB) + +## 原理图 + +``` +OpenChamber.exe (便携版) +├── electron.exe (运行时) +├── resources/ +│ ├── app.asar (electron 主代码) +│ ├── dist/ (静态页面) +│ ├── server/ (服务器代码) +│ ├── bin/ (CLI) +│ ├── public/ (静态资源) +│ └── nodejs/ +│ └── node.exe (Node 运行时) +``` + +用户双击 exe: +1. electron 启动 +2. main.js 执行 +3. 创建批处理脚本调用 node.exe 运行 server/index.js +4. 服务器在 3000 端口启动 +5. 系统托盘显示图标 + +## 注意事项 + +1. **路径问题**:打包后用 `process.resourcesPath` 获取资源目录 +2. **node 路径**:必须把系统 node.exe 复制到打包目录 +3. **托盘图标**:需要 favicon.png,否则用空图标 +4. **端口占用**:默认 3000 端口