Electron 内部:将 Node 用作库
这是解释 Electron 内部原理的系列文章中的第二篇。如果您还没有读过,请查看关于事件循环集成的第一篇文章。
大多数人将 Node 用于服务器端应用,但由于 Node 丰富的 API 集和活跃的社区,它也非常适合作为嵌入式库使用。本文将解释 Electron 中如何将 Node 用作库。
构建系统
Node 和 Electron 都使用 GYP
作为其构建系统。如果您想在应用中嵌入 Node,您也必须使用它作为您的构建系统。
刚接触 GYP
?在本文中继续之前,请先阅读本指南。
Node 的 flags
Node 源代码目录中的 node.gyp
文件描述了 Node 如何构建,同时还有许多 GYP
变量控制着 Node 的哪些部分被启用以及是否打开某些配置。
要更改构建 flags,您需要在项目中的 .gypi
文件中设置变量。Node 中的 configure
脚本可以为您生成一些常见配置,例如运行 ./configure --shared
将生成一个 config.gypi
,其中包含指示 Node 构建为共享库的变量。
Electron 不使用 configure
脚本,因为它有自己的构建脚本。Node 的配置定义在 Electron 根源代码目录中的 common.gypi
文件中。
将 Node 与 Electron 链接
在 Electron 中,通过将 GYP 变量 node_shared
设置为 true
,Node 被链接为共享库,因此 Node 的构建类型将从 executable
更改为 shared_library
,并且包含 Node main
入口点的源代码将不会被编译。
由于 Electron 使用随 Chromium 附带的 V8 库,Node 源代码中包含的 V8 库不被使用。这是通过将 node_use_v8_platform
和 node_use_bundled_v8
都设置为 false
来实现的。
共享库还是静态库
与 Node 链接时,有两种选择:您可以将 Node 构建为静态库并将其包含在最终的可执行文件中,或者将其构建为共享库并与最终的可执行文件一起分发。
在很长一段时间里,Electron 将 Node 构建为静态库。这使得构建过程变得简单,能够实现最佳的编译器优化,并且允许 Electron 在分发时无需额外的 node.dll
文件。
然而,在 Chrome 切换使用 BoringSSL 后,情况发生了变化。BoringSSL 是 OpenSSL 的一个分支,它移除了几个未使用的 API 并更改了许多现有接口。由于 Node 仍然使用 OpenSSL,如果它们链接在一起,编译器会因为符号冲突而产生大量的链接错误。
Electron 不能在 Node 中使用 BoringSSL,也不能在 Chromium 中使用 OpenSSL,所以唯一的选择是切换到将 Node 构建为共享库,并在各自的组件中隐藏 BoringSSL 和 OpenSSL 的符号。
这个改变给 Electron 带来了一些积极的副作用。在此更改之前,如果在 Windows 上使用了原生模块,则无法重命名 Electron 的可执行文件,因为可执行文件的名称硬编码在导入库中。将 Node 构建为共享库后,此限制消失了,因为所有原生模块都链接到 node.dll
,其名称不需要更改。
支持原生模块
Node 中的原生模块的工作原理是定义一个供 Node 加载的入口函数,然后从 Node 中搜索 V8 和 libuv 的符号。这对嵌入者来说有点麻烦,因为默认情况下,在将 Node 构建为库时,V8 和 libuv 的符号是隐藏的,原生模块将因为找不到符号而加载失败。
因此,为了使原生模块工作,Electron 中暴露了 V8 和 libuv 的符号。对于 V8,这是通过强制暴露 Chromium 配置文件中的所有符号来实现的。对于 libuv,则是通过设置 BUILDING_UV_SHARED=1
定义来实现的。
在应用中启动 Node
完成所有构建和链接 Node 的工作后,最后一步是在您的应用中运行 Node。
Node 没有提供很多用于将其自身嵌入其他应用的公共 API。通常,您只需调用 node::Start
和 node::Init
来启动一个新的 Node 实例。但是,如果您正在构建一个基于 Node 的复杂应用,您必须使用诸如 node::CreateEnvironment
之类的 API 来精确控制每个步骤。
在 Electron 中,Node 以两种模式启动:在主进程中运行的独立模式(类似于官方 Node 二进制文件),以及将 Node API 插入网页的嵌入模式。详细内容将在未来的文章中解释。