electron基础知识

1. 定义

Electron = 前端项目 + Chromium 浏览器(谷歌浏览器开源版本),封装了丰富的系统级 API。


2. 进程模型

Electron 采用多进程架构:

  • 主进程:管理应用生命周期和窗口创建,可访问 Node.js API 和系统资源
  • 渲染进程:每个窗口/页面独立运行,负责 UI 渲染和用户交互
  • 预加载脚本:安全桥接层,在渲染进程启动前运行,可访问 Node.js API 并通过 contextBridge 向渲染进程暴露接口
  • 效率进程:通过 UtilityProcess 创建的子进程,用于执行不稳定或耗时的任务

3. IPC 进程间通信

Electron 提供三种 IPC 通信模式:

3.1 单向通信:渲染进程 → 主进程

适用于"发送即忘"的操作,如更新窗口标题。

API 组合ipcRenderer.send + ipcMain.on

image.png

3.1.1 主进程 - main.js

const { app, BrowserWindow, ipcMain } = require("electron");
const path = require("path");

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js"),
    },
  });

  win.loadFile("index.html");
  win.webContents.openDevTools(); // 开发时打开调试工具
  return win;
};

app.on("ready", () => {
  // 监听 set-title 事件(渲染进程 → 主进程)
  ipcMain.on("set-title", (event, title) => {
    const webContents = event.sender;
    const win = BrowserWindow.fromWebContents(webContents);
    win.setTitle(title);
  });

  createWindow();
});

3.1.2 预加载脚本 - preload.js

const { contextBridge, ipcRenderer } = require("electron");

// 暴露版本信息到渲染进程
contextBridge.exposeInMainWorld("versions", {
  node: process.versions.node,
  chrome: process.versions.chrome,
  electron: process.versions.electron,
});

// 暴露 IPC 通信接口
contextBridge.exposeInMainWorld("electron", {
  setTitle: (title) => ipcRenderer.send("set-title", title),
});

3.1.3 渲染进程 - index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <h1>Hello World</h1>
  <h1 id="info">info:</h1>
  <input type="text" id="titleInput"/>
  <button id="btn">修改标题</button>
  <script src="./renderer.js"></script>
</body>
</html>

3.1.4 渲染进程 - renderer.js

const info = document.getElementById("info");
info.innerHTML = `Chrome (v${window.versions.chrome}), Node.js (v${window.versions.node}), Electron (v${window.versions.electron})`;

const btn = document.getElementById("btn");
const titleInput = document.getElementById("titleInput");
btn.addEventListener("click", () => {
  const title = titleInput.value;
  window.electron.setTitle(title);
});

3.2 双向通信:渲染进程 ↔ 主进程

适用于需要等待返回结果的异步操作,如文件读写。

API 组合ipcRenderer.invoke + ipcMain.handle

image.png

3.2.1 修改 index.html

<body>
  <h1>Hello World</h1>
  <h1 id="info">info:</h1>
  <input type="text" id="titleInput"/>
  <button id="btn">修改标题</button>

  文件内容:<input id="content"/>
  <button id="btnFile" type="button">写入文件</button>
  <div id="fileInfo"></div>
</body>

3.2.2 修改 preload.js

contextBridge.exposeInMainWorld("electron", {
  setTitle: (title) => ipcRenderer.send("set-title", title),
  // 双向通信:返回 Promise
  writeFile: (content) => ipcRenderer.invoke("write-file", content),
});

3.2.3 修改 renderer.js

const btnFile = document.getElementById("btnFile");
const contentInput = document.getElementById("content");
btnFile.addEventListener("click", async () => {
  const content = contentInput.value;
  const len = await window.electron.writeFile(content);
  const fileInfo = document.getElementById("fileInfo");
  fileInfo.innerHTML = `文件写入成功,文件大小为 ${len} 字节`;
});

3.2.4 修改 main.js

const fs = require("fs");

app.on("ready", () => {
  // ... 其他代码

  // 处理 write-file 事件,返回值会作为 Promise 返回给渲染进程
  ipcMain.handle("write-file", async (event, content) => {
    await fs.promises.writeFile("test.txt", content);
    const stats = await fs.promises.stat("test.txt");
    return stats.size;
  });

  createWindow();
});

3.3 单向通信:主进程 → 渲染进程

适用于主进程主动推送数据,如定时更新、系统通知。

API 组合webContents.send + ipcRenderer.on

image.png

3.3.1 修改 index.html

<body>
  <h1>Hello World</h1>
  <h1 id="info">info:</h1>
  <input type="text" id="titleInput"/>
  <button id="btn">修改标题</button>
  文件内容:<input id="content"/>
  <button id="btnFile" type="button">写入文件</button>
  <div id="fileInfo"></div>
  <h1 id="counter"></h1>
</body>

3.3.2 修改 preload.js

contextBridge.exposeInMainWorld("electron", {
  setTitle: (title) => ipcRenderer.send("set-title", title),
  writeFile: (content) => ipcRenderer.invoke("write-file", content),
  // 主进程 → 渲染进程:注册监听器
  onUpdateCounter: (callback) =>
    ipcRenderer.on("update-counter", (event, value) => {
      callback(value);
    }),
});

3.3.3 修改 renderer.js

// 监听主进程推送的计数器更新
const counter = document.getElementById("counter");
window.electron.onUpdateCounter((value) => {
  counter.innerText = `counter:${value}`;
});

3.3.4 修改 main.js

app.on("ready", () => {
  // ... 其他代码

  const mainWindow = createWindow();
  let counter = 1;

  // 等待页面加载完成后再发送消息
  mainWindow.webContents.on("did-finish-load", () => {
    mainWindow.webContents.send("update-counter", counter);
    // 每 3 秒更新一次计数器
    setInterval(() => {
      counter += 3;
      mainWindow.webContents.send("update-counter", counter);
    }, 3000);
  });
});

3.3.5 关键要点

  1. 发送消息:使用 win.webContents.send(channel, data)
  2. 接收消息:在 preload.js 中用 ipcRenderer.on(channel, callback) 接收并暴露给渲染进程
  3. 生命周期:必须等待 did-finish-load 事件,确保渲染进程已准备好
  4. 安全实践:通过 preload 脚本暴露接口,避免直接在渲染进程访问 ipcRenderer

3.3.6 常见页面生命周期事件

// 页面开始加载
mainWindow.webContents.on('did-start-loading', () => {});

// 开始导航
mainWindow.webContents.on('did-start-navigation', () => {});

// DOM 准备就绪(类似 DOMContentLoaded)
mainWindow.webContents.on('dom-ready', () => {});

// 页面完全加载完成(推荐)
mainWindow.webContents.on('did-finish-load', () => {
  // 此时可安全发送消息到渲染进程
});

// 停止加载
mainWindow.webContents.on('did-stop-loading', () => {});

3.4 IPC 通信总结

通信方向 渲染进程 API 主进程 API 使用场景
渲染 → 主 ipcRenderer.send ipcMain.on 单向通知(不需要返回值)
渲染 ↔ 主 ipcRenderer.invoke ipcMain.handle 请求-响应(需要返回值)
主 → 渲染 ipcRenderer.on webContents.send 主进程推送数据

4. 安全机制:沙盒模式

Electron 通过沙盒模式限制渲染进程对系统资源的访问,以减少安全风险。沙盒化应用于主进程以外的大多数进程,包括渲染进程和功能性进程(如音频服务、GPU 服务、网络服务)。

核心原则: 渲染进程在受限环境中运行,需要额外权限的操作通过 IPC 委托给主进程执行。

默认安全状态: 现代 Electron(20+)默认启用了完整的沙盒安全配置(nodeIntegration: falsecontextIsolation: truesandbox: truewebSecurity: true),开发者通常无需手动配置这些选项。以下配置项说明仅供理解安全机制使用。

正确的开发模式: 在默认的沙盒模式(sandbox: true)下,预加载脚本和渲染进程都无法直接访问 Node.js API。正常的交互逻辑应该通过以下方式实现:

  1. 预加载脚本(preload.js):在沙盒模式下只能访问 Electron 渲染进程 API(如 ipcRenderercontextBridge),不能访问 Node.js API
  2. IPC 通信:预加载脚本通过 contextBridge 向渲染进程暴露安全的 API 接口
  3. 主进程处理:需要系统级权限的操作(如文件读写、系统调用)在主进程中执行,通过 IPC 返回结果

这种模式确保了应用的安全性,同时提供了必要的系统功能访问能力。

比如,在preload.js,修改如下:

const { contextBridge, ipcRenderer } = require("electron");
const fs = require("fs");
console.log(fs);
......

运行会报错:


image.png

4.1 关键安全配置项

沙盒模式由多个配置项共同控制,默认配置已经是安全的,通常不需要修改:

4.1.1 nodeIntegration(Node.js 集成)

控制渲染进程是否可以直接访问 Node.js API。

安全配置(推荐)

const win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: false,  // ✅ 渲染进程无法直接使用 Node.js API
    preload: path.join(__dirname, "preload.js")
  }
});
  • 渲染进程无法直接访问 requirefs 等 Node.js API
  • 必须通过 preload 脚本的 contextBridge 暴露接口
  • 适用于生产环境

不安全配置(不推荐)

const win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true,   // ❌ 渲染进程可直接使用 Node.js
    contextIsolation: false  // 必须同时关闭
  }
});
  • 渲染进程可直接使用 require('fs')require('path')
  • 无需 preload 脚本,但存在严重安全风险
  • 如果加载不受信任的远程内容,可能导致任意代码执行
// 示例:渲染进程直接使用 Node.js(不安全)
const fs = require('fs');
fs.readFileSync('file.txt', 'utf-8');

4.1.2 contextIsolation(上下文隔离)

隔离 preload 脚本和渲染进程的 JavaScript 上下文。

const win = new BrowserWindow({
  webPreferences: {
    contextIsolation: true,  // ✅ 默认值,preload 和渲染进程上下文隔离
  }
});
  • true(默认):preload 脚本和网页代码运行在不同的上下文,必须通过 contextBridge 通信
  • false:共享全局对象,网页可直接访问 preload 中的变量(不安全)

4.1.3 sandbox(系统级沙盒)

启用操作系统级别的沙盒保护。

const win = new BrowserWindow({
  webPreferences: {
    sandbox: true,  // ✅ 启用系统级沙盒
  }
});
  • 限制渲染进程的系统调用能力
  • 即使渲染进程被攻破,也难以影响操作系统

4.1.4 webSecurity(Web 安全策略)

控制是否启用同源策略等 Web 安全机制。

const win = new BrowserWindow({
  webPreferences: {
    webSecurity: true,  // ✅ 默认值,启用同源策略
  }
});

4.2 完整的安全配置示例

const { app, BrowserWindow } = require('electron');
const path = require('path');

const createSecureWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 核心安全配置
      nodeIntegration: false,      // 禁止渲染进程直接访问 Node.js
      contextIsolation: true,      // 隔离 preload 和渲染进程上下文
      sandbox: true,               // 启用操作系统级沙盒
      webSecurity: true,           // 启用 Web 安全策略

      // 安全地暴露 API
      preload: path.join(__dirname, 'preload.js')
    }
  });

  win.loadFile('index.html');
  // 或加载远程内容
  // win.loadURL('https://example.com');

  return win;
};

app.on('ready', createSecureWindow);

4.3 配置项总结

配置项 默认值 作用 建议
nodeIntegration false 控制渲染进程是否可访问 Node.js API 保持 false
contextIsolation true 隔离 preload 和渲染进程上下文 保持 true
sandbox true (Electron 20+) 启用操作系统级沙盒 保持 true
webSecurity true 启用同源策略等 Web 安全机制 保持 true

4.4 沙盒模式 vs iframe 沙盒

4.4.1 相似之处

Electron 沙盒和 Web iframe 沙盒都涉及内容隔离权限控制

对比维度 Electron 渲染进程(沙盒) Web iframe(沙盒)
隔离机制 进程级隔离 同源策略 + 沙盒属性
权限限制 限制访问 Node.js API 和系统资源 限制表单提交、脚本执行、弹窗等
通信方式 IPC(进程间通信) postMessage(窗口间通信)
安全目的 防止恶意代码访问系统 防止恶意代码访问父页面

4.4.2 关键区别

隔离级别不同

// Electron:进程级隔离
// 渲染进程和主进程是独立的操作系统进程,崩溃互不影响
const win = new BrowserWindow({
  webPreferences: { sandbox: true }  // 操作系统级别的沙盒
});
<!-- Web iframe:浏览器沙盒 -->
<!-- 仍在同一个浏览器进程内,只是限制了部分功能 -->
<iframe src="content.html" sandbox="allow-scripts"></iframe>

权限范围不同

  • Electron 沙盒:主要限制 Node.js API 和系统资源访问(文件系统、子进程等)
  • iframe 沙盒:限制 Web API(表单提交、弹窗、同源访问、脚本执行等)

通信机制不同

// Electron IPC:跨进程通信,可传递复杂数据
// 渲染进程
window.electron.readFile('/path/to/file');

// 主进程
ipcMain.handle('read-file', async (event, filePath) => {
  return fs.readFileSync(filePath, 'utf-8');
});
// iframe postMessage:跨窗口通信,只能传递可序列化数据
// 父页面
iframe.contentWindow.postMessage({ type: 'getData' }, '*');

// iframe 内
window.addEventListener('message', (event) => {
  if (event.data.type === 'getData') {
    event.source.postMessage({ result: 'data' }, event.origin);
  }
});

使用场景不同

  • Electron 沙盒:适用于需要加载第三方 Web 内容但又要访问系统功能的桌面应用
  • iframe 沙盒:适用于 Web 应用中嵌入不受信任的第三方内容

4.4.3 实际示例对比

Electron 场景:嵌入不受信任的网页

// 加载外部网站,但仍想在应用内控制
const win = new BrowserWindow({
  webPreferences: {
    nodeIntegration: false,     // 禁止访问 Node.js
    contextIsolation: true,     // 隔离上下文
    sandbox: true,              // 启用沙盒
    webSecurity: true           // 启用 Web 安全策略
  }
});
win.loadURL('https://untrusted-site.com');

Web iframe 场景:嵌入第三方组件

<!-- 嵌入第三方广告或小部件 -->
<iframe
  src="https://ads.example.com/banner"
  sandbox="allow-scripts allow-same-origin"
  style="width: 300px; height: 250px;">
</iframe>

4.4.4 总结

  • 概念相似:都是通过隔离和权限限制提升安全性
  • 实现不同:Electron 是操作系统级进程隔离,iframe 是浏览器内的功能隔离
  • 应用场景:Electron 关注系统资源保护,iframe 关注 Web 内容隔离
  • 推荐实践:Electron 默认配置(nodeIntegration: false)+ preload 脚本是最安全的方案
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容