什么是 electron
Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux------不需要本地开发 经验。
它的运行核心可以总结为下图

electron 的核心概念
主进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,这意味着它具有 require
模块和使用所有 Node.js API 的能力。
预加载脚本
预加载(preload)脚本包含了那些执行于渲染器进程中,且先于网页内容开始加载的代码 。 这些脚本虽运行于渲染器的环境中,却因能访问 Node.js API 而拥有了更多的权限, 预加载脚本是运行在渲染进程的,他的优先级高于渲染进程, 他有部分的 node.js API 权限。
渲染进程
每个 Electron 应用都会为每个打开的 BrowserWindow
( 与每个网页嵌入 ) 生成一个单独的渲染器进程。 洽如其名,渲染器负责 渲染 网页内容。 所以实际上,运行于渲染器进程中的代码是须遵照网页标准的 (至少就目前使用的 Chromium 而言是如此) 。每一个 tab 栏就对应一个渲染进程。
进程间通讯
其本质就是发布订阅模式,但是这里叫 ipc(进程间通讯)
- 主进程向渲染进程
- 主进程通过 webContents.send 向渲染进程发送消息
- 预加载脚本通过 ipcRenderer.on 方法发送到渲染进程
- 渲染进程接受对应方法即可
- 渲染进程向主进程
- 渲染进程调用预加载脚本的方法传递消息
- 预加载脚本通过 ipcRender.send 方法发送到主进程
- 主进程通过 ipcMain.on 方法监听消息
- 主进程与渲染进程双向通讯
- 发送方使用 invoke 触发事件
- 接受方通过 handle 处理事件
其数据核心流向可以总结为这张图

启动项目
我们使用 vite + electron + typescript 做一个简单的开机一键启动应用的工具, 在这里我们就不自己在项目中去配置 electron , 感兴趣的可以查看官网 www.electronjs.org/zh/docs/lat... 如果下载不下来,要确保 node 版本为高版本, 并使用 pnpm 下载。 这里我们直接使用便捷的脚手架进行搭建 cn.electron-vite.org/guide/
sql
npm create @quick-start/electron@latest
根据指引构建完成后你会得到这样一个文件目录
erlang
.
├──src
│ ├──main
│ │ ├──index.ts
│ │ └──...
│ ├──preload
│ │ ├──index.ts
│ │ └──...
│ └──renderer # with vue, react, etc.
│ ├──src
│ ├──index.html
│ └──...
├──electron.vite.config.ts
├──package.json
└──...
可以根据自己的需要在 vite 配置文件中配置想要的相关信息

同时也可以在 ts 配置文件中配置 ts 相关配置

-
main 文件夹
main 文件夹就是主进程, 可以和计算机底层进行 io 操作,在这里我们添加两行代码
javascript
import { onGetAppList, onHandleStartCMD } from './startApp'
....
app.whenReady().then(() => {
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
// ============== event start =============
ipcMain.handle('getAppPath', onGetAppList)
ipcMain.handle('execStartCMD', onHandleStartCMD)
// ============== event end ================
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
抽离的两个方法
javascript
// startApp.ts
import { exec, execSync } from 'child_process'
// 一键启动应用
const onHandleStartCMD = (event, data) => {
// 启动目标软件(例如启动 VSCode)
data.forEach((item) => {
exec(`open "/Applications/${item}"`, (error, stdout) => {
if (error) console.error(`执行失败: ${error}`)
else console.log(`已启动: ${stdout}`)
})
})
}
// 获取应用列表
const onGetAppList = async () => {
const res = execSync('ls /Applications').toString()
return res.split('\n').filter((item) => item !== '')
}
export { onHandleStartCMD, onGetAppList }
注意上图的启动路径是可以配置的,不用写死, 这里为了方便直接写死了 /Applications
-
preload 文件夹
这个文件是预加载脚本,可以理解为一个通道, 通过它可以实现 主进程与渲染进程的通讯
js
import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
/**
* send 使用 on 监听
* invoke 使用 handle 双向通讯
*/
// Custom APIs for renderer
const api = {
// 获取应用路径
getAppPath: async () => {
return await ipcRenderer.invoke('getAppPath')
},
// 执行启动命令
execStartCMD: async (list: string[]) => {
return await ipcRenderer.invoke('execStartCMD', list)
}
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (define in dts)
window.electron = {
sendMessage(channel: string, ...args: unknown[]) {
ipcRenderer.send(channel, ...args)
}
}
// @ts-ignore (define in dts)
window.api = api
}
-
render 文件夹
该文件夹就是一个普通的前端项目文件夹, 可以按照正常的前端项目开发进行开发

在启动时只需要调用暴露的方法名即可 api.execStartCMD(startList)
javascript
import React from 'react'
import { Button, Flex } from 'antd'
import { useNavigate } from 'react-router-dom'
import { useAppSelector } from '@renderer/store/hooks'
const Start: React.FC = () => {
const navigate = useNavigate()
const startList = useAppSelector((state) => state.startConfig.startList)
return (
<main>
<Flex gap={12}>
<section>
<Button
onClick={async () => {
api.execStartCMD(startList)
}}
>
一键启动
</Button>
</section>
<section>
<Button
onClick={() => {
navigate('/settings')
}}
>
配置启动项
</Button>
</section>
</Flex>
</main>
)
}
export default Start
打包
执行对应命令行即可

最终打包成功后会生成 dist 文件

将压缩包解压后可以获得我们的程序

使用
双击打开后

点击配置启动项

返回点击一键启动

这个是一个简单的实现, 功能并不完善, 但能够帮助你快速的理解 electron 的运行规则, 你可以基于此去实现自己想要的功能