跨平台桌面应用框架怎么选?Electron 快速上手案例实操
跨平台桌面应用框架选择与Electron实践指南 本文对比了三大跨平台桌面应用框架:Electron、Flutter和Tauri。Electron生态成熟但体积大,Flutter视觉统一但非Web技术栈,Tauri小巧安全但生态较弱。文章从架构、性能、生态三方面进行对比,指出Tauri在体积和性能上最优,Electron在开发体验上占优。 详细介绍了Electron开发实践:环境搭建、项目初始化、
文章目录
跨平台桌面应用框架选择
想开发一款跨平台桌面应用,摆在面前的选择常常让人纠结:Electron 生态成熟但打包体积巨大,Flutter 多端视觉统一却不属于 Web 技术栈,而 Tauri 以小巧安全出圈,到底该如何抉择?
三大框架的核心定位:

底层架构差异:
架构设计直接决定了框架的性能、体积和使用体验,三者的底层逻辑截然不同。

体积与性能对比:
| 框架 | 打包体积 | 启动速度 | 内存占用 |
|---|---|---|---|
| Tauri | 几 MB | 极快 | 极低 |
| Flutter | 约 40MB | 中等 | 中等 |
| Electron | 60~100+MB | 慢 | 高 |
整体表现排序:Tauri 最优 -> Flutter 居中 -> Electron 相对逊色
开发体验与生态:

实际应用案例:

Electron 快速上手
参考:
electron官方文档
B站:禹神:一小时快速上手Electron,前端Electron开发教程
什么是 Electron?
Electron 是由 GitHub 开发的跨平台桌面应用开发框架,核心价值在于:一套代码可打包生成 Windows、Linux、macOS 三端安装包;基于前端技术栈开发,降低桌面应用开发门槛;内置 Chrome 内核与 Node.js 环境,兼顾页面渲染与系统底层操作。
Electron 的本质组成
Electron 并非单纯的前端框架,而是三大核心的结合体:
- Chromium:负责页面渲染,相当于内置浏览器,无需依赖用户系统浏览器;
- Node.js:提供文件操作、进程管理等系统底层 API;
- Native API:封装各平台原生系统接口,实现系统通知、窗口管理、注册表操作等原生能力。
简单来说:页面渲染靠 Chromium,系统操作靠 Node.js,跨平台兼容靠 Native API。
核心优势与小缺点
- 优势:前端技术栈零门槛、跨平台、生态成熟、可快速开发桌面工具;
- 缺点:打包体积偏大(内置 Chrome+Node 环境),性能略逊于原生桌面框架。
搭建一个工程
- 环境准备:安装 Node.js(推荐 LTS 版本,如 v20+);
验证环境:
node -v
npm -v
- 初始化项目:创建项目文件夹并进入:
mkdir electron-test && cd electron-test
- 初始化 package.json:
npm init -y
- 填写好 package.json 中的必要信息及启动命令。
{
"name": "test",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron ." //start命令用于启动整个应用
},
"author": "tianyu", //为后续能顺利打包,此处要写明作者。
"license": "ISC",
"description": "this is a electron demo" //为后续能顺利打包,此处要编写描述。
}
- 安装 electron 作为开发依赖。
npm i electron -D

- 在 main.js 中编写代码,创建一个基本窗口
/*
main.js运行在应用的主进程上,无法访问Web相关API,主要负责:控制生命周期、显示界面、
控制渲染进程等其他操作。
*/
const { app, BrowserWindow } = require('electron')
// 用于创建窗口
function createWindow() {
const win = new BrowserWindow({
width: 800, // 窗口宽度
height: 600, // 窗口高度
autoHideMenuBar: true, // 自动隐藏菜单栏
alwaysOnTop: true, // 置顶
x: 0, // 窗口位置x坐标
y: 0 // 窗口位置y坐标
})
// 加载一个远程页面
win.loadURL('http://www.atguigu.com')
}
// 当app准备好后,执行createWindow创建窗口
app.on('ready',()=>{
createWindow()
})
关于 BrowserWindow 的更多配置项,请参考:BrowserWindow实例属性
- 启动应用查看效果
npm start
加载本地页面
- 创建 pages/index.html 编写内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>index</title>
</head>
<body>
<h1>你好啊!</h1>
</body>
</html>
- 修改 main.js 加载本地页面
// 加载一个本地页面
win.loadFile('./pages/index.html')

弹出的窗口就是一个游览器,可以开启开发者工具 (Ctrl + shift + i)

此时开发者工具会报出一个安全警告,需要修改 index.html ,配置 CSP(Content-Security-Policy)
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">
具体的index.html文件内容如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>index</title>
<!-- ✅ CSP 必须放在 head 里面 -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
script-src 'self';
">
</head>
<body>
<h1>你好啊!</h1>
</body>
</html>
上述配置的说明:
default-src 'self'
- default-src:配置加载策略,适用于所有未在其它指令中明确指定的资源类型。
- self:仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
style-src 'self' 'unsafe-inline'
- style-src:指定样式表(CSS)的加载策略。
- self:仅允许从同源的资源加载,禁止从不受信任的外部来源加载,提高安全性。
- unsafe-inline:允许在HTML文档内使用内联样式。
img-src 'self' data:
- img-src:指定图像资源的加载策略。
- self:表示仅允许从同源加载图像。
- data::允许使用 data: URI 来嵌入图像。这种URI模式允许将图像数据直接嵌入到HTML或CSS中,而不是通过外部链接引用。
完善窗口行为
- Windows 和 Linux 平台窗口特点是:关闭所有窗口时退出应用。
- mac 应用即使在没有打开任何窗口的情况下也继续运行,并且在没有窗口可用的情况下激活应用时会打开新的窗口。
// 当所有窗口都关闭时
app.on('window-all-closed', () => {
// 如果所处平台不是mac(darwin),则退出应用。
if (process.platform !== 'darwin') app.quit()
})
// 当app准备好后,执行createWindow创建窗口
app.on('ready',()=>{
createWindow()
// 当应用被激活时
app.on('activate', () => {
//如果当前应用没有窗口,则创建一个新的窗口
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
配置自动重启
- 安装 Nodemon
npm i nodemon -D
- 修改 package.json 命令
"scripts": {
"start": "nodemon --exec electron ."
}
配置 nodemon.json 规则
{
"ignore": [
"node_modules",
"dist"
],
"restartable": "r",
"watch": ["*.*"],
"ext": "html,js,css"
}
配置好以后,当代码修改后,应用就会自动重启了。

主进程与渲染进程
Electron 应用的核心是主进程与渲染进程的分离设计,理解进程模型是开发的关键。

- 主进程(Main Process)
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。 主进程在 Node.js 环境中运行,它具有 require 模块和使用所有 Node.js API 的能力,主进程的核心就是:使用 BrowserWindow 来创建和管理窗口。
入口文件:项目根目录的 main.js(可自定义);
运行环境:纯 Node.js 环境,可调用所有 Node API;
核心作用:
- 管理应用生命周期(启动、退出、激活);
- 创建、管理渲染进程(窗口);
- 调用系统原生 API;
- 处理渲染进程的通信请求。
特性:一个应用仅有一个主进程,无法使用浏览器专属 API(如 alert、window)。
- 渲染进程(Renderer Process)
每个 BrowserWindow 实例都对应一个单独的渲染器进程,运行在渲染器进程中的代码,必须遵守网页标准,这也就意味着:渲染器进程无权直接访问 require 或使用任何 Node.js 的 API。
载体:应用的每一个窗口对应一个渲染进程;
运行环境:浏览器环境,仅支持 HTML/CSS/JS 前端 API;
核心作用:负责页面 UI 渲染、用户交互;
特性:一个应用可存在多个渲染进程,无法直接调用 Node API(如 fs、__dirname)。
处于渲染器进程的用户界面,该怎样才与 Node.js 和 Electron 的原生桌面功能进行交互呢?
预加载脚本(Preload Script)
预加载(Preload)脚本是运行在渲染进程中的, 但它是在网页内容加载之前执行的,这意味着它具有比普通渲染器代码更高的权限,可以访问 Node.js 的 API,同时又可以与网页内容进行安全的交互。
简单说:它是 Node.js 和 Web API 的桥梁,Preload 脚本可以安全地将部分 Node.js 功能暴露给网页,从而减少安全风险。
进程关系总结
主进程(Node 环境) ←→ 预加载脚本(桥梁) ←→ 渲染进程(浏览器环境)
主进程管理所有渲染进程,二者通过预加载脚本完成进程通信(IPC)。
需求:点击按钮后,在页面呈现当前的 Node 版本。
- 创建预加载脚本 preload.js ,内容如下:
const {contextBridge} = require('electron')
// 暴露数据给渲染进程
contextBridge.exposeInMainWorld('myAPI',{
n:666,
version:process.version
})
- 在主线程中引入 preload.js
const win = new BrowserWindow({
/*******/
webPreferences:{
preload:path.resolve(__dirname,'./preload.js')
}
/*******/
})
- 在 html 页面中编写对应按钮,并创建专门编写网页脚本的 render.js ,随后引入。
<body>
<h1>你好啊!</h1>
<button id="btn1">点我</button>
<script type="text/javascript" src="./render.js"></script>
</body>
- 在渲染进程中使用 version
const btn1 = document.getElementById('btn1')
btn1.onclick = () => {
alert(myAPI.version)
}
效果如下所示:

核心:进程通信(IPC)
值得注意的是: 上文中的 preload.js ,无法使用全部 Node 的 API ,比如:不能使用 Node 中的 fs 模块,但主进程( main.js )是可以的,这时就需要进程通信了。简单说:要让 preload.js 通知 main.js 去调用 fs 模块去干活。
关于 Electron 进程通信,我们要知道:
- IPC 全称为: InterProcess Communication ,即:进程通信。
- IPC 是 Electron 中最为核心的内容,它是从 UI 调用原生 API 的唯一方法!
- Electron 中,主要使用 ipcMain 和 ipcRenderer 来定义“通道”,进行进程通信。
渲染进程 ➡️ 主进程(单向)
在渲染器进程中 ipcRenderer.send 发送消息,在主进程中使用 ipcMain.on 接收消息。 常用于:在 Web 中调用主进程的 API,例如下面的这个需求:
需求:点击按钮后,在用户的 D 盘创建一个 hello.txt 文件,文件内容来自于用户输入。
- 页面中添加相关元素, render.js 中添加对应脚本
<input id="content" type="text"><br><br>
<button id="btn2">在用户的D盘创建一个hello.txt</button>
const btn1 = document.getElementById('btn1')
const btn2 = document.getElementById('btn')
const content = document.getElementById('content')
btn1.onclick = () => {
alert(myAPI.version)
}
btn1.onclick = () => {
myAPI.saveFile(content.value)
}
- preload.js 中使用 ipcRenderer.send(‘信道’,参数) 发送消息,与主进程通信。
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
saveFile(str){
// 渲染进程给主进程发送一个消息
ipcRenderer.send('create-file',str)
}
})
- 主进程中,在加载页面之前,使用 ipcMain.on(‘信道’,回调) 配置对应回调函数,接收消息。
const fs = require('fs')
// 用于创建窗口
function createWindow() {
/**********/
// 主进程注册对应回调
ipcMain.on('create-file',createFile)
// 加载一个本地页面
win.loadFile(path.resolve(__dirname,'./pages/index.html'))
}
//创建文件
function createFile(event,data){
fs.writeFileSync('D:/hello.txt',data)
}
效果如下:

渲染进程 ↔ 主进程(双向)
渲染进程通过 ipcRenderer.invoke 发送消息,主进程使用 ipcMain.handle 接收并处理消息。 备注: ipcRender.invoke 的返回值是 Promise 实例。 常用于:从渲染器进程调用主进程方法并等待结果,例如下面的这个需求:
需求:点击按钮从 D 盘读取 hello.txt 中的内容,并将结果呈现在页面上。
- html页面中添加相关元素, render.js 中添加对应脚本
<button id="btn3">读取D盘中的hello.txt</button>
const btn3 = document.getElementById('btn3')
btn3.onclick = () => {
// myAPI.readFile() returns a Promise (ipcRenderer.invoke)
myAPI.readFile()
.then((data) => {
alert(data)
})
.catch((err) => {
alert('读取失败: ' + (err && err.message ? err.message : err))
})
}
- preload.js 中使用 ipcRenderer.invoke(‘信道’,参数) 发送消息,与主进程通信。
const {contextBridge,ipcRenderer} = require('electron')
contextBridge.exposeInMainWorld('myAPI',{
/*******/
readFile (){
return ipcRenderer.invoke('read-file')
}
})
- 主进程中,在加载页面之前,使用 ipcMain.handle(‘信道’,回调) 接收消息,并配置回调函数。
// 用于创建窗口
function createWindow() {
/**********/
// 主进程注册对应回调
ipcMain.handle('read-file',readFile)
// 加载一个本地页面
win.loadFile(path.resolve(__dirname,'./pages/index.html'))
}
//读取文件
function readFile(event,path){
return fs.readFileSync('D:/hello.txt').toString()
}
效果如下:

主进程到 ➡️ 渲染进程
主进程使用 win.webContents.send 发送消息,渲染进程通过 ipcRenderer.on 处理消息。 常用于:从主进程主动发送消息给渲染进程。
项目打包分发
使用 electron-builder 打包,支持自定义安装配置、生成 exe 安装包。
- 安装打包工具 electron-builder
npm install electron-builder -D
- 在 package.json 中进行相关配置,具体配置如下: 备注:json 文件不支持注释,使用时请去掉所有注释。
{
"name": "video-tools", // 应用程序的名称
"version": "1.0.0", // 应用程序的版本
"main": "main.js", // 应用程序的入口文件
"scripts": {
"start": "electron .", // 使用 `electron .` 命令启动应用程序
"build": "electron-builder" // 使用 `electron-builder` 打包应用程序,生成安装包
},
"build": {
"appId": "com.atguigu.video", // 应用程序的唯一标识符
// 打包windows平台安装包的具体配置
"win": {
"icon":"./logo.ico", //应用图标
"target": [
{
"target": "nsis", // 指定使用 NSIS 作为安装程序格式
"arch": ["x64"] // 生成 64 位安装包
}
]
},
"nsis": {
"oneClick": false, // 设置为 `false` 使安装程序显示安装向导界面,而不是一键安装
"perMachine": true, // 允许每台机器安装一次,而不是每个用户都安装
"allowToChangeInstallationDirectory": true // 允许用户在安装过程中选择安装目录
}
},
"devDependencies": {
"electron": "^30.0.0", // 开发依赖中的 Electron 版本
"electron-builder": "^24.13.3" // 开发依赖中的 `electron-builder` 版本
},
"author": "tianyu", // 作者信息
"license": "ISC", // 许可证信息
"description": "A video processing program based on Electron" // 应用程序的描述
}
- 执行打包命令
npm run build
打包完成后,项目根目录生成 dist 文件夹,内含 exe 安装包,双击即可安装使用。---- 注:Electron 打包体积偏大(示例项目约 70M+),因内置 Chrome 与 Node 环境,属正常现象。
总结
本文从 Electron 核心概念出发,完整覆盖进程模型、项目搭建、进程通信、打包分发四大核心环节。Electron 的核心精髓是主进程 + 渲染进程的分离设计,而进程通信是连接页面与系统底层的关键。
更多推荐
所有评论(0)