Skip to main content

Electron 内部原理:将 Node 作为库使用

· 9 min read

这是一个正在进行的系列文章中的第二篇,讲解 Electron 的内部原理。如果你还没有看过关于事件循环集成的 第一条帖子,可以先去看看。

🌐 This is the second post in an ongoing series explaining the internals of Electron. Check out the first post about event loop integration if you haven't already.

大多数人使用 Node 开发服务器端应用,但由于 Node 拥有丰富的 API 集合和繁荣的社区,它也非常适合作为嵌入式库使用。本文解释了 Node 是如何在 Electron 中作为库使用的。

🌐 Most people use Node for server-side applications, but because of Node's rich API set and thriving community, it is also a great fit for an embedded library. This post explains how Node is used as a library in Electron.


构建系统

🌐 Build system

Node 和 Electron 都使用 GYP 作为它们的构建系统。如果你想在你的应用中嵌入 Node,你也必须使用它作为你的构建系统。

🌐 Both Node and Electron use GYP as their build systems. If you want to embed Node inside your app, you have to use it as your build system too.

刚接触 GYP?在继续阅读本文之前,请先阅读 本指南

🌐 New to GYP? Read this guide before you continue further in this post.

Node 的标志

🌐 Node's flags

Node 源代码目录中的 node.gyp 文件描述了 Node 是如何构建的,以及大量 GYP 变量,这些变量控制 Node 的哪些部分被启用以及是否打开某些配置。

🌐 The node.gyp file in Node's source code directory describes how Node is built, along with lots of GYP variables controlling which parts of Node are enabled and whether to open certain configurations.

要更改构建标志,你需要在项目的 .gypi 文件中设置变量。Node 中的 configure 脚本可以为你生成一些常见的配置,例如运行 ./configure --shared 将生成一个 config.gypi,其中包含指示 Node 构建为共享库的变量。

🌐 To change the build flags, you need to set the variables in the .gypi file of your project. The configure script in Node can generate some common configurations for you, for example running ./configure --shared will generate a config.gypi with variables instructing Node to be built as a shared library.

Electron 不使用 configure 脚本,因为它有自己的构建脚本。Node 的配置在 Electron 根源代码目录下的 common.gypi 文件中定义。

🌐 Electron does not use the configure script since it has its own build scripts. The configurations for Node are defined in the common.gypi file in Electron's root source code directory.

🌐 Link Node with Electron

在 Electron 中,通过将 GYP 变量 node_shared 设置为 true 来将 Node 链接为共享库,因此 Node 的构建类型将从 executable 改为 shared_library,并且包含 Node main 入口点的源代码将不会被编译。

🌐 In Electron, Node is being linked as a shared library by setting the GYP variable node_shared to true, so Node's build type will be changed from executable to shared_library, and the source code containing the Node's main entry point will not be compiled.

由于 Electron 使用随 Chromium 提供的 V8 库,因此不会使用 Node 源代码中包含的 V8 库。这是通过将 node_use_v8_platformnode_use_bundled_v8 都设置为 false 来实现的。

🌐 Since Electron uses the V8 library shipped with Chromium, the V8 library included in Node's source code is not used. This is done by setting both node_use_v8_platform and node_use_bundled_v8 to false.

共享库或静态库

🌐 Shared library or static library

在与 Node 链接时,有两种选择:你可以将 Node 构建为静态库并将其包含在最终可执行文件中,或者你可以将其构建为共享库并随最终可执行文件一起发布。

🌐 When linking with Node, there are two options: you can either build Node as a static library and include it in the final executable, or you can build it as a shared library and ship it alongside the final executable.

在 Electron 中,Node 长期以来都是作为静态库构建的。这使得构建进程简单,能够实现最佳的编译器优化,并且允许 Electron 在不附加额外 node.dll 文件的情况下进行分发。

🌐 In Electron, Node was built as a static library for a long time. This made the build simple, enabled the best compiler optimizations, and allowed Electron to be distributed without an extra node.dll file.

然而,在 Chrome 转向使用 BoringSSL 之后,这种情况发生了变化。BoringSSL 是 OpenSSL 的一个分支,它移除了一些未使用的 API 并修改了许多现有的接口。由于 Node 仍然使用 OpenSSL,如果将它们链接在一起,编译器将会生成大量由于符号冲突引起的链接错误。

🌐 However, this changed after Chrome switched to use BoringSSL. BoringSSL is a fork of OpenSSL that removes several unused APIs and changes many existing interfaces. Because Node still uses OpenSSL, the compiler would generate numerous linking errors due to conflicting symbols if they were linked together.

Electron 无法在 Node 中使用 BoringSSL,也无法在 Chromium 中使用 OpenSSL,因此唯一的选择是将 Node 构建为共享库,并在每个组件中使用 隐藏 BoringSSL 和 OpenSSL 符号

🌐 Electron couldn't use BoringSSL in Node, or use OpenSSL in Chromium, so the only option was to switch to building Node as a shared library, and hide the BoringSSL and OpenSSL symbols in the components of each.

这一变化为 Electron 带来了一些积极的副作用。在此变化之前,如果你使用本地模块,在 Windows 上是无法重命名 Electron 的可执行文件的,因为可执行文件的名称在导入库中是硬编码的。在 Node 被构建为共享库之后,这一限制就消除了,因为所有本地模块都链接到了 node.dll,其名称无需更改。

🌐 This change brought Electron some positive side effects. Before this change, you could not rename the executable file of Electron on Windows if you used native modules because the name of the executable was hard coded in the import library. After Node was built as a shared library, this limitation was gone because all native modules were linked to node.dll, whose name didn't need to be changed.

支持原生模块

🌐 Supporting native modules

Node 中的 原生模块 通过为 Node 定义一个入口函数来加载,然后从 Node 中搜索 V8 和 libuv 的符号。这对于嵌入者来说有点麻烦,因为默认情况下,当将 Node 构建为库时,V8 和 libuv 的符号是隐藏的,本地模块将无法加载,因为它们找不到这些符号。

因此,为了使本地模块能够工作,V8 和 libuv 的符号在 Electron 中被暴露出来。对于 V8,这是通过 强制在 Chromium 的配置文件中公开所有符号 实现的。对于 libuv,则是通过 设置 BUILDING_UV_SHARED=1 定义 实现的。

🌐 So in order to make native modules work, the V8 and libuv symbols were exposed in Electron. For V8 this is done by forcing all symbols in Chromium's configuration file to be exposed. For libuv, it is achieved by setting the BUILDING_UV_SHARED=1 definition.

在你的应用中启动 Node

🌐 Starting Node in your app

在完成了所有构建和与 Node 的链接工作后,最后一步是在你的应用中运行 Node。

🌐 After all the work of building and linking with Node, the final step is to run Node in your app.

Node 并没有提供很多用于嵌入到其他应用的公共 API。通常,你可以直接调用 node::Startnode::Init 来启动一个新的 Node 实例。然而,如果你正在基于 Node 构建一个复杂的应用,你必须使用像 node::CreateEnvironment 这样的 API 来精确控制每一个步骤。

🌐 Node doesn't provide many public APIs for embedding itself into other apps. Usually, you can just call node::Start and node::Init to start a new instance of Node. However, if you are building a complex app based on Node, you have to use APIs like node::CreateEnvironment to precisely control every step.

在 Electron 中,Node 以两种模式启动:独立模式运行在主进程中,类似于官方的 Node 二进制文件,以及嵌入模式,将 Node API 插入到网页中。具体细节将在未来的文章中说明。

🌐 In Electron, Node is started in two modes: the standalone mode that runs in the main process, which is similar to official Node binaries, and the embedded mode which inserts Node APIs into web pages. The details of this will be explained in a future post.