Electron 知识点详解


第一章:Electron 入门与核心概念

  1. 什么是 Electron?

    • 定义:一个使用 Web 技术 (HTML, CSS, JavaScript) 构建跨平台桌面应用程序的开源框架。
    • 核心组成:Chromium (用于渲染界面) + Node.js (用于访问操作系统和后端能力) + 自定义 APIs。
    • 目标:让 Web 开发者能够轻松创建功能丰富的桌面应用。
  2. 为什么选择 Electron?

    • 跨平台: 一套代码库,可构建 Windows, macOS, Linux 应用。
    • Web 技术栈: 复用现有的 Web 开发知识和生态系统 (NPM 包、框架如 Vue/React/Angular)。
    • 快速开发: 利用 Web 技术的开发效率。
    • 强大的能力: 可以访问完整的操作系统 API (通过 Node.js) 和 Electron 提供的原生 API。
    • 成熟的社区和生态: 广泛使用 (VS Code, Slack, Discord 等),拥有丰富的文档和第三方库。
  3. Electron 的主要挑战/缺点:

    • 包体积大: 每个应用都内嵌了 Chromium 和 Node.js,导致基础包体积较大 (几十 MB 到上百 MB)。
    • 内存占用: 相较于原生应用,内存占用可能更高。
    • 性能: 对于极其注重性能的场景,可能不如原生应用。
    • 安全风险: 如果不注意,将 Node.js 能力暴露给渲染进程可能带来安全风险。
  4. 核心架构:主进程 (Main Process) 与渲染进程 (Renderer Process)

    • 主进程 (Main Process):
      • 唯一,程序的入口点 (main.jspackage.json 中指定的入口文件)。
      • 拥有完整的 Node.js 环境。
      • 负责管理应用的生命周期、创建和管理 BrowserWindow (渲染进程)、处理原生操作系统交互 (菜单、对话框、托盘等)。
      • 负责渲染 HTML/CSS。
    • 渲染进程 (Renderer Process):
      • 每个 BrowserWindow 实例拥有一个独立的渲染进程。
      • 本质上是一个 Chromium 浏览器窗口环境,负责渲染 HTML, CSS, 执行 JavaScript (UI 逻辑)。
      • 默认情况不能直接访问 Node.js API 或操作系统资源 (出于安全考虑)。
      • 可以通过特定的机制 (IPC, Preload Script) 与主进程通信以获取系统能力。
    • 理解进程模型是掌握 Electron 的关键。

第二章:环境搭建与基础项目

  1. 环境要求:

    • Node.js (自带 npm 或使用 yarn)
    • 代码编辑器 (如 VS Code)
  2. 创建基础项目:

    • 创建项目目录:mkdir my-electron-app && cd my-electron-app
    • 初始化 npm 项目:npm init -y
    • 安装 Electron:npm install --save-dev electron
    • 创建入口文件 (main.js) 和界面文件 (index.html)。
  3. package.json 关键配置:

    • "main": 指定主进程入口文件 (e.g., "main": "main.js")。
    • "scripts":
      • "start": "electron .": 定义启动应用的命令。
  4. main.js (主进程) 基础代码:

    • 引入 appBrowserWindow 模块:const { app, BrowserWindow } = require('electron')
    • 创建窗口函数 createWindow()
    • appready 事件触发时调用 createWindow()
    • 加载 HTML 文件:win.loadFile('index.html')win.loadURL('http://localhost:3000') (用于加载开发服务器)。
    • 处理应用生命周期事件 (如 window-all-closed, activate)。
  5. index.html (渲染进程) 基础代码:

    • 标准的 HTML 结构。
    • 可以通过 <script src="renderer.js"></script> 引入渲染进程的 JavaScript 文件。
  6. 启动与调试:

    • 启动应用:npm start
    • 打开开发者工具:在 BrowserWindow 实例上调用 win.webContents.openDevTools()

第三章:主进程 (Main Process) 详解

  1. app 模块:

    • 控制应用程序的事件生命周期。
    • 常用事件:ready, window-all-closed, activate, before-quit, will-quit
    • 常用方法:app.quit(), app.getPath(name) (获取系统路径), app.getName(), app.getVersion(), app.isPackaged
  2. BrowserWindow 模块:

    • 创建和控制浏览器窗口。
    • 构造函数选项 (new BrowserWindow({...})):
      • width, height: 窗口尺寸。
      • x, y: 窗口位置。
      • frame: 是否显示窗口边框和标题栏。
      • show: 创建时是否立即显示。
      • webPreferences: 配置网页功能的关键选项 (见下)。
    • 实例方法:win.loadURL(), win.loadFile(), win.close(), win.show(), win.hide(), win.maximize(), win.minimize(), win.isMaximized(), win.webContents (访问 WebContents 对象)。
    • 实例事件:closed, focus, blur, resize, move
  3. webPreferences 选项 (在 BrowserWindow 中配置):

    • nodeIntegration (boolean, 默认 false): 是否在渲染进程中启用 Node.js 集成。强烈建议保持 false 以提高安全性。
    • contextIsolation (boolean, 默认 true): 是否启用上下文隔离。强烈建议保持 true。这使得 preload 脚本和渲染进程的 JavaScript 运行在不同的上下文中,更安全。
    • preload (string): 指定一个预加载脚本的路径。该脚本在渲染进程加载网页之前运行,并且可以访问 Node.js API (即使 nodeIntegration: false) 和 DOM API。这是连接主进程和渲染进程、安全暴露特定 Node.js 功能的关键。
    • sandbox (boolean, 默认 false): 是否启用 Chromium OS 级别的沙盒。

第四章:渲染进程 (Renderer Process) 详解

  1. 角色:

    • 负责展示用户界面 (HTML/CSS)。
    • 执行用户界面的交互逻辑 (JavaScript)。
    • 运行标准的 Web API (Fetch, DOM 操作, Canvas 等)。
  2. 访问 Node.js (不推荐直接开启 nodeIntegration):

    • 安全隐患: 如果 nodeIntegration: true,渲染进程中的任何脚本 (包括第三方库) 都可以访问文件系统、执行命令等,容易受到 XSS 攻击影响。
    • 推荐方式: 使用 preload 脚本 + contextBridge
  3. preload.js 脚本:

    • webPreferences 中通过 preload 选项指定。
    • 运行在具有 Node.js 环境但与渲染器隔离的上下文中 (当 contextIsolation: true)。
    • 可以访问 windowdocument 对象。
    • 主要用途:
      • 使用 contextBridge.exposeInMainWorld(apiKey, apiObject) 安全地向渲染进程暴露选择性的 Node.js 功能或 IPC 调用接口。
      • 监听来自主进程的 IPC 消息。
  4. renderer.js (渲染进程脚本):

    • 通过 <script> 标签在 HTML 中引入。
    • 负责 DOM 操作、事件处理、调用 preload 脚本暴露的 API。
    • 如果使用了 contextBridge,可以通过 window[apiKey] 访问暴露的接口。

第五章:进程间通信 (Inter-Process Communication - IPC)

  1. 为什么需要 IPC?

    • 主进程和渲染进程是独立的进程,需要一种机制来传递消息和数据。
    • 渲染进程需要请求主进程执行特权操作 (如读写文件、显示原生对话框)。
    • 主进程需要通知渲染进程更新 UI 或传递数据。
  2. 主要模块:

    • ipcMain (在主进程中使用)
    • ipcRenderer (在渲染进程或 preload 脚本中使用)
    • contextBridge (在 preload 脚本中使用,用于安全暴露 API)
  3. 通信模式:

    • 渲染进程 -> 主进程 (单向):
      • 渲染进程 (preloadrenderer): ipcRenderer.send(channel, ...args)
      • 主进程: ipcMain.on(channel, (event, ...args) => { ... })
    • 渲染进程 -> 主进程 -> 渲染进程 (双向异步,请求/响应):
      • 渲染进程 (preloadrenderer): const result = await ipcRenderer.invoke(channel, ...args)
      • 主进程: ipcMain.handle(channel, async (event, ...args) => { ...; return result; })
    • 主进程 -> 渲染进程 (单向):
      • 主进程 (需要 webContents 对象): win.webContents.send(channel, ...args)
      • 渲染进程 (preloadrenderer): ipcRenderer.on(channel, (event, ...args) => { ... })
  4. 安全 IPC 的最佳实践 (使用 contextBridge):

    • main.js: 使用 ipcMain.handleipcMain.on 处理来自渲染进程的请求。
    • preload.js:
      const { contextBridge, ipcRenderer } = require('electron');
      
      contextBridge.exposeInMainWorld('electronAPI', {
          // 暴露一个调用主进程函数的接口
          doSomething: (data) => ipcRenderer.invoke('do-something', data),
          // 暴露一个监听主进程消息的接口
          onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
          // 需要注意移除监听器以防内存泄漏
          removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel)
      });
      
    • renderer.js:
      // 调用暴露的函数
      const result = await window.electronAPI.doSomething('some data');
      console.log(result);
      
      // 监听暴露的事件
      window.electronAPI.onUpdateCounter((value) => {
          console.log('Counter updated:', value);
      });
      
      // 在组件卸载或页面关闭时清理监听器
      // window.electronAPI.removeAllListeners('update-counter');
      

第六章:原生 UI 元素

  1. 应用程序菜单 (Menu):

    • 创建自定义的顶部应用程序菜单 (File, Edit, View 等)。
    • 创建上下文菜单 (右键菜单)。
    • 使用 Menu.buildFromTemplate(template) 创建菜单。
    • template 是一个包含菜单项对象的数组 (e.g., { label: 'File', submenu: [...] }, { label: 'Quit', role: 'quit' }, { type: 'separator' })。
    • 通过 Menu.setApplicationMenu(menu) 设置应用菜单。
    • 通过 win.webContents.on('context-menu', ...) 弹出上下文菜单 (menu.popup())。
    • role 属性可以快速创建标准菜单项 (如 undo, redo, cut, copy, paste, quit, toggledevtools)。
  2. 对话框 (dialog):

    • 显示原生的系统对话框。
    • dialog.showOpenDialogSync() / dialog.showOpenDialog(): 文件/文件夹选择框。
    • dialog.showSaveDialogSync() / dialog.showSaveDialog(): 文件保存框。
    • dialog.showMessageBoxSync() / dialog.showMessageBox(): 消息提示框 (info, warning, error, question)。
    • dialog.showErrorBox(): 显示错误信息框。
    • 注意: dialog 模块只能在主进程中使用,渲染进程需要通过 IPC 调用。
  3. 系统托盘 (Tray):

    • 在操作系统的通知区域 (系统托盘) 创建图标。
    • new Tray('/path/to/icon.png') 创建实例。
    • tray.setToolTip('Tooltip text') 设置鼠标悬停提示。
    • tray.setContextMenu(menu) 设置右键菜单。
    • 监听点击事件 (click, right-click 等)。
  4. 原生通知 (Notification):

    • 显示操作系统的原生通知。
    • new Notification({ title: 'Title', body: 'Body text' }).show()
    • 可以在主进程或支持的渲染进程中使用 (需要用户授权)。

第七章:系统集成与常用 API

  1. 访问文件系统 (Node.js fs 模块):

    • 在主进程或通过 preload 脚本安全暴露给渲染进程。
    • fs.readFile(), fs.writeFile(), fs.mkdir(), fs.readdir(), etc.
    • 配合 Node.js path 模块处理路径。
  2. shell 模块:

    • 管理文件和外部 URL。
    • shell.openExternal('https://electronjs.org'): 在默认浏览器打开链接。
    • shell.openPath('/path/to/file'): 用默认程序打开文件或目录。
    • shell.showItemInFolder('/path/to/item'): 在文件管理器中显示文件。
    • shell.trashItem('/path/to/item'): 将文件移动到回收站。
  3. 剪贴板 (clipboard):

    • 读写系统剪贴板。
    • clipboard.writeText('Example Text')
    • clipboard.readText()
    • clipboard.writeImage(nativeImage)
    • clipboard.readImage()
  4. 屏幕信息 (screen):

    • 获取屏幕尺寸、显示器信息、鼠标位置等。
    • screen.getPrimaryDisplay().workAreaSize
    • screen.getAllDisplays()
    • screen.getCursorScreenPoint()
  5. 系统主题 (nativeTheme):

    • 检测和响应操作系统的颜色主题 (亮色/暗色模式)。
    • nativeTheme.shouldUseDarkColors (boolean)
    • nativeTheme.on('updated', () => { ... }) 监听主题变化。
    • nativeTheme.themeSource = 'dark' / 'light' / 'system' 设置应用主题模式。
  6. 其他常用模块:

    • powerMonitor: 监控系统电源状态 (如进入睡眠、唤醒)。
    • globalShortcut: 注册/注销全局键盘快捷键。
    • protocol: 注册自定义协议 (myapp://...)。

第八章:安全

  1. 核心原则:最小权限原则

    • 不要给渲染进程不必要的权限。
    • 默认配置 (nodeIntegration: false, contextIsolation: true) 是最安全的起点。
  2. 关键安全设置 (webPreferences):

    • contextIsolation: true (默认): 强烈推荐。隔离 preload 脚本和渲染进程的 JavaScript 上下文,防止渲染进程直接访问 Node.js 或 Electron API。
    • nodeIntegration: false (默认): 强烈推荐。禁止在渲染进程中使用 require() 和 Node.js 全局变量。
    • sandbox: true: 启用 Chromium 沙盒,进一步限制渲染进程的能力。通常需要配合 contextBridge 和 IPC 使用。
  3. preload 脚本的重要性:

    • 作为受信任的脚本,连接隔离的渲染进程和主进程。
    • 使用 contextBridge.exposeInMainWorld 安全地暴露有限的、必要的 API 给渲染进程。不要暴露整个 ipcRenderer 或 Node.js 模块。
  4. 内容安全策略 (CSP - Content Security Policy):

    • 通过 HTTP Header (session.defaultSession.webRequest.onHeadersReceived) 或 <meta> 标签设置。
    • 限制资源加载来源 (脚本、样式、图片等),防止 XSS 攻击。
    • 例如:default-src 'self' 只允许加载同源资源。
  5. 校验 IPC 消息:

    • 不要完全信任来自渲染进程的任何数据。
    • 在主进程的 IPC 处理函数中,对接收到的参数进行严格的类型、格式和范围校验。
  6. 限制导航:

    • 监听 webContentswill-navigatenew-window 事件,阻止应用导航到非预期的外部网站或打开恶意窗口。
  7. 检查依赖项:

    • 定期更新依赖项 (npm audit),注意第三方库可能存在的安全漏洞。

第九章:打包与分发

  1. 为什么需要打包?

    • 将应用程序代码、Electron 可执行文件、Node.js 模块等捆绑成用户可以直接安装和运行的格式 (如 .exe, .dmg, .deb)。
    • 简化用户安装过程。
  2. 常用打包工具:

    • electron-builder: 功能强大,配置灵活,支持多种目标格式和自动更新。推荐使用。
    • electron-packager: 相对简单,只负责基础打包,不包含安装程序制作和自动更新。
  3. electron-builder 配置 (通常在 package.jsonbuild 字段或 electron-builder.yml 文件中):

    • appId: 应用程序的唯一标识符 (如 com.example.myapp)。
    • productName: 应用名称。
    • files: 指定需要包含在打包中的文件/目录。
    • directories: 指定输出目录 (output) 和构建资源目录 (buildResources)。
    • 特定平台配置 (win, mac, linux):
      • target: 打包的目标格式 (e.g., nsis for Windows installer, dmg for macOS, AppImage, deb, rpm for Linux)。
      • icon: 指定应用程序图标。
      • asar: 是否将应用源码打包成 asar 归档文件 (提高读取性能,隐藏源码)。
  4. 打包命令 (使用 electron-builder):

    • npm run buildyarn build (通常配置在 scripts 中,e.g., "build": "electron-builder")。
    • 可以指定平台:electron-builder --win --mac --linux
  5. 代码签名 (Code Signing):

    • 目的: 向操作系统和用户证明应用程序来源可信,未被篡改。
    • macOS: 必须进行签名和公证 (Notarization) 才能在较新系统上顺利分发。需要 Apple Developer ID 证书。
    • Windows: 推荐使用 EV 证书或标准代码签名证书进行签名,以避免 SmartScreen 警告。
    • electron-builder 支持配置签名证书。
  6. 自动更新 (electron-updater):

    • electron-builder 内置支持 electron-updater 模块。
    • 需要在主进程中集成更新逻辑 (检查更新、下载、安装)。
    • 配置 publish 选项 (如 GitHub Releases, S3 等) 来指定更新包的发布位置。

第十章:进阶主题与最佳实践

  1. 性能优化:

    • 懒加载: 按需加载模块和资源,避免启动时加载所有内容。
    • 优化 IPC: 避免频繁、大量数据的 IPC 通信。考虑合并请求,使用 invoke/handle 代替多次 send/on
    • 避免在渲染进程中执行阻塞操作: 将耗时任务 (如复杂计算、文件读写) 放到主进程或 Web Workers 中。
    • 管理窗口: 不用的窗口及时销毁 (win.close()) 而不是隐藏 (win.hide()),以释放资源。
    • 使用 V8 代码缓存: app.enableSandbox() 或通过 webPreferences 控制。
    • 分析性能: 使用 Chrome DevTools 的 Performance 和 Memory 面板。
  2. 状态管理:

    • 对于复杂应用,在多个窗口/进程间同步状态可能比较复杂。
    • 方案:
      • 将状态主要存储在主进程,通过 IPC 同步给需要的渲染进程。
      • 使用 electron-store 等库持久化简单配置。
      • 使用 Redux, Vuex, Pinia 等状态管理库,并配合 IPC 或 electron-redux, vuex-electron 等桥接库进行跨进程同步。
  3. 测试:

    • 单元测试: 使用 Jest, Mocha 等测试框架测试独立的模块和函数 (主进程、渲染进程逻辑)。
    • 端到端 (E2E) 测试: 使用 Spectron (官方维护,基于 WebDriver) 或 Playwright/Puppeteer (需要额外配置) 来模拟用户交互,测试整个应用程序的行为。
  4. 使用现代前端框架 (Vue, React, Angular):

    • 可以将 Vue/React/Angular 项目构建后的静态文件 (dist 目录) 加载到 Electron 的 BrowserWindow 中 (win.loadFile('dist/index.html'))。
    • 通常使用 Vite 或 Webpack 等构建工具。
    • 需要配置好 preload 脚本和 IPC 通信,以连接前端框架和 Electron 的原生能力。
    • 社区有模板项目 (如 electron-vite, electron-react-boilerplate) 可以快速启动。
  5. 主进程与渲染进程代码分离:

    • 保持清晰的项目结构,将主进程代码、preload 脚本、渲染进程 UI 代码分别放在不同的目录中。

这份总结覆盖了 Electron 开发的主要方面。掌握这些知识点将为构建稳定、安全、功能丰富的桌面应用打下坚实的基础。在实践中不断深入探索和学习特定 API 及最佳实践非常重要。

示例

Electron 知识点详解 (带示例)


第一章:Electron 入门与核心概念

(本章偏重概念,代码示例从第二章开始)

  1. 什么是 Electron?

    • 定义:使用 HTML, CSS, JavaScript 构建跨平台桌面应用的框架。
    • 核心:Chromium + Node.js + 自定义 APIs。
  2. 为什么选择 Electron?

    • 跨平台、Web 技术栈、快速开发、强大能力、成熟生态。
  3. 主要挑战/缺点:

    • 包体积大、内存占用、潜在性能瓶颈、安全需关注。
  4. 核心架构:主进程 (Main Process) 与渲染进程 (Renderer Process)

    • 主进程: 唯一的 Node.js 后端环境,管理窗口和系统交互。
    • 渲染进程: 每个窗口的浏览器环境,负责 UI 渲染和前端逻辑。

第二章:环境搭建与基础项目

  1. 环境要求: Node.js, npm/yarn。

  2. 创建基础项目:

    # 1. 创建目录并进入
    mkdir my-electron-app && cd my-electron-app
    # 2. 初始化 npm 项目
    npm init -y
    # 3. 安装 Electron
    npm install --save-dev electron
    # 4. 创建文件
    touch main.js index.html renderer.js
    
  3. package.json 关键配置:

    // package.json
    {
      "name": "my-electron-app",
      "version": "1.0.0",
      "description": "My First Electron App",
      "main": "main.js", // 指定主进程入口文件
      "scripts": {
        "start": "electron .", // 定义启动命令
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "Your Name",
      "license": "MIT",
      "devDependencies": {
        "electron": "^28.0.0" // 版本号可能不同
      }
    }
    
  4. main.js (主进程) 基础代码:

    // main.js
    const { app, BrowserWindow } = require('electron');
    const path = require('path');
    
    function createWindow() {
      // 创建浏览器窗口
      const mainWindow = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
          // preload: path.join(__dirname, 'preload.js') // 预加载脚本,后续章节会用到
        }
      });
    
      // 加载 index.html
      mainWindow.loadFile('index.html');
    
      // 打开开发者工具 (可选)
      mainWindow.webContents.openDevTools();
    }
    
    // Electron 会在初始化后并准备
    // 创建浏览器窗口时,调用这个函数。
    // 部分 API 在 ready 事件触发后才能使用。
    app.whenReady().then(() => {
      createWindow();
    
      // 在 macOS 上,当单击 dock 图标并且没有其他窗口打开时,
      // 通常在应用程序中重新创建一个窗口。
      app.on('activate', function () {
        if (BrowserWindow.getAllWindows().length === 0) createWindow();
      });
    });
    
    // 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 因此,通常对程序和它们在
    // 任务栏上的图标来说,应当保持活跃状态,直到用户使用 Cmd + Q 退出。
    app.on('window-all-closed', function () {
      if (process.platform !== 'darwin') app.quit();
    });
    
  5. index.html (渲染进程) 基础代码:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8">
        <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
        <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"> -->
        <title>Hello World!</title>
    </head>
    <body>
        <h1>Hello World!</h1>
        We are using Node.js <span id="node-version"></span>,
        Chromium <span id="chrome-version"></span>,
        and Electron <span id="electron-version"></span>.
    
        <script src="./renderer.js"></script>
    </body>
    </html>
    
  6. renderer.js (渲染进程) 基础代码 (演示访问 process 对象,但依赖 nodeIntegration,后续会用更安全的方式):

    // renderer.js
    // 注意:直接访问 process 等 Node.js API 需要在 BrowserWindow 中开启 nodeIntegration: true
    // 这不是推荐的安全做法,后续会通过 preload 脚本实现
    const information = document.getElementById('info');
    const nodeVersionSpan = document.getElementById('node-version');
    const chromeVersionSpan = document.getElementById('chrome-version');
    const electronVersionSpan = document.getElementById('electron-version');
    
    // 尝试获取版本信息 (如果 nodeIntegration: false, 这会报错)
    try {
        nodeVersionSpan.innerText = process.versions.node;
        chromeVersionSpan.innerText = process.versions.chrome;
        electronVersionSpan.innerText = process.versions.electron;
    } catch (error) {
        console.error("Could not access process.versions. Is nodeIntegration enabled?", error);
        nodeVersionSpan.innerText = 'N/A';
        chromeVersionSpan.innerText = 'N/A';
        electronVersionSpan.innerText = 'N/A';
    }
    
    • 重要提示: 上述 renderer.js 示例直接访问 process。为了让它工作,你需要在 main.jsBrowserWindow 配置中添加 webPreferences: { nodeIntegration: true, contextIsolation: false }但这极不安全! 我们将在第五章展示如何使用 preloadcontextBridge 安全地实现类似功能。
  7. 启动与调试:

    • 启动:npm start
    • 调试:在 main.js 中添加 mainWindow.webContents.openDevTools(); 后启动,即可在窗口中看到 Chrome 开发者工具。

第三章:主进程 (Main Process) 详解

  1. app 模块:

    • 示例:获取应用路径
      // main.js
      const { app } = require('electron');
      console.log('User Data Path:', app.getPath('userData'));
      console.log('App Path:', app.getAppPath());
      console.log('Is Packaged:', app.isPackaged); // 开发时为 false, 打包后为 true
      
    • 示例:处理退出
      // main.js
      app.on('before-quit', (event) => {
        console.log('App is about to quit...');
        // event.preventDefault(); // 可以阻止退出
      });
      
  2. BrowserWindow 模块:

    • 示例:创建无边框窗口
      // main.js (在 createWindow 函数内)
      const win = new BrowserWindow({
        width: 400,
        height: 300,
        frame: false, // 移除窗口边框和标题栏
        webPreferences: { /* ... */ }
      });
      
    • 示例:窗口加载完成后显示 (避免白屏)
      // main.js (在 createWindow 函数内)
      const win = new BrowserWindow({
        show: false, // 先不显示
        width: 800,
        height: 600,
        webPreferences: { /* ... */ }
      });
      win.loadFile('index.html');
      win.once('ready-to-show', () => {
        win.show(); // 页面加载好后再显示
      });
      
  3. webPreferences 选项 (关键配置):

    • 示例:配置 preload 脚本 (安全)
      // main.js
      const path = require('path');
      // ...
      const mainWindow = new BrowserWindow({
          width: 800,
          height: 600,
          webPreferences: {
              // --- 安全推荐设置 ---
              nodeIntegration: false, // 禁用 Node.js 集成 (渲染进程)
              contextIsolation: true, // 开启上下文隔离
              preload: path.join(__dirname, 'preload.js') // 指定预加载脚本
              // --------------------
              // sandbox: true, // 更严格的沙盒,需要更多 IPC 配置
          }
      });
      
      • preload.js 的内容将在下一章展示。

第四章:渲染进程 (Renderer Process) 详解

  1. 角色: UI 展示与交互。

  2. 访问 Node.js (推荐方式:preload + contextBridge)

  3. preload.js 脚本:

    • 示例:使用 contextBridge 暴露 API (安全)
      // preload.js
      const { contextBridge, ipcRenderer } = require('electron');
      const os = require('os'); // preload 可以访问 Node.js 模块
      
      contextBridge.exposeInMainWorld('electronAPI', {
          // 暴露一个同步获取信息的接口 (虽然不推荐同步,但可演示)
          getPlatform: () => os.platform(),
          // 暴露一个调用主进程函数的接口 (异步)
          setTitle: (title) => ipcRenderer.send('set-title', title),
          // 暴露一个双向通信的接口 (异步)
          openFile: () => ipcRenderer.invoke('dialog:openFile'),
          // 暴露一个监听主进程消息的接口
          onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
          // 移除监听器的方法
          removeUpdateCounterListener: () => ipcRenderer.removeAllListeners('update-counter')
      });
      
      // 也可以直接在 preload 中操作 DOM,但不推荐,应由 renderer.js 负责
      // window.addEventListener('DOMContentLoaded', () => { ... });
      
      • exposeInMainWorld 的第一个参数 'electronAPI' 是暴露到 window 对象下的键名。
  4. renderer.js (渲染进程脚本):

    • 示例:调用 preload 暴露的 API
      // renderer.js
      
      // 调用同步方法
      const platformSpan = document.createElement('p');
      platformSpan.textContent = `Platform: ${window.electronAPI.getPlatform()}`;
      document.body.appendChild(platformSpan);
      
      // 调用单向 IPC
      const titleButton = document.createElement('button');
      titleButton.textContent = 'Set Window Title to "My App"';
      titleButton.onclick = () => {
          window.electronAPI.setTitle('My App');
      };
      document.body.appendChild(titleButton);
      
      // 调用双向 IPC
      const openFileButton = document.createElement('button');
      openFileButton.textContent = 'Open File Dialog';
      openFileButton.onclick = async () => {
          const filePath = await window.electronAPI.openFile();
          const filePathP = document.createElement('p');
          filePathP.textContent = filePath ? `Selected: ${filePath}` : 'No file selected.';
          document.body.appendChild(filePathP);
      };
      document.body.appendChild(openFileButton);
      
      // 监听来自主进程的消息
      const counterP = document.createElement('p');
      counterP.textContent = 'Counter: 0';
      document.body.appendChild(counterP);
      window.electronAPI.onUpdateCounter((value) => {
          counterP.textContent = `Counter: ${value}`;
      });
      
      // 注意:在页面/组件卸载时,应调用 removeUpdateCounterListener 清理监听
      // window.onbeforeunload = () => {
      //   window.electronAPI.removeUpdateCounterListener();
      // };
      

第五章:进程间通信 (Inter-Process Communication - IPC)

  1. 为什么需要 IPC? 隔离的进程间传递消息。

  2. 主要模块: ipcMain, ipcRenderer, contextBridge

  3. 通信模式示例 (配合上一章的 preload.jsrenderer.js)

    • 渲染进程 -> 主进程 (单向): (setTitle)

      • renderer.js: window.electronAPI.setTitle('New Title') (通过 preload 调用 ipcRenderer.send)
      • main.js:
        const { app, BrowserWindow, ipcMain } = require('electron');
        // ... 在 createWindow 后 ...
        ipcMain.on('set-title', (event, title) => {
          const webContents = event.sender;
          const win = BrowserWindow.fromWebContents(webContents);
          if (win) {
            win.setTitle(title);
          }
        });
        
    • 渲染进程 -> 主进程 -> 渲染进程 (双向异步): (openFile)

      • renderer.js: const filePath = await window.electronAPI.openFile() (通过 preload 调用 ipcRenderer.invoke)
      • main.js:
        const { app, BrowserWindow, ipcMain, dialog } = require('electron');
        // ...
        ipcMain.handle('dialog:openFile', async () => {
          const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openFile'] });
          if (!canceled && filePaths.length > 0) {
            return filePaths[0];
          }
          return null; // 或者 undefined
        });
        
    • 主进程 -> 渲染进程 (单向): (update-counter)

      • main.js (示例:每秒发送一次计数器):
        // 需要 mainWindow 实例
        let counter = 0;
        setInterval(() => {
          // 确保窗口还存在
          if (mainWindow && !mainWindow.isDestroyed()) {
              mainWindow.webContents.send('update-counter', counter++);
          }
        }, 1000);
        
        (注意: 上述代码需要将 mainWindow 变量提升到 setInterval 可访问的作用域)
      • renderer.js (通过 preload 的 onUpdateCounter 监听):
        window.electronAPI.onUpdateCounter((value) => {
          console.log('Received counter from main:', value);
          // 更新 UI...
        });
        
  4. 安全 IPC 的最佳实践: 始终使用 contextBridge,如上例所示。避免直接暴露 ipcRenderer


第六章:原生 UI 元素

  1. 应用程序菜单 (Menu):

    • 示例:创建简单的应用菜单 (macOS & Windows/Linux)
      // main.js
      const { app, Menu, shell } = require('electron');
      
      const isMac = process.platform === 'darwin';
      
      const template = [
        // { role: 'appMenu' } 或者 app.getName()
        ...(isMac ? [{
          label: app.getName(),
          submenu: [
            { role: 'about' },
            { type: 'separator' },
            { role: 'services' },
            { type: 'separator' },
            { role: 'hide' },
            { role: 'hideOthers' },
            { role: 'unhide' },
            { type: 'separator' },
            { role: 'quit' }
          ]
        }] : []),
        // { role: 'fileMenu' }
        {
          label: 'File',
          submenu: [
            {
              label: 'New Window',
              accelerator: 'CmdOrCtrl+N',
              click: () => { /* 调用 createWindow() */ }
            },
            isMac ? { role: 'close' } : { role: 'quit' }
          ]
        },
        // { role: 'editMenu' }
        {
          label: 'Edit',
          submenu: [
            { role: 'undo' },
            { role: 'redo' },
            { type: 'separator' },
            { role: 'cut' },
            { role: 'copy' },
            { role: 'paste' },
            ...(isMac ? [
              { role: 'pasteAndMatchStyle' },
              { role: 'delete' },
              { role: 'selectAll' },
              { type: 'separator' },
              {
                label: 'Speech',
                submenu: [
                  { role: 'startSpeaking' },
                  { role: 'stopSpeaking' }
                ]
              }
            ] : [
              { role: 'delete' },
              { type: 'separator' },
              { role: 'selectAll' }
            ])
          ]
        },
        // { role: 'viewMenu' }
        {
          label: 'View',
          submenu: [
            { role: 'reload' },
            { role: 'forceReload' },
            { role: 'toggleDevTools' },
            { type: 'separator' },
            { role: 'resetZoom' },
            { role: 'zoomIn' },
            { role: 'zoomOut' },
            { type: 'separator' },
            { role: 'togglefullscreen' }
          ]
        },
        // { role: 'windowMenu' }
        {
          label: 'Window',
          submenu: [
            { role: 'minimize' },
            { role: 'zoom' },
            ...(isMac ? [
              { type: 'separator' },
              { role: 'front' },
              { type: 'separator' },
              { role: 'window' }
            ] : [
              { role: 'close' }
            ])
          ]
        },
        {
          role: 'help',
          submenu: [
            {
              label: 'Learn More',
              click: async () => {
                await shell.openExternal('https://electronjs.org');
              }
            }
          ]
        }
      ];
      
      const menu = Menu.buildFromTemplate(template);
      Menu.setApplicationMenu(menu); // 设置应用菜单
      
      // 也可以创建上下文菜单
      // const contextMenu = Menu.buildFromTemplate([...]);
      // window.webContents.on('context-menu', (e, params) => {
      //   contextMenu.popup(window);
      // });
      
  2. 对话框 (dialog):

    • 示例:显示消息框 (主进程或通过 IPC 调用)
      // main.js (或在 ipcMain.handle 中)
      const { dialog } = require('electron');
      
      async function showInfoMessage() {
        await dialog.showMessageBox({
          type: 'info', // 'none', 'info', 'error', 'question', 'warning'
          title: 'Information',
          message: 'This is an informational message.',
          detail: 'Some extra details here.',
          buttons: ['OK', 'Cancel'] // 返回点击按钮的索引 (0 or 1)
        });
      }
      // 调用 showInfoMessage()
      
    • 示例:显示打开文件对话框 (已在 IPC 示例中)
  3. 系统托盘 (Tray):

    • 示例:创建简单的系统托盘图标
      // main.js
      const { app, Tray, Menu, nativeImage } = require('electron');
      const path = require('path');
      
      let tray = null; // 需要持有引用,否则会被垃圾回收
      
      app.whenReady().then(() => {
        // 需要一个图标文件 (e.g., icon.png in project root)
        // 推荐使用 16x16 或 32x32 的 .png 或 .ico
        const iconPath = path.join(__dirname, 'icon.png'); // 替换为你的图标路径
        const icon = nativeImage.createFromPath(iconPath);
        tray = new Tray(icon);
      
        const contextMenu = Menu.buildFromTemplate([
          { label: 'Show App', click: () => { /* 显示窗口逻辑 */ } },
          { label: 'Quit', click: () => { app.quit(); } }
        ]);
      
        tray.setToolTip('My Electron App');
        tray.setContextMenu(contextMenu);
      
        tray.on('click', () => {
           // 点击托盘图标的操作,例如显示/隐藏窗口
           // mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
        });
      });
      
  4. 原生通知 (Notification):

    • 示例:显示一个简单的通知
      // main.js (或在渲染进程中,但需检查支持性)
      const { Notification } = require('electron');
      
      function showNotification() {
        if (Notification.isSupported()) { // 检查系统是否支持
          const notification = new Notification({
            title: 'Hello!',
            body: 'This is a notification from Electron.',
            // icon: path.join(__dirname, 'icon.png') // 可选图标
          });
          notification.show();
      
          notification.on('click', () => {
            console.log('Notification clicked!');
            // 可以添加点击后的操作,如聚焦窗口
          });
        } else {
          console.log('Notifications not supported on this system.');
        }
      }
      // 调用 showNotification()
      

第七章:系统集成与常用 API

  1. 访问文件系统 (Node.js fs 模块):

    • 示例:通过 preload 安全暴露读取文件功能
      • preload.js:
        const { contextBridge, ipcRenderer } = require('electron');
        contextBridge.exposeInMainWorld('electronAPI', {
          // ... 其他 API ...
          readFile: (filePath) => ipcRenderer.invoke('fs:readFile', filePath)
        });
        
      • main.js:
        const { ipcMain } = require('electron');
        const fs = require('fs').promises; // 使用 promise 版本
        
        ipcMain.handle('fs:readFile', async (event, filePath) => {
          try {
            // !! 安全警告:实际应用中必须严格校验 filePath !!
            // 防止路径遍历攻击等,例如限制在特定目录下
            console.log(`Reading file requested by renderer: ${filePath}`);
            const data = await fs.readFile(filePath, 'utf-8');
            return { success: true, data: data };
          } catch (error) {
            console.error('Error reading file:', error);
            return { success: false, error: error.message };
          }
        });
        
      • renderer.js:
        async function readMyFile() {
          // 需要用户选择文件或指定安全路径
          const result = await window.electronAPI.readFile('path/to/your/file.txt');
          if (result.success) {
            console.log('File content:', result.data);
          } else {
            console.error('Failed to read file:', result.error);
          }
        }
        
  2. shell 模块:

    • 示例:打开外部链接
      // main.js 或 preload.js (暴露给渲染进程)
      const { shell } = require('electron');
      // shell.openExternal('https://www.google.com');
      // --- 通过 preload 暴露 ---
      // preload.js
      contextBridge.exposeInMainWorld('electronAPI', {
        // ...
        openExternal: (url) => shell.openExternal(url) // 注意安全,校验 URL
      });
      // renderer.js
      // window.electronAPI.openExternal('https://electronjs.org');
      
  3. 剪贴板 (clipboard):

    • 示例:读写文本
      • preload.js:
        const { contextBridge, clipboard } = require('electron');
        contextBridge.exposeInMainWorld('clipboardAPI', {
          writeText: (text) => clipboard.writeText(text),
          readText: () => clipboard.readText()
        });
        
      • renderer.js:
        async function testClipboard() {
          await window.clipboardAPI.writeText('Copied from Electron!');
          const text = await window.clipboardAPI.readText();
          console.log('Clipboard content:', text);
        }
        // 调用 testClipboard()
        
  4. 屏幕信息 (screen):

    • 示例:获取主显示器尺寸
      • preload.js:
        const { contextBridge, screen } = require('electron');
        contextBridge.exposeInMainWorld('electronAPI', {
          // ...
          getPrimaryDisplaySize: () => screen.getPrimaryDisplay().workAreaSize
        });
        
      • renderer.js:
        const size = window.electronAPI.getPrimaryDisplaySize();
        console.log(`Primary display work area: ${size.width}x${size.height}`);
        
  5. 系统主题 (nativeTheme):

    • 示例:检测并响应暗色模式
      • preload.js:
        const { contextBridge, ipcRenderer } = require('electron');
        contextBridge.exposeInMainWorld('electronAPI', {
           // ...
           isDarkMode: () => ipcRenderer.invoke('nativeTheme:isDarkMode'),
           onThemeUpdate: (callback) => ipcRenderer.on('theme-updated', () => callback())
        });
        
      • main.js:
        const { nativeTheme, ipcMain } = require('electron');
        ipcMain.handle('nativeTheme:isDarkMode', () => nativeTheme.shouldUseDarkColors);
        // 监听主题变化并通知渲染进程
        nativeTheme.on('updated', () => {
          // 通知所有窗口
          BrowserWindow.getAllWindows().forEach(win => {
            if(win && !win.isDestroyed()) {
              win.webContents.send('theme-updated');
            }
          });
        });
        
      • renderer.js:
        async function checkTheme() {
          const isDark = await window.electronAPI.isDarkMode();
          document.body.classList.toggle('dark-mode', isDark);
          console.log(`Current theme is ${isDark ? 'dark' : 'light'}`);
        }
        checkTheme(); // Initial check
        window.electronAPI.onThemeUpdate(() => {
          console.log('Theme updated!');
          checkTheme(); // Re-check on update
          // 更新 UI ...
        });
        
        (你需要在 CSS 中定义 .dark-mode 样式)

第八章:安全

  1. 核心原则: 最小权限。

  2. 关键安全设置 (webPreferences): 见第三章示例 (nodeIntegration: false, contextIsolation: true).

  3. preload 脚本与 contextBridge: 这是现代 Electron 安全的核心。 见第四、五章示例。永远不要preload 中这样写:window.ipcRenderer = require('electron').ipcRenderer;

  4. 内容安全策略 (CSP):

    • 示例:在 HTML 中设置
      <!-- index.html -->
      <head>
        <meta charset="UTF-8">
        <!-- 只允许加载同源资源 (最严格) -->
        <!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'"> -->
        <!-- 允许同源脚本,允许 data: 图像,允许特定域的样式 -->
        <meta http-equiv="Content-Security-Policy" content="script-src 'self'; img-src 'self' data:; style-src 'self' https://trusted.cdn.com; default-src 'self'">
        <title>Secure App</title>
      </head>
      
  5. 校验 IPC 消息:

    • 示例:在 ipcMain.handle 中校验
      // main.js
      ipcMain.handle('process-data', (event, input) => {
        // 假设 input 应该是一个包含 name 和 age 的对象
        if (typeof input !== 'object' || input === null) {
          throw new Error('Invalid input type: expected object.');
        }
        if (typeof input.name !== 'string' || input.name.length === 0) {
           throw new Error('Invalid input: name must be a non-empty string.');
        }
        if (typeof input.age !== 'number' || input.age < 0 || input.age > 150) {
           throw new Error('Invalid input: age must be a number between 0 and 150.');
        }
        // ... 处理校验通过的数据 ...
        console.log(`Processing valid data for ${input.name}`);
        return { success: true, message: `Processed ${input.name}` };
      });
      
  6. 限制导航:

    • 示例:阻止导航到外部网站
      // main.js (在 createWindow 内,获取 webContents 后)
      mainWindow.webContents.on('will-navigate', (event, url) => {
        const parsedUrl = new URL(url);
        // 允许 file:// 协议或特定安全域
        if (parsedUrl.protocol !== 'file:' /* && parsedUrl.hostname !== 'trusted.com' */) {
          console.warn(`Blocked navigation to: ${url}`);
          event.preventDefault(); // 阻止导航
          shell.openExternal(url); // 可选:在外部浏览器打开
        }
      });
      
  7. 检查依赖项: npm audit


第九章:打包与分发

  1. 为什么需要打包? 创建可执行文件。

  2. 常用打包工具: electron-builder (推荐), electron-packager

  3. electron-builder 配置 (示例 package.json):

    // package.json
    {
      // ... 其他配置 ...
      "scripts": {
        "start": "electron .",
        "pack": "electron-builder --dir", // 打包成未压缩目录 (测试用)
        "dist": "electron-builder" // 打包成分发格式 (exe, dmg 等)
      },
      "build": {
        "appId": "com.example.myelectronapp",
        "productName": "MyElectronApp",
        "files": [
          "main.js",
          "preload.js",
          "index.html",
          "renderer.js",
          "node_modules/**/*", // 通常 builder 会自动处理
          "assets/", // 包含你的静态资源
          "!node_modules/**/{test,tests,spec,specs,example,examples,.bin}/**/*" // 排除不必要的文件
        ],
        "directories": {
          "output": "dist", // 打包输出目录
          "buildResources": "build" // 构建资源目录 (如图标)
        },
        "win": {
          "target": "nsis", // NSIS 安装程序
          "icon": "build/icon.ico" // Windows 图标
        },
        "mac": {
          "target": "dmg", // DMG 镜像
          "icon": "build/icon.icns", // macOS 图标
          "category": "public.app-category.utilities" // App Store 分类
        },
        "linux": {
          "target": [
            "AppImage",
            "deb"
          ],
          "icon": "build/icon.png", // Linux 图标
          "category": "Utility"
        },
        "nsis": { // NSIS 安装程序特定配置
          "oneClick": false, // 非静默安装
          "allowToChangeInstallationDirectory": true
        },
        "asar": true // 将应用代码打包到 asar 存档中
      },
      "devDependencies": {
        "electron": "^28.0.0",
        "electron-builder": "^24.9.1" // 添加 electron-builder
      }
    }
    
    • 注意: 你需要创建 build 文件夹并放入相应格式的图标文件 (icon.ico, icon.icns, icon.png)。
  4. 打包命令:

    • npm run distyarn dist
  5. 代码签名: 需要平台特定的证书,并在 electron-builder 配置中指定 (参考其文档)。

  6. 自动更新 (electron-updater):

    • 示例:主进程检查更新
      // main.js
      const { autoUpdater } = require('electron-updater');
      const { dialog } = require('electron');
      
      // 配置 autoUpdater (通常会自动读取 build.publish 配置)
      // autoUpdater.setFeedURL({ provider: 'github', owner: 'your-gh-username', repo: 'your-repo' });
      
      function checkForUpdates() {
          // 在应用启动后或菜单项点击时调用
          autoUpdater.checkForUpdatesAndNotify().catch(err => {
              console.error('Update check failed:', err);
          });
      }
      
      // 监听更新事件
      autoUpdater.on('update-available', () => {
          dialog.showMessageBox({
              type: 'info',
              title: 'Update Available',
              message: 'A new version is available. Do you want to download and install it now?',
              buttons: ['Yes', 'Later']
          }).then(result => {
              if (result.response === 0) { // 'Yes' button
                  autoUpdater.downloadUpdate();
              }
          });
      });
      
      autoUpdater.on('update-downloaded', () => {
          dialog.showMessageBox({
              type: 'info',
              title: 'Update Ready',
              message: 'Update downloaded. The application will now quit to install...',
              buttons: ['OK']
          }).then(() => {
              setImmediate(() => autoUpdater.quitAndInstall());
          });
      });
      
      autoUpdater.on('error', (error) => {
        dialog.showErrorBox('Update Error', error == null ? "unknown" : (error.stack || error).toString());
      });
      
      // 在 app ready 后调用检查更新
      app.whenReady().then(() => {
          // ... createWindow ...
          if (app.isPackaged) { // 只在打包后检查更新
            checkForUpdates();
          }
      });
      

第十章:进阶主题与最佳实践

  1. 性能优化:

    • 懒加载示例 (主进程动态 import):
      // main.js
      ipcMain.handle('load-heavy-module', async () => {
        const heavyModule = await import('./heavy-module.js'); // 动态导入
        return heavyModule.doWork();
      });
      
    • 避免阻塞操作:fs.readFileSync 替换为 fs.readFile (异步)。
  2. 状态管理: 使用 Redux/Vuex/Pinia 等,配合 electron-store 或自定义 IPC 同步机制。

  3. 测试 (Spectron E2E 示例概念):

    // test/spec.js (概念性)
    const Application = require('spectron').Application;
    const assert = require('assert');
    const electronPath = require('electron'); // 获取 Electron 可执行文件路径
    const path = require('path');
    
    describe('Application launch', function () {
      this.timeout(10000); // 增加超时
      let app;
    
      beforeEach(function () {
        app = new Application({
          path: electronPath,
          args: [path.join(__dirname, '..')] // 指向你的 app 根目录
        });
        return app.start();
      });
    
      afterEach(function () {
        if (app && app.isRunning()) {
          return app.stop();
        }
      });
    
      it('shows an initial window', async function () {
        const count = await app.client.getWindowCount();
        assert.strictEqual(count, 1);
      });
    
      it('should have the correct title', async function () {
         const title = await app.client.getTitle();
         assert.strictEqual(title, 'Hello World!'); // 或你的初始标题
      });
    
      // ... 更多测试,如点击按钮、检查文本等
    });
    
  4. 使用现代前端框架 (Vue/React/Angular):

    • 示例:加载 Vite 构建的 Vue 应用
      • 用 Vite 创建 Vue 项目: npm create vite@latest my-vue-app --template vue-ts
      • 构建 Vue 项目: cd my-vue-app && npm install && npm run build (会生成 dist 目录)
      • main.js:
        const { app, BrowserWindow } = require('electron');
        const path = require('path');
        
        function createWindow() {
          const mainWindow = new BrowserWindow({ /* ... webPreferences ... */ });
        
          if (app.isPackaged) {
            // 打包后加载构建的 index.html
            mainWindow.loadFile(path.join(__dirname, '../renderer/index.html')); // 假设 dist 目录被复制到打包后的 renderer 目录
          } else {
            // 开发时加载 Vite 开发服务器
            mainWindow.loadURL('http://localhost:5173'); // Vite 默认端口
            mainWindow.webContents.openDevTools();
          }
        }
        // ... app lifecycle ...
        
      • 你需要调整打包配置 (electron-builder),将 Vue 构建的 dist 目录包含进去,并可能调整 loadFile 的路径。使用 electron-vite 模板可以简化这个过程。
  5. 主进程与渲染进程代码分离:

    • 项目结构示例:
      my-electron-app/
      ├── build/             # 图标等构建资源
      ├── dist/              # electron-builder 输出目录
      ├── node_modules/
      ├── src/
      │   ├── main/          # 主进程代码
      │   │   ├── main.js    # 主入口
      │   │   └── modules/   # 主进程其他模块
      │   ├── preload/       # Preload 脚本
      │   │   └── preload.js
      │   └── renderer/      # 渲染进程代码 (UI)
      │       ├── index.html
      │       ├── renderer.js
      │       └── style.css
      ├── package.json
      └── ... 其他配置文件 ...
      

这些示例应该能让你更具体地理解 Electron 的各个核心概念和常用功能。记住,安全和性能是 Electron 开发中需要持续关注的重要方面。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐