第一个桌面应用
将项目打包为自己的桌面应用
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux
参考官网:https://www.electronjs.org/zh/docs/latest/
将 Electron 安装为您项目的 devDependencies,即仅在开发环境需要的额外依赖
在 package.json 中指定的 main 文件是 Electron 应用的入口。 这个文件控制 主程序 (main process),它运行在 Node.js 环境里,负责控制您应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes)
通常我们使用触发器的 .on 函数来监听 Node.js 事件
应用中的每个页面都在一个单独的进程中运行,我们称这些进程为 渲染器 (renderer) 。 渲染进程使用与常规Web开发相同的JavaScript API和工具,例如使用 webpack来打包和压缩您的代码,或使用 React 构建用户界面。
通过检查 Node.js 的 process.platform 变量,您可以针对特定平台运行特定代码。 请注意,Electron 目前只支持三个平台:win32 (Windows), linux (Linux) 和 darwin (macOS)
什么是预加载脚本?
Electron 的主进程是一个拥有着完全操作系统访问权限的 Node.js 环境,另一方面,出于安全原因,渲染进程默认跑在网页页面上,而并非 Node.js里。
无论是从渲染进程直接访问 Node.js 接口,亦或者是从主进程访问 HTML 文档对象模型 (DOM),都是不可能的
使用进程间通信 (IPC)。可以使用 Electron 的 ipcMain 模块和 ipcRenderer 模块来进行进程间通信。 为了从你的网页向主进程发送消息,你可以使用 ipcMain.handle 设置一个主进程处理程序(handler),然后在预处理脚本中暴露一个被称为 ipcRenderer.invoke 的函数来触发该处理程序(handler)。
实践
主进程:
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
预加载
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
ping: () => ipcRenderer.invoke('ping')
})
HTML页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
渲染器
render.js
const information = document.getElementById('info')
information.innerText = `This app is using Chrome (v${window.versions.chrome()}), Node.js (v${window.versions.node()}), and Electron (v${window.versions.electron()})`
const func = async () => {
const response = await window.versions.ping()
console.log(response) // 打印 'pong'
}
func()
打包应用程序
Electron 的核心模块中没有捆绑任何用于打包或分发文件的工具。 如果您在开发模式下完成了一个 Electron 应用,需要使用额外的工具来打包应用程序 (也称为可分发文件) 并分发给用户 。 可分发文件可以是安装程序 (例如 Windows 上的 MSI) 或者绿色软件 (例如 macOS 上的 .app 文件)
electron-builder:一个完整的解决方案,用于打包和构建适用于 macOS、Windows 和 Linux 的 Electron、Proton Native 应用程序,并支持开箱即用的“自动更新”https://www.electron.build/
所有平台: 7z , zip , tar.xz , tar.7z , tar.lz , tar.gz , tar.bz2 , dir (解压目录)。
macOS: dmg , pkg , mas 。
Linux: AppImage, snap, Debian 软件包( deb ), rpm , freebsd , pacman , p5p , apk 。
Windows: nsis (安装程序), nsis-web (网络安装程序), portable (无需安装的便携式应用程序),AppX(Windows 商店),MSI,Squirrel.Windows。
记录
使用 electron.resourcesPath 获取真实路径
在主进程中通过 app.getAppPath() 或 process.resourcesPath 获取资源路径:
const { app, process } = require('electron');
const path = require('path');
const pythonScriptPath = path.join(process.resourcesPath, 'python_scripts/your_script.py');
排除 asar 打包
在 electron-builder 配置中将 Python 脚本标记为非 asar 资源:
// package.json
"build": {
"asar": true,
"files": [
"!python_scripts/**/*" // 排除 Python 脚本
],
"extraResources": [
"python_scripts/**/*" // 复制到资源目录
]
}
二、环境变量缺失
现象分析:
打包后的应用可能丢失系统环境变量(如 PATH 中的 Python/Appium 路径)。
解决方案:
显式指定二进制路径
在调用 Python 或 Appium 时使用绝对路径:
const { spawn } = require('child_process');
const pythonProcess = spawn('/usr/local/bin/python', [pythonScriptPath]);
动态注入环境变量
在主进程中设置环境变量:
const env = {
...process.env,
PATH: `${process.env.PATH}:/usr/local/bin`
};
const pythonProcess = spawn('python', [scriptPath], { env });
三、子进程生命周期管理
现象分析:
未正确处理子进程的 I/O 或错误事件可能导致进程静默退出。
三、子进程生命周期管理
现象分析:
未正确处理子进程的 I/O 或错误事件可能导致进程静默退出。
解决方案:
强制处理 stdout/stderr 并监听错误事件:
const pythonProcess = spawn('python', [scriptPath]);
pythonProcess.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
pythonProcess.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
pythonProcess.on('error', (err) => {
console.error('Failed to start subprocess:', err);
});
pythonProcess.on('close', (code) => {
console.log(`子进程退出,代码:${code}`);
});
四、权限问题
现象分析:
打包后的应用可能因权限不足无法执行脚本或启动服务。
解决方案:
赋予脚本可执行权限
在打包后通过 chmod 修改权限:
javascript
const { chmod } = require('fs');
chmod(pythonScriptPath, 0o755, (err) => { /* ... */ });
以管理员权限运行应用
在 electron-builder 中配置请求管理员权限(仅限必要场景):
json
"build": {
"win": {
"requestedExecutionLevel": "requireAdministrator"
}