electron扫盲

electron 是谁

Electron 是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架,底层借助浏览器壳子支持了跨平台,然后可以借助 Node.js 操作进程逻辑和调用系统能力,最后再渲染网页内容,整体组成如下:

Electron = Chromium + Node.js + Native API

快速开始

用脚手架 electron-quick-start 快速搭建项目

git clone https://github.com/electron/electron-quick-start
cd electron-quick-start
yarn
yarn start

项目结构比较简单

electron-quick-start
├─ LICENSE.md
├─ README.md
├─ index.html        # html网页
├─ main.js           # 主进程脚本
├─ package-lock.json
├─ package.json
├─ preload.js        # 预执行脚本
├─ renderer.js       # 页面js脚本
└─ styles.css
  • main.js:启动应用程序并创建一个浏览器窗口来呈现 HTML
  • preload.js:在渲染器进程加载之前运行的脚本,可以将 Electron 的不同类型的进程桥接在一起
// main.js
// ...
function createWindow() {
  // 创建window对象
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      // 加载preload脚本
      preload: path.join(__dirname, "preload.js"),
    },
  });
  // 加载网页
  mainWindow.loadFile("index.html");
}

preload.js 应用示例

// preload.js
const { contextBridge } = require("electron");

// exposeInMainWorld 将 API 注入到 window 对象,之后渲染进程可通过 window[apiKey] 访问
contextBridge.exposeInMainWorld("versions", {
  node: () => process.versions.node,
  chrome: () => process.versions.chrome,
  electron: () => process.versions.electron,
});

现在渲染器能够全局访问 versions 了,让我们快快将里边的信息显示在窗口中

// renderer.js
const information = document.getElementById("info");
information.innerText = `本应用正在使用 Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), 和 Electron (v${versions.electron()})`;

调整 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'; style-src 'self' 'unsafe-inline'"
    />
    <link href="./styles.css" rel="stylesheet" />
    <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>. -->
    <p id="info"></p>
    <!-- You can also require other files to run in this process -->
    <script src="./renderer.js"></script>
  </body>
</html>

当然脚手架本身的示例也是类似的效果,不过并不是用 contextBridge.exposeInMainWorld 的方式,而是在获取到进程信息后直接操作 dom 节点,殊途同归~

打包

借助 Electron-forge 可以打包 electron 应用,用法如下:

yarn add --dev @electron-forge/cli
npx electron-forge import
yarn make

Electron-forge 会创建 out 文件夹,在那里找到生成的软件包,直接运行试试!

当然也可以用 Electron Builder,它集成了许多插件,如自动更新、发布、代码签名等,感兴趣可以自行探究~

实际项目可能还要用到 typescript、react、代码工具等,所以建议用基于构建工具(比如 webpack)的脚手架,比如 electron-react-boilerplate,是个比较出名的 electron-react 脚手架。vue 也有对应的脚手架 electron-vue

调试

其实就跟浏览器调试一样了,devtools 全家桶

多进程架构

参考 https://chend0316.github.io/frontend/electron/#electron%E7%9A%84%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84

每个 Electron 应用有且只有一个主进程(Main Process)、以及 n 个渲染进程(Renderer Process), 对应多个网页,除此之外还有 GPU 进程、扩展进程等

主要介绍下两个进程:主进程和渲染进程

主进程

  • 主进程使用 BrowserWindow 实例创建页面,主进程可以创建多个渲染进程
  • 响应应用(app)的生命周期事件
  • 负责与原生操作系统 API 进行通信,可以使用 Node.js 所有模块
  • 执行诸如注册全局快捷方式、创建菜单和对话框等操作

这里有俩个关键模块

  • app 控制应用程序的事件生命周期
  • BrowserWindow 创建和管理应用程序窗口

渲染进程

可以操作部分 Node.js api,比如 fs、cypto、path 等,参考下图

  • 当 BrowserWindow 实例销毁时,对应的渲染进程也会终止。渲染进程可以加载 Web 页面
  • 在窗口中包含 webContents 实例之前,实际上不会创建渲染器进程。一个窗口可以托管多个 webview,每个 webview 都有自己的 webContents 实例和渲染器进程
  • 渲染进程中可以使用所有 DOM API、Node.js APIElectron API 的子集
  • 渲染进程之间相互隔离,并且不允许它们直接访问操作系统级别的 API。当渲染进程需要访问系统级别 api,可以与主进程通信,由主进程实现其功能

preload 脚本

搭建渲染进程和主进程的桥梁,通过 contextBridge.exposeInMainWorld() 将 electron 或者 Node.js 的 api 或者自定义函数暴露到 window 上,使得网页代码中可以使用额外的 api,跟主进程进行通信

从 Electron 20 开始,预加载脚本默认沙盒化,不再拥有完整 Node.js 环境的访问权。可用 api 参考 https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-preload

进程间通信

IPC 实现中使用结构化克隆算法序列化进程之间传递的对象,分为 ipcmain(主进程 IPC 协议)和 ipcRenderer(渲染进程 IPC 协议) ,这两个都是 EventEmitter 类的实例

  • 渲染进程向主进程(单向)on/send
  • 渲染进程向主进程(双向)handle/invoke
  • 主进程向渲染进程(单向)webContents
  • 渲染进程之间,主进程做消息代理或基于 MessagePort

代码示例参考 https://www.electronjs.org/zh/docs/latest/tutorial/ipc

其他桌面开发方式

  • Native(C++/C#/Objective-C) 不管从原生体验、包体积、性能方面来说都是最佳的选择,但是开发门槛和成本都比较高
  • Web 的代表ElectronNW.js ,相比后者,Electron 有更活跃的社区,像 Atom、vscode 这样的大型应用都是基于 Electron 开发的,性能相比于 Native 是肯定要差
  • QT 是基于 C++的跨平台开发框架,跨平台应用很广泛,比如 WPS 就是用 QT 开发的。性能很好,可以媲美 Native,但是开发门槛还是比较高的
  • Flutter 渲染性能优于 web,但稳定性和生态相对差一些

进阶方向

主要是以下几个大方向可以深入探究

  • 构建工具与 electron 配套的结合,进一步提高构建效率。这块可以学习下优秀的脚手架项目的实现
  • 深化与操作系统和 Node.js 的集成,以及可以试着写写 c++插件
  • 应用性能优化,比如减少包体积和白屏现象等

参考