Skip to content

Electron 简介

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用的框架。通过将 Chromium 和 Node.js 嵌入到其二进制文件中,Electron 允许你维护一个 JavaScript 代码库并创建可在 Windows、macOS 和 Linux 上运行的跨平台应用 - 无需原生开发经验。一个 Electron 应用分为一个主进程和若干个渲染进程

  • 主进程:
    任何 Electron 应用的入口点都是其 main 脚本。该脚本控制主进程,该进程在完整的 Node.js 环境中运行,负责控制应用的生命周期、显示原生界面、执行特权操作以及管理渲染器进程(稍后会详细介绍)。

    在执行过程中,Electron 将在应用的 package.json 配置的 main 字段中查找此脚本,你应该在 应用脚手架 步骤中配置该脚本。

  • 渲染进程:
    每个页面窗口都是一个渲染进程。

主进程

js
/**
 * @app 当前应用
 * @BrowserWindow  用于新建窗口
 * @ipcMain 主进程:用于和渲染进程通信
 * @screen 获取屏幕信息
 * @BrowserWindowConstructorOptions 窗口选项的定义
 * @session
 *    defaultSession.loadExtension(plugin) 加载插件
 * @dialog 弹窗对象:
 *showErrorBox(message)
 *showMessageBox({message:'信息',type:'infor',title:'title',buttons:['是','否']})
 * @shell
 *    openExternal(url) 使用浏览器打开应用
 * @nativeTheme
 *    themeSource:system|light|dark 修改主题
 * IpcMianEvent
 */
import {
  app,
  BrowserWindow,
  ipcMain,
  screen,
  BrowserWindowConstructorOptions,
  dialog,
  shell,
  nativeTheme,
} from "electron";

/**
 * electron基于node环境,主进程可以使用node模块
 */
import path from "node:path";

function talkWithRender() {
  /** ipcMain用于和渲染进程进行通信
   * 监听渲染进程的talk事件,回调函数接受事件对象和传里的数据
   * 通过on(eventName,callback)监听通过send派发的事件。同步事件
   * 通过handle(eventName,callback)监听通过invoke派发的事件,其
   * 返回值会发送给渲染进程。为异步事件
   */
  ipcMain.handle("talk", (event, value) => {
    event.sender.send(
      "talk",
      "双向通信:通过sender拿到webContents,再次派发talk事件"
    );
    return "渲染进程,你好";
  });
  ipcMain.handleOnce("onceEvent", (event, value) => {
    console.log("只会异步通信一次");
  });
  ipcMain.on("test", (event, data) => {
    event.returnValue = "把数据同步发给渲染进程";
  });
}

// 分装创建新窗口的工厂函数
function createWindow() {
  /** BrowserWindow窗口构造器
   *  getAllWindows()获取所有窗口对象
   *  formId(windowId) 返回windowId对应的窗口对象
   *  getFocusedWindow() 获取聚焦的窗口
   *
   */

  /**
   * 使用BrowserWindow()新建一个窗口,参数为窗口配置对象
   * 每个窗口都属于一个渲染进程,
   * setPosition(x,y) 设置窗口位置
   * getBounds()  获取窗口边界信息
   * setBounds({x,y,width,height})
   * webContents  获取窗口web内容对象
   * id 窗口唯一标识id
   * isMinimized() 是否最小状态
   * restore()  恢复窗口
   * focus()  使窗口聚焦
   * removeAllListeners() 取消订阅所有与该窗口相关的事件
   * setProgressBar(number) 设置托盘图表进度
   * isDestroyed() 是否已经被销毁
   * isFocused() 是否聚焦
   * loadURL(remoteURL) 加载远程url页面
   * loadFile('index.html') 加载本地页面
   * getSize()  获取窗口大小
   * @return 窗口对象
   */
  const mainWindow = new BrowserWindow({
    title: "窗口标题",
    icon: "/hello.svg", // 托盘图表
    width: 800, //窗口宽度
    height: 600, //窗口高度

    show: true, // 是否显示窗口
    //web偏好配置
    webPreferences: {
      offscreen: false, // 是否显示UI页面
      // 渲染进程执行前需要执行的脚本,其属于特殊的渲染进程
      preload: path.resolve("./preload/preload.js"),
      // 是否集成node,集成后可部分使用node功能
      nodeIntegration: true,
      // 是否上下文隔离,隔离后不能使用contextBridge通信,只能使用进程间通讯
      contextIsolation: false,
    },
  });

  mainWindow.loadURL("http://localhost:5173/");
  mainWindow.on("closed", () => {
    console.log("窗口已经关闭了");
    childWindow.removeAllListeners();
    childWindow = null;
  });
  /** 窗口里的内容对象
   *  send('eventName',data) 同步触发eventName事件
   * openDevTools() 打开开发者工具
   * setWindowOpenHandler(callback(content)=>({action:'deny'}))
   *
   */
  const webContents = mainWindow.webContents;
  webContents.openDevTools();
  webContents.on("did-finish-load", () => {
    console.log("页面资源加载完毕执行:", mainWindow.getTitle(), 3);
  });
}

/** app应用已经完成初始化工作,准备创建窗口
 * disableHardwareAcceleration() 禁用GPU加速
 * getName() 获取应用名称
 * setAppUserModelId()
 * requestSingleInstanceLock() 单例锁,保证只有一个app运行
 * quit() 停止运行,退出应用
 * setAsDefaultProtocolClient(protocl,path,args) 注册应用唤醒的协议
 * isPackaged 是否被打包过
 * @return 初始化完成后兑现的promise
 */
app.whenReady().then(() => {
  createWindow(); // 初始化之后,开始创建窗口
  app.on("activate", function () {
    /**
     * 相比之下,macOS 应用通常即使没有打开任何窗口也会继续运行。
     * 在没有可用窗口时激活应用应该会打开一个新窗口。
     */
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

//所有窗口关闭后退出应用(Windows 和 Linux)
app.on("window-all-closed", function () {
  if (process.platform !== "darwin") app.quit();
});
app.on("browser-window-created", function (v) {
  talkWithRender();
});
// 第二个实例唤醒
app.on("second-instance", function (event, args, workingDirectory) {
  talkWithRender();
});

prefetch 预渲染进程

js
/**
 * preload脚本会在index.html加载前执行。有权限访问web API,可以访问electron的ipcRenderer模块,和部分node功能。
 */
const { contextBridge, ipcRenderer } = require("electron");

let talkWithMain = async value => {
  /**
   * 拿到渲染进程传来的value,通过ipcRenderer.invoke(eventName,value)
   * 派发主进程的handle(eventName,callback(event,value)=>{})事件监听器
   * @return 主进程handle的回调函数返回的数据
   */
  let result = await ipcRenderer.invoke("talk", value); // send invoke
  console.log("主进程返回的数据", result);
};

/** contextBridge为主进程和渲染进程的桥梁:向渲染进程暴露属性和方法
 * 当前窗口没有配置contextIsolation:true的话,可以使用
 * contextBridge.exposeInMainWorld(key,value),向渲染进程暴露数据
 * 渲染进程可通过window.electronAPI拿到此数据
 */
contextBridge.exposeInMainWorld("electronAPI", {
  talkWithMain,
});

// prefetch可以使用web API,可以使用部分node功能process
window.addEventListener("DOMContentLoaded", () => {
  const replaceText = (selector, text) => {
    const element = document.getElementById(selector);
    if (element) element.innerText = text;
  };

  for (const type of ["chrome", "node", "electron"]) {
    replaceText(`${type}-version`, process.versions[type]);
  }
});

渲染进程

js
// 通过contextBridge暴露的数据 和主进程通信
const talkHandler = () => {
  window.electronAPI.talkWithMain("主进程,你好啊");
};

// 直接使用进程间通信
import { ipcRenderer } from "electron";

// 监听主进程的send('langChange',value) 事件
ipcRenderer.on("langChange", (event, data) => {
  console.log("同步监听langChange事件");
});
// 派发主进程通过handle注册的talk事件
let result = await ipcRenderer.invoke("talk", value);
console.log("主进程返回的数据", result);

根据 MIT 许可证发布