如果为了降低部署复杂度,可以考虑使用@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 nodefunction 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
错误 解决: 确保所有依赖已打包:
- 使用
npm install --save
安装依赖 - 在
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段)