NodeJS SEA
NodeJS "单一可执行应用"[^1] 是独立的 CLI 工具,它将打包的脚本嵌入到 NodeJS 二进制文件的特殊独立副本中。
¥NodeJS "Single Executable Applications"[^1] are standalone CLI tools that embed bundled scripts in a special standalone copy of the NodeJS binary.
SheetJS 是一个用于从电子表格读取和写入数据的 JavaScript 库。
¥SheetJS is a JavaScript library for reading and writing data from spreadsheets.
该演示使用 NodeJS SEA 和 SheetJS 创建一个独立的 CLI 工具,用于解析电子表格和生成 CSV 行。
¥This demo uses NodeJS SEA and SheetJS to create a standalone CLI tool for parsing spreadsheets and generating CSV rows.
强烈建议在命令行工具中使用 SheetJS 库的系统上安装 NodeJS。仅当认为需要独立的二进制文件时才应考虑此解决方法。
¥It is strongly recommended to install NodeJS on systems using SheetJS libraries in command-line tools. This workaround should only be considered if a standalone binary is considered desirable.
优秀的开源软件会随着用户测试和报告而不断成长。任何问题都应报告给 NodeJS 单一可执行项目以进行进一步诊断。
¥Great open source software grows with user tests and reports. Any issues should be reported to the NodeJS single-executable project for further diagnosis.
集成详情
¥Integration Details
NodeJS SEA 基本脚本可能需要 SheetJS NodeJS 模块。
¥The SheetJS NodeJS module can be required from NodeJS SEA base scripts.
NodeJS SEA 不支持 ECMAScript 模块!
¥NodeJS SEA does not support ECMAScript Modules!
CommonJS 脚本方便地包含在 SheetJS NodeJS 模块包中。
¥A CommonJS script is conveniently included in the SheetJS NodeJS module package.
在较高层次上,单一可执行应用的构建分为四个步骤:
¥At a high level, single-executable applications are constructed in four steps:
-
预处理现有 NodeJS 脚本,创建 SEA 包。
¥Pre-process an existing NodeJS script, creating a SEA bundle.
-
复制 NodeJS 二进制文件并删除所有签名。
¥Copy the NodeJS binary and remove any signatures.
-
将 SEA 包注入未签名的 NodeJS 二进制文件中。
¥Inject the SEA bundle into the unsigned NodeJS binary.
-
重新签署二进制文件。
¥Re-sign the binary.
macOS 和 Windows 强制实现数字签名。如果签名的程序被修改,两种操作系统都会警告用户。
¥macOS and Windows enforce digital signatures. Both operating systems will warn users if a signed program is modified.
在注入 SEA 包之前应删除现有签名。注入 SEA 包后,二进制文件应该被重新签名。
¥Existing signatures should be removed before injecting the SEA bundle. After injecting the SEA bundle, the binary should be resigned.
脚本要求
¥Script Requirements
专门使用 SheetJS 库和 NodeJS 内置模块的脚本可以使用 NodeJS SEA 进行打包。由于 SEA 打包器的限制,必须手动创建特殊的 require
函数:
¥Scripts that exclusively use SheetJS libraries and NodeJS built-in modules can
be bundled using NodeJS SEA. Due to limitations in the SEA bundler, a special
require
function must be created manually:
const { createRequire } = require('node:module');
require = createRequire(__filename);
const { readFile, utils } = require("xlsx");
例如,以下脚本接受一个命令行参数,使用 SheetJS readFile
方法 [^2] 解析指定文件,使用 sheet_to_csv
[^3] 从第一个工作表生成 CSV 文本,然后打印到终端:
¥For example, the following script accepts one command line argument, parses the
specified file using the SheetJS readFile
method[^2], generates CSV text from
the first worksheet using sheet_to_csv
[^3], and prints to terminal:
// For NodeJS SEA, the CommonJS `require` must be used
const { createRequire } = require('node:module');
require = createRequire(__filename);
const { readFile, utils } = require("xlsx");
// argv[2] is the first argument to the script
const filename = process.argv[2];
// read file
const wb = readFile(filename);
// generate CSV of first sheet
const ws = wb.Sheets[wb.SheetNames[0]];
const csv = utils.sheet_to_csv(ws);
// print to terminal
console.log(csv);
SEA 打包
¥SEA Bundles
SEA Bundles 是代表脚本和支持库的 blob。
¥SEA Bundles are blobs that represent the script and supporting libraries.
配置
¥Configuration
SEA 配置是使用特殊的 JSON 文件指定的。假设脚本没有打包任何特殊资源,则有两个相关字段:
¥SEA configuration is specified using a special JSON file. Assuming no special assets are bundled with the script, there are two relevant fields:
-
main
是入口脚本的相对路径。¥
main
is a relative path to the entry script. -
output
是输出文件的相对路径(通常以.blob
结尾)¥
output
is a relative path to the output file (typically ending in.blob
)
例如,以下配置指定 sheet2csv.js
作为入口脚本,sheet2csv.blob
作为输出 blob:
¥For example, the following configuration specifies sheet2csv.js
as the entry
script and sheet2csv.blob
as the output blob:
{
"main": "sheet2csv.js",
"output": "sheet2csv.blob"
}
构建
¥Construction
主 node
程序,带有命令行标志 --experimental-sea-config
,将生成一个 SEA 包:
¥The main node
program, with the command-line flag --experimental-sea-config
,
will generate a SEA bundle:
node --experimental-sea-config sheet2csv.json
该打包包将写入 SEA 配置文件的 output
字段中指定的文件。
¥The bundle will be written to the file specified in the output
field of the
SEA configuration file.
注入
¥Injection
使用特殊的 postject
实用程序将 SEA 包添加到 NodeJS 二进制文件中。具体命令取决于操作系统。
¥A special postject
utility is used to add the SEA bundle to the NodeJS binary.
The specific command depends on the operating system.
在 macOS 上,假设 NodeJS 二进制文件的副本名为 sheet2csv
,SEA 包名为 sheet2csv.blob
,则以下命令将注入该包:
¥On macOS, assuming the copy of the NodeJS binary is named sheet2csv
and the
SEA bundle is named sheet2csv.blob
, the following command injects the bundle:
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA sheet2csv NODE_SEA_BLOB sheet2csv.blob
完整示例
¥Complete Example
该演示在以下部署中进行了测试:
¥This demo was tested in the following deployments:
架构 | NodeJS | 日期 |
---|---|---|
darwin-x64 | 22.2.0 | 2024-05-28 |
darwin-arm | 22.2.0 | 2024-05-29 |
win10-x64 | 20.12.0 | 2024-03-26 |
win11-x64 | 20.13.1 | 2024-05-22 |
win11-arm | 20.14.0 | 2024-06-11 |
linux-x64 | 20.11.1 | 2024-03-18 |
linux-arm | 20.14.0 | 2024-06-10 |
ARM 上的 Windows 上的 NodeJS 使用 X64 兼容层。它不会生成原生 ARM64 二进制文件!
¥NodeJS on Windows on ARM uses the X64 compatibility layer. It does not generate a native ARM64 binary!
-
确保已安装 NodeJS 版本 20 或更高版本。
¥Ensure NodeJS version 20 or later is installed.
要显示当前版本,请运行以下命令:
¥To display the current version, run the following command:
node --version
主版本号从 v
之后开始,到第一个 .
之前结束
¥The major version number starts after the v
and ends before the first .
如果版本号为 19 或更早版本,请先升级 NodeJS,然后再继续。
¥If the version number is 19 or earlier, upgrade NodeJS before proceeding.
项目设置
¥Project Setup
-
创建一个新的项目文件夹:
¥Create a new project folder:
mkdir sheetjs-sea
cd sheetjs-sea
npm init -y
-
将
sheet2csv.js
代码块的内容 至sheet2csv.js
保存在项目文件夹中。¥Save the contents of the
sheet2csv.js
code block tosheet2csv.js
in the project folder. -
安装 SheetJS 依赖:
¥Install the SheetJS dependency:
- npm
- pnpm
- Yarn
npm i --save https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
pnpm install --save https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
yarn add https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz
脚本测试
¥Script Test
在构建独立应用之前,应使用本地 NodeJS 平台测试基本脚本。
¥Before building the standalone app, the base script should be tested using the local NodeJS platform.
-
下载测试文件 https://xlsx.nodejs.cn/pres.numbers:
¥Download the test file https://xlsx.nodejs.cn/pres.numbers:
curl -o pres.numbers https://xlsx.nodejs.cn/pres.numbers
-
运行脚本并传递
pres.numbers
作为第一个参数:¥Run the script and pass
pres.numbers
as the first argument:
node sheet2csv.js pres.numbers
该脚本应显示第一张工作表中的 CSV 内容:
¥The script should display CSV contents from the first sheet:
Name,Index
Bill Clinton,42
GeorgeW Bush,43
Barack Obama,44
Donald Trump,45
Joseph Biden,46
SEA 打包
¥SEA Bundle
-
将
sheet2csv.json
代码块的内容 至sheet2csv.json
保存在项目文件夹中。¥Save the contents of the
sheet2csv.json
code block tosheet2csv.json
in the project folder. -
生成 SEA 包:
¥Generate the SEA bundle:
node --experimental-sea-config sheet2csv.json
SEA 注入
¥SEA Injection
-
创建 NodeJS 二进制文件的本地副本:
¥Create a local copy of the NodeJS binary:
- MacOS
- Windows
- Linux
cp `which node` sheet2csv
-
删除代码签名。
¥Remove the code signature.
codesign --remove-signature ./sheet2csv
在 PowerShell 中,Get-Command
命令显示 node.exe
的位置:
¥In PowerShell, the Get-Command
command displays the location to node.exe
:
PS C:\sheetjs-sea> get-command node
CommandType Name Version Source
----------- ---- ------- ------
Application node.exe 20.12.0.0 C:\Program Files\nodejs\node.exe
将程序(列在 "来源" 列中)复制到 sheet2csv.exe
:
¥Copy the program (listed in the "Source" column) to sheet2csv.exe
:
copy "C:\Program Files\nodejs\node.exe" sheet2csv.exe
-
删除代码签名。
¥Remove the code signature.
signtool remove /s .\sheet2csv.exe
signtool
包含在 Windows SDK[^4] 中。
¥signtool
is included in the Windows SDK[^4].
cp `which node` sheet2csv
-
请注意,许多 Linux 发行版并不强制执行代码签名。
¥Observe that many Linux distributions do not enforce code signatures.
-
注入 SEA 打包包。
¥Inject the SEA bundle.
- MacOS
- Windows
- Linux
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --macho-segment-name NODE_SEA sheet2csv NODE_SEA_BLOB sheet2csv.blob
-
放弃二进制文件。以下命令执行 macOS 即席签名:
¥Resign the binary. The following command performs macOS ad-hoc signing:
codesign -s - ./sheet2csv
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 sheet2csv.exe NODE_SEA_BLOB sheet2csv.blob
-
放弃二进制文件。
¥Resign the binary.
以下序列生成自签名证书:
¥The following sequence generates a self-signed certificate:
$cert = New-SelfSignedCertificate -Type CodeSigning -DnsName www.onlyspans.net -CertStoreLocation Cert:\CurrentUser\My
$pass = ConvertTo-SecureString -String "hunter2" -Force -AsPlainText
Export-PfxCertificate -Cert "cert:\CurrentUser\My\$($cert.Thumbprint)" -FilePath "mycert.pfx" -Password $pass
创建证书后,签署二进制文件:
¥After creating a cert, sign the binary:
signtool sign /v /f mycert.pfx /p hunter2 /fd SHA256 sheet2csv.exe
npx -y postject --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 sheet2csv NODE_SEA_BLOB sheet2csv.blob
-
请注意,许多 Linux 发行版并不强制执行代码签名。
¥Observe that many Linux distributions do not enforce code signatures.
独立测试
¥Standalone Test
-
运行命令并传递
pres.numbers
作为第一个参数:¥Run the command and pass
pres.numbers
as the first argument:
./sheet2csv pres.numbers
该程序应显示与脚本相同的 CSV 内容(来自步骤 5)
¥The program should display the same CSV contents as the script (from step 5)
- MacOS
- Windows
- Linux
-
验证二进制签名:
¥Validate the binary signature:
codesign -dv ./sheet2csv
检查输出,以下行确认使用了临时签名:
¥Inspecting the output, the following line confirms ad-hoc signing was used:
Signature=adhoc
-
验证二进制签名:
¥Validate the binary signature:
signtool verify sheet2csv.exe
如果证书是自签名的,可能会出现错误:
¥If the certificate is self-signed, there may be an error:
SignTool Error: A certificate chain processed, but terminated in a root
certificate which is not trusted by the trust provider.
此错误是预期的。
¥This error is expected.
-
请注意,许多 Linux 发行版并不强制执行代码签名。
¥Observe that many Linux distributions do not enforce code signatures.
[^1]: 请参阅 NodeJS 文档中的 "单一可执行应用"。
¥See "Single Executable Applications" in the NodeJS documentation.
[^2]: 见 readFile
于 "读取文件"
¥See readFile
in "Reading Files"
[^3]: 见 sheet_to_csv
于 "CSV 和文本"
¥See sheet_to_csv
in "CSV and Text"
[^4]: 有关 Windows 开发者中心文档中的 Windows SDK。
¥See Windows SDK in the Windows Dev Center documentation.