Electron 是一个开源框架,允许开发者使用 Web 技术(如 JavaScript、HTML 和 CSS)来构建跨平台的桌面应用程序。它由 GitHub 创建并维护,最初是为了构建 Atom 编辑器而设计的。Electron 使得 Web 开发者可以轻松地进入桌面应用开发领域,同时也为那些寻求构建高质量跨平台桌面应用的团队提供了强大的工具。尽管 Electron 对 Web 开发者友好,但要想有效地利用它来开发桌面应用程序,开发者仍需深入理解其核心概念和组成部分。接下来将通过一个实践案例——开发一个 ChatGPT 桌面应用,一步一步掌握 Electron 的开发流程。
创建项目
使用下列命令创建项目,选择 Vue 模板:
npm create electron-vite
目录分析
目录结构大致如下:
chat-electron-app
├── electron
│ ├── electron-env.d.ts
│ ├── main.ts
│ └── preload.ts
├── electron-builder.json5
├── package.json
├── public
│ ├── electron-vite.animate.svg
│ ├── electron-vite.svg
│ └── vite.svg
├── src
│ └── main.ts
└── vite.config.ts
- ./electron/main.ts - 程序入口。这个文件是 Electron 应用的主进程,负责创建和管理应用程序窗口、处理系统事件。主进程运行在 Node.js 环境中,可以访问操作系统的底层功能。
- ./electron/preload.ts - 预加载脚本。预加载脚本在渲染进程(即网页)加载之前运行。它们可以用于安全地向渲染进程注入 Node.js 功能。
- src - Vue 项目内容。包含 Electron 应用的前端 Vue 代码。这通常包括组件、视图、样式、脚本等。
每个 Electron 应用程序都有一个主进程,作为应用程序的入口点。每次打开BrowserWindow
(以及每个网络嵌入)都会生成一个单独的渲染器进程,负责渲染网页内容。该多进程模型如图所示:
在 Electron 中,进程间通信(IPC)是构建功能丰富的桌面应用程序的关键组成部分。由于主进程和渲染器进程在 Electron 的进程模型中具有不同的职责,因此 IPC 是执行诸如从 UI 调用原生 API 或从原生菜单触发 Web 内容更改等常见任务的唯一方法。默认情况下,渲染器进程没有权限访问 Node.js 和 Electron 模块,需要以预加载脚本作为桥梁,将必要的 API 暴露出来,以便主进程与渲染器进程之间进行有效的通信。
基础代码
程序入口 ./electron/main.ts
import { app, BrowserWindow } from 'electron'
function createWindow() {
// 初始化一个新的BrowserWindow
win = new BrowserWindow({
// 设置窗口图标
icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'),
webPreferences: {
// 设置预加载的脚本
preload: path.join(__dirname, 'preload.js'),
},
})
win.webContents.on('did-finish-load', () => {
// 发送当前时间到渲染进程
win?.webContents.send('main-process-message', new Date().toLocaleString())
})
if (VITE_DEV_SERVER_URL) {
// 开发环境,加载开发服务器的URL
win.loadURL(VITE_DEV_SERVER_URL)
} else {
// 否则加载本地的index.html文件
win.loadFile(path.join(process.env.DIST, 'index.html'))
}
}
app.whenReady().then(createWindow)
Vue 项目入库 ./src/main.ts
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
createApp(App)
.mount('#app')
.$nextTick(() => {
postMessage({ payload: 'removeLoading' }, '*')
window.ipcRenderer.on('main-process-message', (_event, message) => {
console.log(message)
})
})
最重要的部分是window.ipcRenderer.on
的使用。这里的 ipcRenderer
是一个从 Electron 的主进程到渲染进程的异步通信机制。它被预加载脚本添加到window
对象中。这段代码监听来自主进程的main-process-message
事件,当事件触发时,它将接收到的消息输出到控制台。
实际上,在 Electron 官方文档中,直接将整个ipcRenderer
模块添加到window
对象是不推荐的做法。
你永远都不会想要通过预加载直接暴露整个
ipcRenderer
模块。 这将使得你的渲染器能够直接向主进程发送任意的 IPC 信息,会使得其成为恶意代码最强有力的攻击媒介。
因此,建议开发前把这些代码移除,包括预加载脚本 ./electron/preload.ts的暴露ipcRenderer
模块代码:
import { contextBridge, ipcRenderer } from 'electron'
contextBridge.exposeInMainWorld('ipcRenderer', withPrototype(ipcRenderer))
function withPrototype(obj: Record<string, any>) {
// 省略部分代码
}
虽然上面的一些代码不推荐,但基本的进程间通信也就是这样,通过预加载脚本暴露 API 给主进程和渲染进程进行调用实现通信。具体的通信方法后续再详细讲解。