Skip to content

单文件可执行应用程序

[历史]

版本变更
v20.6.0新增对 "useSnapshot" 的支持。
v20.6.0新增对 "useCodeCache" 的支持。
v19.7.0, v18.16.0首次引入:v19.7.0, v18.16.0

[稳定性:1 - 实验性]

稳定性:1 稳定性:1.1 - 积极开发中

源代码: src/node_sea.cc

此功能允许将 Node.js 应用程序方便地分发到未安装 Node.js 的系统。

Node.js 支持创建单文件可执行应用程序,方法是允许注入由 Node.js 准备的 blob,该 blob 可以包含捆绑的脚本,注入到 node 二进制文件中。启动期间,程序会检查是否注入了任何内容。如果找到 blob,则执行 blob 中的脚本。否则,Node.js 将按正常方式运行。

单文件可执行应用程序功能目前仅支持使用 CommonJS 模块系统运行单个嵌入式脚本。

用户可以使用 node 二进制文件本身以及任何可以将资源注入二进制文件的工具,从其捆绑的脚本创建单文件可执行应用程序。

以下是使用一种此类工具 postject 创建单文件可执行应用程序的步骤:

生成单个可执行准备 Blob

可以使用 Node.js 二进制文件的 --experimental-sea-config 标志生成注入应用程序的单个可执行准备 Blob。它接受 JSON 格式配置文件的路径。如果传递给它的路径不是绝对路径,Node.js 将使用相对于当前工作目录的路径。

当前配置读取以下顶级字段:

json
{
  "main": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "disableExperimentalSEAWarning": true, // 默认值:false
  "useSnapshot": false, // 默认值:false
  "useCodeCache": true, // 默认值:false
  "assets": {
    // 可选
    "a.dat": "/path/to/a.dat",
    "b.txt": "/path/to/b.txt"
  }
}

如果路径不是绝对路径,Node.js 将使用相对于当前工作目录的路径。用于生成 Blob 的 Node.js 二进制文件的版本必须与将向其注入 Blob 的版本相同。

注意:在生成跨平台 SEA(例如,在 darwin-arm64 上为 linux-x64 生成 SEA)时,必须将 useCodeCacheuseSnapshot 设置为 false,以避免生成不兼容的可执行文件。由于代码缓存和快照只能在编译它们的相同平台上加载,因此在尝试加载在不同平台上构建的代码缓存或快照时,生成的可执行文件可能会在启动时崩溃。

资源

用户可以通过在配置中添加一个键值对字典作为 assets 字段来包含资源。在构建时,Node.js 将从指定的路径读取资源并将它们捆绑到准备好的 Blob 中。在生成的执行文件中,用户可以使用 sea.getAsset()sea.getAssetAsBlob() API 获取资源。

json
{
  "main": "/path/to/bundled/script.js",
  "output": "/path/to/write/the/generated/blob.blob",
  "assets": {
    "a.jpg": "/path/to/a.jpg",
    "b.txt": "/path/to/b.txt"
  }
}

单执行应用程序可以按如下方式访问资源:

js
const { getAsset, getAssetAsBlob, getRawAsset } = require('node:sea')
// 返回 ArrayBuffer 中数据的副本。
const image = getAsset('a.jpg')
// 返回从资源中以 UTF8 解码的字符串。
const text = getAsset('b.txt', 'utf8')
// 返回包含资源的 Blob。
const blob = getAssetAsBlob('a.jpg')
// 返回包含原始资源的 ArrayBuffer,无需复制。
const raw = getRawAsset('a.jpg')

更多信息请参见 sea.getAsset()sea.getAssetAsBlob()sea.getRawAsset() API 的文档。

启动快照支持

useSnapshot 字段可用于启用启动快照支持。在这种情况下,最终可执行文件启动时不会运行 main 脚本。而是在构建机器上生成单个可执行应用程序准备数据块时运行它。然后,生成准备数据块将包含一个捕获 main 脚本初始化状态的快照。注入准备数据块的最终可执行文件将在运行时反序列化快照。

useSnapshot 为 true 时,主脚本必须调用 v8.startupSnapshot.setDeserializeMainFunction() API 来配置用户启动最终可执行文件时需要运行的代码。

单个可执行应用程序使用快照的典型模式是:

启动快照脚本的一般约束也适用于在用于为单个可执行应用程序构建快照时使用的主脚本,并且主脚本可以使用 v8.startupSnapshot API 来适应这些约束。请参阅 Node.js 中启动快照支持的文档

V8 代码缓存支持

当配置中 useCodeCache 设置为 true 时,在生成单个可执行文件准备 blob 期间,Node.js 将编译 main 脚本以生成 V8 代码缓存。生成的代码缓存将成为准备 blob 的一部分,并注入到最终的可执行文件中。当启动单个可执行应用程序时,Node.js 将使用代码缓存来加快编译速度,然后执行脚本,从而提高启动性能。

**注意:**当 useCodeCachetrue 时,import() 不起作用。

注入的 main 脚本中

单个可执行应用程序 API

node:sea 内建模块允许从嵌入到可执行文件中的 JavaScript main 脚本与单个可执行应用程序进行交互。

sea.isSea()

新增于:v21.7.0, v20.12.0

  • 返回值: <boolean> 此脚本是否在单个可执行应用程序内运行。

sea.getAsset(key[, encoding])

新增于: v21.7.0, v20.12.0

此方法用于检索在构建时配置为捆绑到单个可执行应用程序中的资源。如果找不到匹配的资源,则会抛出错误。

  • key <string> 单个可执行应用程序配置中assets字段指定的字典中资源的键。
  • encoding <string> 如果指定,则资源将被解码为字符串。接受TextDecoder支持的任何编码。如果未指定,则会返回包含资源副本的ArrayBuffer
  • 返回值: <string> | <ArrayBuffer>

sea.getAssetAsBlob(key[, options])

新增于:v21.7.0, v20.12.0

类似于 sea.getAsset(),但返回的结果是 Blob 对象。如果找不到匹配的资源,则会抛出错误。

  • key <string> 在单可执行应用程序配置中由 assets 字段指定的字典中资源的键。

  • options <Object>

    • type <string> Blob 的可选 MIME 类型。
  • 返回值:<Blob>

sea.getRawAsset(key)

新增于:v21.7.0, v20.12.0

此方法可用于检索在构建时配置为捆绑到单可执行应用程序中的资源。如果找不到匹配的资源,则会抛出错误。

sea.getAsset()sea.getAssetAsBlob() 不同,此方法不返回副本。相反,它返回捆绑在可执行文件内的原始资源。

目前,用户应避免写入返回的数组缓冲区。如果注入的部分未标记为可写或未正确对齐,则写入返回的数组缓冲区可能会导致崩溃。

  • key <string> 在单可执行应用程序配置中由 assets 字段指定的字典中资源的键。
  • 返回值:<ArrayBuffer>

注入的主脚本中的 require(id) 不是基于文件的

注入的主脚本中的 require() 与非注入模块可用的 require() 不同。它也没有非注入 require() 的任何属性,除了 require.main。它只能用于加载内置模块。尝试加载只能在文件系统中找到的模块将抛出错误。

与其依赖基于文件的 require(),用户可以将他们的应用程序捆绑成一个独立的 JavaScript 文件,然后注入到可执行文件中。这也确保了更确定的依赖关系图。

但是,如果仍然需要基于文件的 require(),也可以实现:

js
const { createRequire } = require('node:module')
require = createRequire(__filename)

注入的主脚本中的 __filenamemodule.filename

注入的主脚本中 __filenamemodule.filename 的值等于 process.execPath

注入的主脚本中的 __dirname

注入的主脚本中的 __dirname 值等于 process.execPath 的目录名。

注意

单可执行应用程序创建过程

旨在创建单个可执行 Node.js 应用程序的工具必须将使用 --experimental-sea-config" 准备的 blob 内容注入到:

  • 如果 node 二进制文件是 PE 文件,则注入到名为 NODE_SEA_BLOB 的资源中
  • 如果 node 二进制文件是 Mach-O 文件,则注入到 NODE_SEA 段中名为 NODE_SEA_BLOB 的节中
  • 如果 node 二进制文件是 ELF 文件,则注入到名为 NODE_SEA_BLOB 的备注中

搜索二进制文件中的 NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2:0 fuse 字符串,并将最后一个字符翻转为 1 以指示已注入资源。

平台支持

单可执行文件支持仅在以下平台上的 CI 中定期测试:

这是因为缺乏更好的工具来生成可在其他平台上测试此功能的单可执行文件。

欢迎提供其他资源注入工具/工作流程的建议。请在 https://github.com/nodejs/single-executable/discussions 启动讨论,以帮助我们记录它们。