Single executable applications
此功能允许将 Node.js 应用方便地分发到未安装 Node.js 的系统
Node.js 通过允许将由 Node.js 准备的 blob 注入 node
二进制文件来支持 单个可执行应用 的创建,其中可以包含打包脚本。在启动过程中,程序会检查是否注入了任何东西。如果找到 blob,它会执行 blob 中的脚本。否则 Node.js 会像往常一样运行
单个可执行应用功能目前仅支持使用 CommonJS
模块系统运行单个嵌入式脚本
用户可以使用 node
二进制文件本身和任何可以将资源注入二进制文件的工具从他们的打包脚本创建单个可执行应用
以下是使用此类工具 postject 创建单个可执行应用的步骤:
- 创建一个
JavaScript
文件:
echo 'console.log(`Hello, ${process.argv[2]}!`);' > hello.js
- 创建一个配置文件,构建一个可以注入单个可执行应用的 blob
echo '{ "main": "hello.js", "output": "sea-prep.blob" }' > sea-config.json
- 生成要注入的 blob:
node --experimental-sea-config sea-config.json
- 创建
node
可执行文件的副本并根据需要命名:- 在 Windows 以外的系统上:
shellcp $(command -v node) hello
- 在 Windows 上:
shellnode -e "require('fs').copyFileSync(process.execPath, 'hello.exe')"
.exe
扩展名是必需的
删除二进制文件的签名(仅限 macOS 和 Windows):
- 在 macOS 上:
shellcodesign --remove-signature hello
- 在 Windows 上(可选):
signtool
可以从已安装的Windows SDK
使用。如果跳过此步骤,请忽略来自postject
的任何与签名相关的警告shellsigntool remove /s hello.exe
通过使用以下选项运行
postject
将 blob 注入到复制的二进制文件中:hello
/hello.exe
- 在步骤 4 中创建的 node 可执行文件副本的名称。NODE_SEA_BLOB
- 二进制文件中将存储 blob 内容的资源/注释/部分的名称sea-prep.blob
- 在步骤 1 中创建的 blob 的名称--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
- Node.js 项目用来检测文件是否被注入的fuse
--macho-segment-name NODE_SEA
(仅在 macOS 上需要) - 二进制文件中将存储 blob 内容的段的名称。
总而言之,这是每个平台所需的命令:
- 在 Linux 上:
npx postject hello NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
- 在 Windows 上 - 电源外壳:
npx postject hello.exe NODE_SEA_BLOB sea-prep.blob `
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
- 在 Windows 上 - 命令提示符:
npx postject hello.exe NODE_SEA_BLOB sea-prep.blob ^
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
- 在 macOS 上:
npx postject hello NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho-segment-name NODE_SEA
- 签署二进制文件(仅限 macOS 和 Windows):
- 在 macOS 上:
codesign --sign - hello
- 在 Windows 上(可选): 需要有证书才能工作。但是,未签名的二进制文件仍然可以运行。
signtool sign /fd SHA256 hello.exe
- 运行二进制文件:
- 在 Windows 以外的系统上
$ ./hello world
Hello, world!
- 在 Windows 上
$ .\hello.exe world
Hello, world!
FAQ
没有 Signtool
- 安装 Windows SDK
- 我的文件夹
C:\Program Files (x86)\Windows Kits\10\App Certification Kit
下面有 signtool,直接配个 path 环境变量就好了
签名出现:SignTool Error: No certificates were found that met all the given criteria.
还未找到原因
生成单个可执行准备 blob
可以使用将用于构建单个可执行文件的 Node.js 二进制文件的 --experimental-sea-config
标志生成注入到应用中的单个可执行文件准备 blob。它采用 JSON 格式的配置文件路径。如果传递给它的路径不是绝对路径,Node.js 将使用相对于当前工作目录的路径。
该配置当前读取以下顶层字段:
{
"main": "/path/to/bundled/script.js",
"output": "/path/to/write/the/generated/blob.blob",
"disableExperimentalSEAWarning": true, // Default: false
"useSnapshot": false, // Default: false
"useCodeCache": true, // Default: false
"assets": { // Optional
"a.dat": "/path/to/a.dat",
"b.txt": "/path/to/b.txt"
}
}
如果路径不是绝对路径,Node.js 将使用相对于当前工作目录的路径。用于生成 blob 的 Node.js 二进制文件的版本必须与将注入 blob 的版本相同。
资源
用户可以通过将键路径字典添加到配置中作为 assets
assets 字段来包含资源。在构建时,Node.js 将从指定路径读取资源并将它们打包到准备 blob
中。在生成的可执行文件中,用户可以使用 sea.getAsset()
和 sea.getAssetAsBlob
() API 检索资源
{
"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"
}
}
单一可执行应用可以按如下方式访问资源:
const { getAsset } = require('node:sea');
// Returns a copy of the data in an ArrayBuffer.
const image = getAsset('a.jpg');
// Returns a string decoded from the asset as UTF8.
const text = getAsset('b.txt', 'utf8');
// Returns a Blob containing the asset.
const blob = getAssetAsBlob('a.jpg');
// Returns an ArrayBuffer containing the raw asset without copying.
const raw = getRawAsset('a.jpg');
启动快照支持
useSnapshot
字段可用于启用启动快照支持。在这种情况下,启动最终可执行文件时将不会出现 main
脚本。相反,它将在构建计算机上生成单个可执行应用准备 blob
时运行。然后,生成的准备 blob
将包含捕获由 main
脚本初始化的状态的快照。注入准备 blob 的最终可执行文件将在运行时反序列化快照。
当 useSnapshot
为 true
时,主脚本必须调用 v8.startupSnapshot.setDeserializeMainFunction()
API 来配置用户启动最终可执行文件时需要运行的代码。
应用在单个可执行应用中使用快照的典型模式是:
- 在构建时,在构建机器上,运行主脚本以将堆初始化为准备接受用户输入的状态。该脚本还应该使用
v8.startupSnapshot.setDeserializeMainFunction()
配置一个 main 函数。该函数将被编译并序列化到快照中,但不会在构建时调用。 - 在运行时,主函数将在用户计算机上的反序列化堆之上运行,以处理用户输入并生成输出。
当用于为单个可执行应用构建快照时,启动快照脚本的一般约束也适用于主脚本,并且主脚本可以使用 v8.startupSnapshot
API 来适应这些约束
V8 代码缓存支持
当配置中将 useCodeCache
设置为 true
时,在生成单个可执行准备 blob
期间,Node.js 将编译 main
脚本以生成 V8 代码缓存。生成的代码缓存将成为准备 blob 的一部分,并注入到最终的可执行文件中。当单个可执行应用启动时,Node.js 不会从头开始编译 main
脚本,而是使用代码缓存来加速编译,然后执行脚本,这将提高启动性能。
注意:当 useCodeCache
为 true
时,import()
不起作用
单个可执行应用 API
node:sea
内置允许通过嵌入到可执行文件中的 JavaScript 主脚本与单个可执行应用进行交互。
sea.isSea()
该脚本是否在单个可执行应用内运行。
sea.getAsset(key[, encoding])
此方法可用于检索配置为在构建时打包到单个可执行应用中的资源。当找不到匹配的资源时会抛出错误。
key
单一可执行应用配置中的assets
字段指定的字典中资源的键encoding
如果指定,资源将被解码为字符串。接受TextDecoder
支持的任何编码。如果未指定,则将返回包含资源副本的ArrayBuffer
sea.getAssetAsBlob(key[, options])
与 sea.getAsset()
类似,但以 Blob
形式返回结果。当找不到匹配的资源时会抛出错误。
key
单一可执行应用配置中的assets
字段指定的字典中资源的键options
type
blob 的可选 MIME 类型
sea.getRawAsset(key)
此方法可用于检索配置为在构建时打包到单个可执行应用中的资源。当找不到匹配的资源时会抛出错误。
与 sea.getRawAsset()
或 sea.getAssetAsBlob()
不同,此方法不返回副本。相反,它返回打包在可执行文件内的原始资源。
目前,用户应避免写入返回的数组缓冲区。如果注入的部分未标记为可写或未正确对齐,则写入返回的数组缓冲区可能会导致崩溃。
key
单一可执行应用配置中的assets
字段指定的字典中资源的键
注入的主脚本中的 require(id)
不是基于文件的
注入的主脚本中的 require()
与未注入的模块可用的 require()
不同。除 require.main
外,它也不具有非注入 require()
所具有的任何属性。它只能用于加载内置模块。尝试加载只能在文件系统中找到的模块将引发错误。
用户可以将他们的应用打包到一个独立的 JavaScript 文件中以注入可执行文件,而不是依赖于基于 require()
的文件。这也确保了更具确定性的依赖图。
但是,如果仍然需要基于 require()
的文件,也可以实现:
const { createRequire } = require('node:module');
require = createRequire(__filename);
注入主脚本中的 __filename
和 module.filename
注入的主脚本中的 __filename
和 module.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 上定期测试单一可执行支持:
- Windows
- macOS
- Linux(除 Alpine 外的所有发行版 由 Node.js 支持 和除 s390x 外的所有架构 由 Node.js 支持)
这是由于缺乏更好的工具来生成可用于在其他平台上测试此功能的单一可执行文件