构建Node.js单可执行应用(SEA)的方法

发布于:2025-08-15 ⋅ 阅读:(15) ⋅ 点赞:(0)

如果为了降低部署复杂度,可以考虑使用@vercel/ncc。除非有特别理由,不建议使用SEA。

1. 环境准备

1.1. 基础要求

  • Node.js: >= 19.0.0 (推荐最新LTS版本)

1.2. 安装依赖

npm install postject typescript

1.3. 验证环境

node -v          # 确认版本 >= 19
tsc -v           # 确认TypeScript已安装
postject --help  # 确认命令可用

2. 项目初始化

2.1. 目录结构

mkdir -p sea-demo/{src,bin,build}
cd sea-demo
npm init -y

2.2. 示例代码

创建src/cli.ts:

#!/usr/bin/env node

function main() {
  console.log("Hello from SEA!");
}

main();

3. 构建配置

3.1. 配置tsconfig.json

nodejs内置模块很多还在使用commonjs,统一使用commonjs可以避免很多麻烦。

{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "outDir": "bin",
    "esModuleInterop": true,
    "strict": true
  }
}

3.2. 配置 sea-config.json

{
  "main": "bin/cli.js",
  "output": "build/sea-prep.blob",
  "disableExperimentalSEAWarning": false,
  "useSnapshot": false,
  "useCodeCache": true
}

4. 完整构建流程

4.1. 编译阶段

# 编译TypeScript
tsc

4.2. 生成SEA Blob

node --experimental-sea-config sea-config.json

4.3. 准备可执行文件

# 获取Node路径
NODE_PATH=$(which node)

# 复制并重命名
cp $NODE_PATH build/sea-demo

4.4. 注入Blob

# 获取当前Node版本的FUSE值
# 有些版本nodejs无法通过下面的命令获取FUSE值,可以在网上搜索FUSE值。
FUSE_VALUE=$(node -p "process.versions.v8.split('.').slice(0, 3).join('')")

# 注入Blob
npx postject build/sea-demo NODE_SEA_BLOB build/sea-prep.blob \
  --sentinel-fuse NODE_SEA_FUSE_${FUSE_VALUE}

4.5. 基础

./build/sea-demo --version

# 查看SEA状态
strings build/sea-demo | grep NODE_SEA_

# 启用详细日志
export NODE_DEBUG=sea
./build/sea-demo

5. 高级配置

5.1. 嵌入资源文件

修改sea-config.json:

{
  "main": "bin/cli.js",
  "output": "build/sea-prep.blob",
  "assets": {
    "a.txt": "src/assets/a.txt",
    "b.json": "data/config.json"
  }
}

代码中通过 sea.resources 访问:

const resources = require('node:sea').resources;
console.log(resources.a.txt.toString());

6. 常见问题解决

6.1. FUSE值不匹配

症状: 运行时报 NODE_SEA_FUSE 错误 解决: 重新生成当前Node版本的blob

rm build/sea-prep.blob
node --experimental-sea-config sea-config.json

6.2. 模块加载失败

症状: 出现 Cannot find module 错误 解决: 确保所有依赖已打包:

  1. 使用 npm install --save 安装依赖
  2. 在 sea-config.json 中添加:
"useNodeBundle": true

7. 附录: SEA Blob 注入技术详解

7.1. 基本原理

技术栈组成:

  • postject: 基于Electron的跨平台二进制注入工具
  • FUSE: 版本特定标识符,防止错误注入
  • NODE_SEA_BLOB: 预定义的二进制段名称

工作流程:

+---------------------+     +---------------------+
| 原始Node可执行文件  | --> | 查找PE/ELF/MachO的  |
+---------------------+     | 特殊可注入区域      |
                            +----------+----------+
                                       |
                       +---------------v---------------+
                       | 验证FUSE标识匹配性           |
                       | 检查段对齐和内存布局         |
                       +---------------+---------------+
                                       |
                            +----------v----------+
                            | 写入压缩后的JS代码  |
                            | 更新文件校验和      |
                            +---------------------+

7.2. 关键实现细节

二进制格式处理:

  • Windows PE: 修改 .rdata 段
  • Linux ELF: 扩展 .rodata 段
  • macOS Mach-O: 使用 __LLVM 段

内存布局示例(Linux ELF):

+-------------------------+
| ELF Header              |
+-------------------------+
| Program Headers         |
+-------------------------+
| .text                   |
+-------------------------+
| .rodata (原始数据)      |
+-------------------------+
| NODE_SEA_BLOB (新增)    | <-- 注入位置
+-------------------------+
| .data                   |
+-------------------------+

7.3. 数据段保护原理

现代二进制注入工具(如postject)采用以下策略保证安全:

保护机制 实现方式
段末尾注入 在目标段的文件空隙区域写入
段扩展 通过增加PE/ELF节区大小实现
重定位表更新 自动调整所有受影响的重定位项
校验和修复 更新PE文件的Checksum字段

典型二进制文件布局(注入前后对比):

;; 注入前
[.text] [.rodata] [.data] [.imports]... <未使用空间>

;; 安全注入后
[.text] [.rodata] [NODE_SEA_BLOB] [.data] [.imports]...
;; 或
[.text] [.rodata+blob] [.data]... (扩展.rodata段)

网站公告

今日签到

点亮在社区的每一天
去签到