使用Electron构建桌面应用程序的正确方法

180 阅读8分钟

如何以正确的方式使用Electron构建桌面应用程序

如果你和我一样,你喜欢JavaScript和它的生态系统,并且你一直在使用React等框架或NodeJS的高性能网络服务器来构建令人惊叹的网络应用。现在你想开发一个桌面应用程序,但你不想学习新的编程语言,或者你想尽可能多地重复使用现有的网络项目。这时,Electron就会进入画面来拯救你。

Electron允许你使用HTML、CSS和JavaScript来构建桌面应用程序。网上有很多反对Electron的论调,其中之一是它的性能和很多时候低质量的应用程序,但不要责怪框架,Electron是强大的,可以有很好的性能。今天,许多流行的应用程序都运行在Electron之上,如VS Code、Slack、Skype、Discord等等。

但是,那为什么很多人对它的争论如此之少呢?这个问题要从应用程序和人们使用Electron的方式说起。对许多人来说,将一个网络应用程序移植到Electron上意味着将你现有的代码按原样嵌入到Electron容器中。这是件糟糕的事情吗?也许不是,但你并没有充分地利用Electron的力量。你只不过是把一个浏览器标签换成了一个独立的应用程序。

我们可以改进什么?在这篇文章中,我们将探讨Electron的基础知识,我们将建立一个示例应用程序来展示Electron的一些方式。


Electron是如何工作的?

Electron是建立在3个主要组件之上的:

  • Chromium:负责网络内容
  • NodeJS:负责与操作系统进行交互
  • 自定义API:解决与操作系统打交道时常见的问题

这些组件中的每一个都在Electron架构层的不同层面上进行交互,如架构图所示。

Electron架构

Electron与两种类型的进程一起工作:

  • 主进程:负责窗口管理和所有与操作系统的交互。它是一切的起点,它可以创建和管理多个渲染器进程
  • 渲染器进程。可以有一个或多个,每一个都会托管一个chromium实例,并负责网页内容。

值得注意的是,渲染器进程不能直接访问操作系统的功能。相反,它们通过IPC与主进程通信来实现这些任务。

许多典型的Electron应用程序会使用主进程来创建一个渲染器进程并加载他们的网络应用。今天我们要把这一点向前推进一步。

必须要有的部分。Hello World!

接下来,我们将建立一个 "hello world!"的应用程序。我们将不使用任何框架或库,以保持对Electron代码的关注。

让我们开始吧。

设置Electron

构建一个应用程序的第一步是创建一个项目并安装electron库,所以从使用NPM创建项目开始:

npm init

并设置好你的应用程序的细节。作为应用程序的起点,我喜欢使用main.js,但你可以使用任何你想要的文件名。

接下来,安装Electron:

npm install -D electron@latest

构建屏幕

对于我们的微型hello world例子,我们需要两个文件:main.jshello-world.htmlmain.js 是我们的主进程;我们将创建第一个渲染器进程,加载我们的hello-world.html

下面是我们的启动程序代码main.js

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

/**
 * Creates the main window
 */
function createMainWindow () {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true
    }
  });

  win.loadFile('hello-world.html');
  win.webContents.openDevTools()
}

app.whenReady().then(createMainWindow);

// When all windows are closed exit the app, except in macOS.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
});

// When the application gets activated create the main window if one does not exist
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createMainWindow()
  }
});

初始套件将处理运行应用程序的最低操作,用new BrowserWindow 创建第一个渲染器,并在其上加载main.html 。它还将处理一些退出应用程序和在需要时重新启动主窗口的情况。

至于我们的main.html ,我们将使用以下内容:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
</body>
</html>

它只是一个简单的HTML,暴露了我们的应用程序使用的node、chrome和Electron的当前版本。

最后,我们需要运行我们的应用程序;首先,你需要改变package.json ,并添加启动脚本:

"scripts": {
    "start": "electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

你的 "脚本 "部分现在应该看起来像这样:

是时候测试它了!在终端上运行

npm start

如果一切顺利的话,你应该看到一个像这样的窗口

用Electron制作的Hello World应用程序

难道没有更简单的方法吗?

简短的回答是YES!尽管它是有代价的。许多模板为构建Electron应用程序提供了一个起点,其中一些使用vanilla JS,另一些则直接与一些最流行的框架如React和Vue集成。

我不太喜欢这些模板,因为它们往往带有许多我不需要的库和附加功能。但它们是一个让你开始的好地方。


构建流畅的应用程序

我们已经在Electron的架构中看到了事情是如何运作的。如果你像我一样,你可能会担心所有这些Chromium和Node的运行实例,你应该如此。我们都知道Chromium(或Chrome)是如何吞噬我们的内存并影响我们的性能的,那么我们如何才能避免我们基于Chromium的应用程序完全这样做?我们如何保持它们的性能呢?

这里有几个提示。

永远不要屏蔽主进程

主进程是一切的开始。它是应用程序中所有进程的父进程。它主要与操作系统进行通信,处理所有的窗口和它们之间的通信,并且运行UI线程。

阻断这个进程意味着应用程序将停止响应,直到操作完成。在任何情况下,都不要在这里运行CPU密集型和需要长时间完成的代码。

这里有一些建议:

  • 对于CPU密集型操作,将这些功能委托给工作线程、渲染器进程,或者甚至生成一个专门的进程来执行该任务(不过要确保你知道你在做什么)。
  • 尽可能地避免使用remote 模块。使用remote 模块很容易在不知不觉中阻塞UI线程。
  • 避免在主线程上使用阻塞的I/O操作;如果需要,使用NodeJS提供的异步等价物。

阻断渲染器进程可以吗?

并非如此。也许后果不会像阻塞主进程那样有害,但阻塞渲染器是有代价的。你的窗口可能会变得迟钝或没有反应,整体的用户体验会很糟糕。

当我们使用网络时,我们已经习惯了一些网络应用突然变慢,不流畅,我们也可以接受;然而,当涉及到桌面应用时,我们的标准就更高了。要意识到这一点,因为用户的期望很重要。

我可以做什么来使我的应用程序更有响应性?与我们在网络应用中可以做的事情差不多;毕竟,在渲染器过程中,我们只是在谈论Chromium:

  • requestIdleCallback。允许在浏览器空闲时间排队执行JavaScript的API,可以在一帧结束时或用户不活动时执行。
  • 网络工作者:通过将昂贵的计算分配给一个新的线程,在网络浏览器上运行这些计算的最佳工具。

你不需要跨浏览器的兼容性

在Web开发过程中,使用polyfills来支持不同的浏览器是非常典型的。当构建Electron应用程序时,你不需要这些。如果它能在Chromium上运行,它就能在Electron上运行,而不需要支持任何其他的浏览器。减少你的捆绑,并通过不加载这些额外的模块来使一切变得更快。

捆绑你所有的代码

在网络开发中,我们有时会从服务器上加载脚本或页面,比如CDN,它们与我们的应用程序分开提供,这很好。毕竟,对于网络,我们总是需要下载这些资产来运行应用程序。

对于桌面应用程序,这是不一样的。通过将你所有的静态资产、脚本和内容捆绑在你的应用程序中,避免任何不必要的网络请求。这将使你的应用程序能够做两件事,一是离线工作,二是加快加载过程,因为读取磁盘的费用比上网要便宜。


结论

下次你需要建立一个跨平台的桌面应用程序时,我建议你试试Electron,特别是如果你来自于JavaScript,而且你已经有一些代码可以重新使用。

请注意,如果使用得当,Electron可以很好。请记住,虽然它看起来像网络,但它并不准确,因此你需要做一些特别的考虑来使它发挥作用。

谢谢你的阅读!