143 lines
3.7 KiB
Markdown
143 lines
3.7 KiB
Markdown
|
|
# 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 端口
|