尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Electron桌面应用开发入门:从环境搭建到IPC通信实战

Electron桌面应用开发入门:从环境搭建到IPC通信实战 1. 从零到一为什么选择Electron作为桌面开发起点如果你是一名前端开发者或者对Web技术栈比较熟悉现在想把手里的网页变成一个独立的、可以安装到用户电脑上的桌面应用那么Electron几乎是你绕不开的第一个选项。我第一次接触Electron就是想把一个内部用的数据看板工具打包成客户端方便团队里不习惯开浏览器的同事使用。当时也对比过NW.js、Tauri这些方案但最终还是选了Electron原因很简单生态成熟、文档齐全、社区活跃遇到问题一搜基本都有答案。对于学习第一个桌面程序来说这能省下大量折腾环境、解决冷门bug的时间让你把精力集中在“如何把想法变成应用”这件事本身。Electron的核心逻辑非常直观它用Chromium来渲染界面用Node.js来跑后台逻辑。这意味着你写窗口里的按钮、表格、动画用的就是HTML、CSS和JavaScript或者Vue/React这些你熟悉的前端框架而你读写本地文件、调用系统接口、处理繁重计算用的就是Node.js那套模块。两者通过一个叫“进程间通信IPC”的机制打通。所以一个Electron应用跑起来至少会有一个“主进程”Main Process和若干个“渲染进程”Renderer Process。主进程是入口负责创建窗口、管理应用生命周期渲染进程就是一个个窗口负责展示UI。理解这个“主从架构”是后续一切开发的基础。很多人卡在第一步环境安装。网上的教程五花八门有的让你装一堆全局包有的又强调要用项目内依赖新手很容易晕。其实核心就两样Node.js和一个趁手的代码编辑器比如VS Code。只要这两样准备好了创建第一个Electron程序只需要几分钟。接下来我会带你走一遍最清晰、最不容易出错的安装和创建流程并解释清楚每一个步骤背后的原因让你不仅能把程序跑起来还能明白它为什么能跑起来。2. 环境搭建避开那些看似简单实则坑人的“捷径”安装环境听起来是小事但很多初学者在这里浪费了大量时间主要是因为用了过时的教程或者跳过了关键的验证步骤。我们的目标是搭建一个干净、可复现的开发环境。2.1 Node.js安装与版本管理的艺术首先你需要Node.js。这不是Electron的要求而是因为Electron的构建工具和依赖管理都基于Node.js的包管理器npm或yarn、pnpm。直接去Node.js官网下载安装包是最直接的方式但我强烈建议你使用Node版本管理工具比如nvmWindows下是nvm-windows。为什么不用安装包直接装因为桌面开发项目周期可能很长不同项目依赖的Node版本或Electron版本可能不同。直接安装固定版本未来切换成本很高。用nvm你可以轻松地在多个Node版本间切换。安装nvm-windows后打开命令行建议使用管理员权限的PowerShell或CMD执行安装命令然后就可以用nvm install 18.19.0这样的命令安装指定版本的Node。我推荐使用Node.js的LTS长期支持版比如18.x或20.x它们在稳定性和兼容性上更有保障。安装完成后别急着下一步。打开终端输入node -v和npm -v确保能正确显示版本号。这步验证能排除90%的“命令未找到”问题这类问题通常是环境变量没有自动配置好。如果提示不是内部或外部命令你需要手动将Node.js的安装路径比如C:\Program Files\nodejs\添加到系统的PATH环境变量中。2.2 包管理器的选择与镜像加速Node.js自带npm但它的下载速度在国内可能比较慢。你有三个主流选择继续用npm但换源、使用yarn、或者使用pnpm。我个人目前更倾向于pnpm因为它采用硬链接管理依赖能极大节省磁盘空间并且安装速度很快。你可以通过npm install -g pnpm来安装它。无论用哪个配置国内镜像源都能大幅提升体验。对于npm可以执行npm config set registry https://registry.npmmirror.com/对于pnpm执行pnpm config set registry https://registry.npmmirror.com/这个步骤能避免后续安装Electron时卡在downloading electron binary...这类网络错误上。很多教程会教你用electron_mirror环境变量但在项目初期直接设置包管理器镜像更一劳永逸。2.3 创建项目目录与初始化环境就绪后找一个合适的地方创建你的项目文件夹例如my-first-electron-app。用终端进入这个目录执行初始化命令npm init -y或者如果你用pnpmpnpm init这个命令会生成一个package.json文件它是你项目的“身份证”和“说明书”记录了项目名称、版本、依赖等信息。-y参数表示全部接受默认配置快速跳过问答环节。之后你可以随时打开这个文件修改。注意有些非常古老的教程可能会让你全局安装electron包npm install -g electron。千万不要这样做Electron应该作为项目的开发依赖devDependency安装这样可以保证每个项目使用自己独立的、版本确定的Electron避免全局版本冲突。这是现代Node.js项目开发的基本规范。3. 第一个Electron应用从“Hello World”理解核心骨架现在我们来创建最核心的三个文件它们构成了一个最小化的Electron应用骨架。这个骨架虽然简单但包含了所有关键概念。3.1 安装Electron依赖在项目根目录下运行安装命令。由于我们只是在开发阶段需要Electron来运行和打包所以将其安装为开发依赖npm install electron --save-dev或pnpm add electron -D安装过程会下载Electron的预编译二进制文件这就是之前提到的downloading electron binary阶段。配置了镜像后这个过程应该很快。安装完成后package.json里会多出一个devDependencies字段里面包含了Electron及其版本。3.2 编写主进程文件 (main.js)主进程是应用的“大脑”。在项目根目录创建一个名为main.js的文件名字可以自定义但这是惯例。写入以下内容// 导入必要的模块。electron 模块提供了控制应用生命周期和原生 GUI 相关的方法。 // app 模块控制整个应用的事件生命周期。 // BrowserWindow 模块用于创建和控制浏览器窗口。 const { app, BrowserWindow } require(electron); const path require(path); // Node.js 的路径模块用于处理文件路径 // 声明一个全局变量用于持有窗口对象的引用。 // 如果不这么做当JavaScript对象被垃圾回收时窗口可能会意外关闭。 let mainWindow; // 定义一个创建应用窗口的函数 function createWindow() { // 创建一个新的浏览器窗口 mainWindow new BrowserWindow({ width: 800, // 窗口宽度 height: 600, // 窗口高度 webPreferences: { // 这里配置网页功能的偏好设置 nodeIntegration: true, // 是否在渲染进程中集成 Node.js。从安全角度新项目建议设为 false并使用上下文隔离和预加载脚本。这里为了简单演示设为 true。 contextIsolation: false, // 是否启用上下文隔离。安全最佳实践是设为 true配合预加载脚本。这里为了演示方便设为 false。 } }); // 并且加载本地的 index.html 文件 // path.join(__dirname, index.html) 会拼接成当前文件所在目录下的 index.html 的绝对路径 mainWindow.loadFile(index.html); // 打开开发者工具调试用上线前应移除 // mainWindow.webContents.openDevTools(); } // 当 Electron 完成初始化并准备创建浏览器窗口时会调用这个函数。 // 部分 API 只能在这个事件发生后使用。 app.whenReady().then(() { createWindow(); // 在 macOS 上当点击 dock 图标并且没有其他窗口打开时通常会在应用程序中重新创建一个窗口。 app.on(activate, function () { if (BrowserWindow.getAllWindows().length 0) createWindow(); }); }); // 在所有窗口关闭时退出应用macOS 除外 // 在 macOS 上除非用户用 Cmd Q 确定地退出否则应用及其菜单栏会保持激活。 app.on(window-all-closed, function () { if (process.platform ! darwin) app.quit(); });我来解释几个关键点app.whenReady().then(...)这是启动的黄金时机。在ready事件触发之前很多Electron API是无法使用的。所以创建窗口的逻辑必须放在这里面。BrowserWindow配置webPreferences里的nodeIntegration和contextIsolation是安全性的关键。老教程和简单Demo为了方便会把nodeIntegration设为truecontextIsolation设为false但这会让渲染进程拥有直接访问Node.js全部API的能力存在安全风险。对于正式项目强烈建议采用“上下文隔离”模式即nodeIntegration: false,contextIsolation: true然后通过预加载脚本preload script暴露有限的、安全的API给渲染进程。我们这个第一个程序以跑通为首要目标所以采用了宽松配置但你必须知道这是为什么。全局变量mainWindow将窗口实例赋值给一个全局变量是为了防止它被JavaScript的垃圾回收机制自动回收导致窗口无故关闭。这是一个非常经典的“坑”。3.3 编写渲染进程文件 (index.html)这个文件就是你熟悉的网页了。在项目根目录创建index.html!DOCTYPE html html head meta charsetUTF-8 titleHello Electron!/title /head body h1Hello from Electron!/h1 p我们正在使用 Node.js scriptdocument.write(process.versions.node)/script,/p pChromium scriptdocument.write(process.versions.chrome)/script,/p p和 Electron scriptdocument.write(process.versions.electron)/script./p p当前目录是: scriptdocument.write(__dirname)/script/p /body /html这个页面会动态显示当前环境中的Node.js、Chromium和Electron版本以及文件所在目录。注意里面用了script标签内联执行JavaScript并且直接访问了process和__dirname这些Node.js全局对象。这能正常工作正是因为我们之前在main.js里设置了nodeIntegration: true。如果将其设为false这些脚本会报错。3.4 修改package.json的启动脚本打开package.json找到scripts部分修改或添加一个start命令scripts: { start: electron . }这个命令告诉npm/pnpm“当我运行npm start时请在当前目录.下执行electron命令”。electron命令会默认去寻找项目根目录下的main.js作为主进程入口文件。3.5 运行你的第一个应用激动人心的时刻到了。在终端里确保你的路径在项目根目录下然后运行npm start或者pnpm start几秒钟后一个独立的桌面窗口应该会弹出来显示着“Hello from Electron!”以及版本信息。恭喜你你的第一个Electron应用成功运行了4. 深入核心主进程与渲染进程的通信初探程序跑起来只是第一步。Electron的灵魂在于主进程和渲染进程之间的通信IPC。我们通过一个简单的例子来感受一下让渲染进程的按钮点击触发主进程执行一个操作比如弹出一个系统对话框然后将结果返回给渲染进程显示。4.1 改造主进程 (main.js)首先我们需要引入IPC模块。修改main.jsconst { app, BrowserWindow, ipcMain, dialog } require(electron); // 引入 ipcMain 和 dialog const path require(path); let mainWindow; function createWindow() { mainWindow new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true, contextIsolation: false, // 预加载脚本的路径稍后我们会创建它 // preload: path.join(__dirname, preload.js) } }); mainWindow.loadFile(index.html); // mainWindow.webContents.openDevTools(); // 可以打开开发者工具方便调试 } // 监听渲染进程通过“channel-name”通道发来的异步消息 ipcMain.on(channel-name, (event, arg) { console.log(收到渲染进程消息:, arg); // arg 是渲染进程发送过来的数据 // 主进程执行一个操作例如弹出一个文件选择对话框 dialog.showOpenDialog({ properties: [openFile] }).then(result { console.log(用户选择的文件:, result.filePaths); // 操作完成后通过 event.reply 将结果发送回给发送消息的渲染进程 event.reply(channel-reply, 主进程已收到。你发送的数据是${arg}。选择的文件是${result.filePaths[0] || 无}); }).catch(err { console.log(err); event.reply(channel-reply, 操作出错${err.message}); }); }); app.whenReady().then(createWindow); // ... 保留之前的 window-all-closed 和 activate 事件处理代码这里我们导入了ipcMain和dialog。ipcMain.on(channel-name, ...)表示主进程在监听一个名为channel-name的频道。当渲染进程向这个频道发送消息时这个回调函数就会被执行。回调函数接收两个参数event对象用于回复消息和arg渲染进程发送过来的数据。我们在回调里用dialog.showOpenDialog弹出一个系统文件选择框然后在Promise的.then中通过event.reply(channel-reply, ...)将结果发送回渲染进程。4.2 改造渲染进程 (index.html)修改index.html添加按钮和显示结果的区域并编写前端IPC逻辑!DOCTYPE html html head meta charsetUTF-8 titleIPC通信演示/title /head body h1Electron IPC 通信测试/h1 button idsendBtn点击我向主进程发送消息并打开文件对话框/button p发送的数据: input typetext idinputData valueHello Main Process! //p div idresult stylemargin-top: 20px; padding: 10px; border: 1px solid #ccc; min-height: 50px; 等待主进程回复... /div script // 引入 electron 的渲染进程 IPC 模块 const { ipcRenderer } require(electron); document.getElementById(sendBtn).addEventListener(click, () { const dataToSend document.getElementById(inputData).value; // 向主进程的 channel-name 通道发送异步消息 ipcRenderer.send(channel-name, dataToSend); document.getElementById(result).innerHTML 消息已发送等待主进程处理...; }); // 监听主进程通过 channel-reply 通道回复的消息 ipcRenderer.on(channel-reply, (event, arg) { console.log(收到主进程回复:, arg); document.getElementById(result).innerHTML strong主进程回复/strong ${arg}; }); /script /body /html在渲染进程的脚本里我们通过require(electron)拿到了ipcRenderer模块。按钮点击时ipcRenderer.send(channel-name, data)将输入框的数据发送给主进程。同时我们通过ipcRenderer.on(channel-reply, ...)监听主进程的回复收到后更新页面上的#result元素。4.3 运行与测试保存所有文件再次运行npm start。点击窗口中的按钮你应该会看到系统文件选择对话框弹出。选择一个文件或取消对话框关闭后页面上会显示主进程返回的信息其中包含你发送的文本和选择的文件路径。这个过程清晰地展示了Electron的典型工作流用户交互发生在渲染进程点击网页按钮。渲染进程通过IPC发送请求给主进程。主进程执行原生或耗时操作如调用系统对话框、访问数据库、读写文件。主进程将结果通过IPC返回给渲染进程。渲染进程更新UI将结果展示给用户。这种架构将敏感的、需要系统权限的操作集中在主进程而将UI交互留在渲染进程既安全又符合桌面应用的开发模式。5. 项目配置优化与常见问题排雷第一个程序跑通后我们还需要做一些优化让它更接近一个真正的项目并提前了解一些必然会遇到的坑。5.1 完善package.json配置一个基础的Electron项目package.json应该包含以下关键字段{ name: my-first-electron-app, version: 1.0.0, description: 我的第一个Electron应用, main: main.js, // 指定主进程入口文件electron命令会找它 scripts: { start: electron ., // 开发启动 pack: electron-builder --dir, // 生成安装包目录测试用 dist: electron-builder // 生成可分发的安装包 }, devDependencies: { electron: ^28.0.0 // 版本号建议锁定大版本避免自动升级导致不兼容 }, build: { appId: com.yourcompany.yourapp, productName: MyFirstApp, directories: { output: dist // 打包输出目录 }, files: [ main.js, index.html, package.json // 如果有其他资源文件或预加载脚本也要加进来 ], win: { target: nsis // Windows下的打包目标nsis是安装程序 }, mac: { target: dmg }, linux: { target: AppImage } } }注意main字段必须指向你的主进程文件。build配置是为后续使用electron-builder打包工具准备的这是一个功能强大且流行的打包工具。5.2 安装与配置打包工具开发完成后你需要将应用打包成可执行文件如.exe, .dmg, .AppImage。electron-builder是首选。在项目中安装它npm install electron-builder --save-dev或pnpm add electron-builder -D安装后运行npm run dist对应上面配置的脚本它就会读取package.json中的build配置开始打包。第一次打包会下载对应平台的构建工具时间较长。5.3 高频问题排查指南Error: Electron failed to install correctly/electron downloading electron binary... typeerror: fetch failed原因网络问题导致Electron二进制文件下载失败。解决检查并设置npm/pnpm的镜像源如前所述。设置Electron镜像环境变量临时方案# Windows (CMD) set ELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/ # Windows (PowerShell) $env:ELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/ # macOS/Linux export ELECTRON_MIRRORhttps://npmmirror.com/mirrors/electron/删除node_modules文件夹和package-lock.json或pnpm-lock.yaml重新运行npm install或pnpm install。Uncaught ReferenceError: require is not defined原因在渲染进程的HTML或JS中使用了require但创建BrowserWindow时未启用nodeIntegration或启用了contextIsolation但未正确配置预加载脚本。解决检查main.js中webPreferences的配置。对于学习Demo可以暂时设为{ nodeIntegration: true, contextIsolation: false }。但务必理解生产环境应采用预加载脚本的安全模式。Error: Could not find any Visual Studio installation原因在Windows上某些Node.js原生模块的编译需要Visual Studio的构建工具。解决安装windows-build-tools已不推荐或直接安装Visual Studio 2019/2022并在安装时勾选“使用C的桌面开发”工作负载。或者更简单的方法是安装Node.js时选择带有“自动安装必要工具”选项的版本。应用图标不显示或打包后白屏原因路径问题。开发时使用loadFile(index.html)是基于当前工作目录。但打包后文件位置变了。解决在加载文件或资源时使用path.join(__dirname, relative/path)来构造绝对路径。例如mainWindow.loadFile(path.join(__dirname, index.html)); // 加载预加载脚本 preload: path.join(__dirname, preload.js)进程崩溃或内存泄漏原因Electron应用本质是浏览器每个窗口都是一个独立的Chromium渲染进程。如果页面JS有内存泄漏或者打开了太多窗口/WebView没关闭会导致内存持续增长。解决使用Chrome开发者工具的Memory面板进行性能分析。确保在窗口关闭时window.on(closed, ...)将窗口引用置为null。对于复杂SPA注意组件销毁时的监听器移除。6. 安全进阶从宽松模式转向生产就绪的上下文隔离我们之前的Demo为了简单关闭了安全特性。对于一个要交付给用户的正式应用必须启用上下文隔离Context Isolation。这相当于在渲染进程的网页你的前端代码和Node.js/Electron API之间筑起一道墙。网页不能直接访问require或process只能通过一个“预加载脚本”Preload Script暴露出来的有限API进行通信。6.1 创建预加载脚本 (preload.js)在项目根目录创建preload.js// 预加载脚本在渲染进程网页加载之前运行并且同时拥有访问Node.js和DOM的有限能力。 const { contextBridge, ipcRenderer } require(electron); // 通过 contextBridge.exposeInMainWorld 向渲染进程的 window 对象暴露安全的 API。 // 这里我们只暴露一个名为 electronAPI 的对象里面包含我们允许渲染进程调用的方法。 contextBridge.exposeInMainWorld(electronAPI, { sendMessage: (data) ipcRenderer.send(channel-name, data), onReply: (callback) ipcRenderer.on(channel-reply, (event, arg) callback(arg)) // 注意我们只暴露了具体的函数而不是整个 ipcRenderer 模块。 });6.2 修改主进程配置修改main.js中创建BrowserWindow的部分webPreferences: { nodeIntegration: false, // 关闭 Node.js 集成 contextIsolation: true, // 启用上下文隔离 preload: path.join(__dirname, preload.js) // 指定预加载脚本路径 }6.3 修改渲染进程代码修改index.html中的脚本部分不再直接使用require(electron)而是使用预加载脚本暴露的APIscript // 不再使用 const { ipcRenderer } require(electron); // 而是使用 window.electronAPI document.getElementById(sendBtn).addEventListener(click, () { const dataToSend document.getElementById(inputData).value; // 使用暴露的 API 发送消息 window.electronAPI.sendMessage(dataToSend); document.getElementById(result).innerHTML 消息已发送等待主进程处理...; }); // 使用暴露的 API 监听回复 window.electronAPI.onReply((arg) { console.log(收到主进程回复:, arg); document.getElementById(result).innerHTML strong主进程回复/strong ${arg}; }); /script现在重启应用npm start功能应该和之前完全一样但架构安全了许多。渲染进程中的网页无法直接访问ipcRenderer或任何Node.js模块只能通过我们精心设计的window.electronAPI接口与主进程通信。这是构建可靠、安全Electron应用的基石。走到这一步你已经完成了Electron开发环境搭建、第一个应用创建、核心IPC通信理解以及基础安全配置。这只是一个起点但已经涵盖了最核心的概念和流程。接下来你可以探索如何集成Vue、React等现代前端框架如何使用electron-builder打包出专业的安装程序如何实现系统托盘、菜单、原生通知等更多桌面特性。记住Electron的官方文档永远是最好、最及时的学习资料遇到问题时先去那里找答案。
返回列表