Files
XCOpenCodeWeb/docs/electron-packaging.md

3.7 KiB
Raw Blame History

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 关键代码

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 打包配置

{
  "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. 打包命令

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 端口