用Node.js構建單可執行應用:輕松部署與高效開發
> 要不要把 Node.js 更新到最新版呢?
几个月之前,我没想到客户提出的这个看似无害的问题会把我带入单一可执行应用程序的领域,即所谓的SEA,也就是可执行二进制文件,如此深入。
他们的团队一直在使用PKG来打包Node.js应用程序成独立的可执行文件,但后来遇到了一个障碍——PKG已经被弃用。虽然社区的Fork一直与Node.js的演进保持同步(甚至支持Node.js v22),但由于Node.js内部结构的不断变化,导致维护这些补丁变得颇具挑战性。每个新版本的Node.js都需要适应内部的变化,这使得他们的部署流程变得更为复杂。
这时我想起了曾经听说过关于 Node.js 单文件应用(SEA)——一个原生功能。作为一个长期优化部署流程和容器策略的人来说,我知道我得探索这个有潜力的替代方案。
在这篇文章中,我将分享关于SEA的一些见解,SEA是一个彻底改变了我们打包和分发Node.js应用程序方式的特性。如果你遇到过:
- 等待打包工具支持新版本 Node.js 而引起的延迟
- 对本地模块复杂的配置需求
- 保持依赖版本和构建工具与运行时同步的维护负担
- 寻找更轻便、更便于携带的部署工件
你会发现这项尝试很有用。
我会带你了解SEA的技术基础和实际应用,展示如何用SEA简化你的部署策略,同时使你的应用程序更加易于携带和安全。
此处省略内容
目录如下- 两个应用分发的世界
- 技术基础
- 实际应用
- Docker集成
- 性能基准测试
- 代码保护的实际情况
- 最后的思考
(注:原文中的星号表示分隔符或占位符,此处保留相同格式。)
两个世界的应用分发首先,我想先区分一下每一种分发方式。
Node.js 应用部署
Node.js 的传统部署方式存在一些限制:
- 分散的文件分布:源文件、依赖项和运行时都是单独的部分
- 环境依赖:应用程序需要目标环境有正确的配置才能运行
- 配置脆弱性:设置文件必须正确放置和格式化
- 版本约束:必须使用正确的Node.js版本
这导致了一个脆弱的部署流程,一个小的配置错误就可能引发整体失败。
单个可执行应用程序部署
这意味着,单一可执行应用(SEA,即Single Executable Application)代表了一个根本性的范式转变。
- 完全自给自足:代码、依赖项、资源和运行时都封装在一个二进制文件里
- 环境独立性:应用程序自带运行环境,无需额外配置
- 安全配置:设置嵌入并保护在可执行文件中
- 部署简便性:部署和运行只需一个文件
这种方法代表了应用交付的左移策略——将部署问题从运维团队转移到开发流程中。使用SEA,开发人员在开发过程中负责打包整个应用程序环境,而不是留到部署阶段。
结果是?更可靠、更安全的且部署起来容易得多的应用。
技术基础作为一个长期从事 Node.js 开发的开发者,我对这个功能是如何实现的感到好奇。让我们一起看看内部实现吧!
剖析一次SEA
一个单一的可执行文件的构建过程包括三个不同的阶段:
- 收集阶段
- 虚拟文件系统阶段
- 二进制整合阶段
收集阶段:
事情开始变得有趣,这里就是了!SEA 工具包括:
- JavaScript应用程序代码
- JSON配置文件
- 来自
node_modules
的依赖项 - 原生扩展
- 静态资源
虚拟文件系统阶段
这里才是真正的魔法发生的地方。不像只是打包文件那样,SEA 创建了一个小型内存文件系统,这个系统:
- 精确保留目录结构
- 维护文件权限
- 保持链接完整
- 保留文件路径
- 提供快速随机访问
二进制整合
最后一步将我们的虚拟文件系统(VFS)集成到 Node.js 可执行文件中:
- 保持良好的执行能力
- 保持代码签名兼容性
- 支持运行时检测
- 支持多种跨平台格式(如 Mach-O、PE、ELF)
一小段 C++ 代码在 Node.js 源代码中担任着 SEA 乐团指挥的角色。
注意 这是技术含量较高的部分,如果你更喜欢动手实践的例子,可以直接跳到动手实践部分。
SEA配置
[node_sea.cc](https://github.com/nodejs/node/blob/main/src/node_sea.cc)
主要围绕 SeaResource
结构设计。
class SeaResource {
public:
static constexpr uint32_t kMagic = 0x1EA; // SEA 魔法数字
static constexpr size_t kHeaderSize = 8; // 头部大小(字节)
SeaFlags flags; // SeaFlags 标志
std::string_view code_path; // 代码路径
std::string_view main_code_or_snapshot; // 主代码或快照
std::optional<std::string_view> code_cache; // 代码缓存
std::unordered_map<std::string_view, std::string_view> assets; // 资源
};
进入全屏,退出全屏
这种结构揭示了几个关键的设计选择:
- 使用魔术数字(0x1EA)来标识在反序列化过程中注入的程序
- 支持代码和快照数据
- 代码缓存的位置可以选(我们之后会详细讲)
- 资源以键值对的形式存储
SEA生成过程
创建SEA的过程其实非常简单:
- 读取主捆绑脚本
- 如有必要,生成 V8 快照(我会稍后解释快照)
- 如有必要,生成代码缓存(我会稍后解释缓存)
- 处理并包含资源
- 将所有内容序列化为单个 Blob
ExitCode 生成单个可执行文件(const SeaConfig& config,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
std::string 主脚本;
ReadFileSync(&主脚本, config.main_path.c_str());
if (static_cast<bool>(config.flags & SeaFlags::kUseSnapshot)) {
生成SEA快照(...);
}
if (static_cast<bool>(config.flags & SeaFlags::kUseCodeCache)) {
生成代码缓存(config.main_path, 主脚本);
}
std::unordered_map<std::string, std::string> 资源;
构建资源文件(config.assets, &资源);
SeaSerializer 序列化器;
序列化器.Write(sea); // Assuming "sea" is a variable or object in the source code
}
切换到全屏 切换回普通模式
资源注入技术
SEA 利用 postject库将我们的 VFS 作为新部分加入到二进制文件格式中。
不同的操作系统使用不同的二进制格式。Node.js 支持的有以下内容:
- macOS 使用 Mach-O 格式
- Windows 使用 PE (可移植可执行) 格式
- Linux 使用 ELF (Executable and Linkable Format)
std::string_view FindSingleExecutableBlob() {
#ifdef __APPLE__
postject_options options;
postject_options_init(&options);
options.macho_segment_name = "NODE_SEA";
const char* blob = static_cast<const char*>(
postject_find_resource("NODE_SEA_BLOB", &size, &options));
// 在 macOS 系统下,使用特定的选项初始化资源查找
#else
const char* blob = static_cast<const char*>(
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
// 在非 macOS 系统下,使用默认选项查找资源
#endif
return {blob, size};
}
全屏模式;退出全屏
只读资产
一个重要的安全特性是只读资产访问权限。
实现通过精心设计的API确保资产数据始终保持为只读。
- 资产以不可变的字符串视图方式存储
- 创建 ArrayBuffer 时使用空操作的删除器
- 一旦资产被捆绑,就没有修改它们的 API
void 获取资产(const FunctionCallbackInfo<Value>& args) {
// 验证输入
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
Utf8Value key(args.GetIsolate(), args[0]);
SeaResource 资源对象 = 查找唯一可执行的资源();
auto it = 资源对象.assets.find(*key);
if (it == 资源对象.assets.end()) {
return;
}
std::unique_ptr<v8::BackingStore> 数据存储 = ArrayBuffer::NewBackingStore(
const_cast<char*>(it->second.data()),
it->second.size(),
[](void*, size_t, void*) {}, // 这个无操作的删除器防止对数据进行修改
nullptr);
Local<ArrayBuffer> ab = ArrayBuffer::New(args.GetIsolate(), std::move(数据存储));
// ab表示返回的ArrayBuffer对象
args.GetReturnValue().Set(ab);
}
点击进入全屏,点击退出全屏
加载SEA数据
最后一步是加载SEA(系统执行环境)并执行捆绑代码。
- 获取当前的 V8 上下文和环境信息
- 使用
FindSingleExecutableResource()
方法找到 SEA 资源 - 验证未使用快照 (
CHECK(!sea.use_snapshot())
) - 通过
ToV8Value
函数将主代码转换成 V8 值 - 然后用转换后的代码调用 CJS (CommonJS) 运行回调,由此你可以猜测,只有 CJS 被支持!
MaybeLocal<Value> 加载单个可执行应用程序(
const StartExecutionCallbackInfo& info) {
Local<Context> context = Isolate::GetCurrent()->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
SeaResource sea = 查找单个可执行资源();
CHECK(!sea.use_snapshot());
Local<Value> main_script =
ToV8Value(env->context(), sea.main_code_or_snapshot).ToLocalChecked();
return info.run_cjs->Call(
env->context(), Null(env->isolate()), 1, &main_script);
}
注释:
MaybeLocal<Value>
返回一个可能的 Local 对象,表示可能成功的操作。StartExecutionCallbackInfo
用于表示启动执行的回调信息。Isolate::GetCurrent()->GetCurrentContext()
获取当前 Isolate 的上下文。Environment::GetCurrent(context)
获取当前环境对象。SeaResource
表示资源对象,sea.use_snapshot
检查是否使用快照。ToV8Value
将输入转换为 V8 对象。info.run_cjs->Call
调用执行函数,传入参数。
点击全屏进入全屏,再次点击退出全屏。
实际操作
(注:在翻译后添加了一个空白行以匹配源文本的格式。)
我用Fastify搭建了一个文件管理服务,来展示SEA的实际应用。完整的代码可以在Node-SEA Demo仓库里找到。
希望Liran Tal 不会因为这个示例应用程序让我丢脸吧,没有输入验证功能,没有限速,没有认证机制,不过至少还有路径遍历检查;)
项目结构
项目结构简单,体现了典型的 Node.js HTTP API 应用。
node-sea-demo/
├── src/
│ ├── main.ts # 入口点,带有 SEA 检测功能
│ ├── app/
│ │ ├── app.ts # Fastify 设置
│ │ ├── helpers.ts # 工具函数库
│ │ └── routes/
│ │ └── root.ts # API 处理函数
│ └── assets/ # 静态资源
├── sea-config.json # SEA 配置文件
└── project.json # 构建配置文件
进入全屏 / 退出全屏
重要实现的具体情况:
我遇到的第一个挑战是检测是否在SEA环境中运行,以便覆盖require
函数。
import sea from 'node:sea';
if (sea.isSea()) {
const { createRequire } = require('node:module');
require = createRequire(__filename);
}
const server = Fastify({
// 初始化Fastify服务器
logger: true,
trustProxy: true,
});
切换到全屏模式,退出全屏
当你以SEA模式运行时,require.cache
未被定义!该问题正在被追踪 这里 跟踪。
另一个棘手的问题是在SEA环境中管理Fastify插件。我发现这种方式显式注册比自动加载更加可靠,来避免编译时出现的问题。
// app.ts
export async function app(fastify: FastifyInstance, opts: AppOptions) {
await fastify.register(sensible);
await fastify.register(fastifyMultipart, {
limits: {
fileSize: opts.fileSize ?? 1048576 * 10, // 10MB
},
});
const assetsDir = await getAssetsDir();
await fastify.register(fastifyStatic, {
root: assetsDir,
prefix: '/assets/',
});
await fastify.register(routes);
}
切换到全屏模式,退出全屏
安全也是一个问题。因为SEA应用程序也可能以更高的权限运行,因此我实施了严格的路径安全性策略。
// routes/root.ts
const safePathResolve = (userPath: string, baseDir: string): string | null => {
if (!userPath || /[/\\]/.test(userPath)) {
return null;
}
try {
const resolvedPath = path.resolve(baseDir, userPath);
if (!resolvedPath.startsWith(path.resolve(baseDir))) {
fastify.log.warn({ path: userPath }, '发现路径遍历尝试');
return null;
}
return resolvedPath;
} catch (err) {
fastify.log.error({ err, path: userPath }, '路径解析时发生错误');
return null;
}
};
点击这里进入全屏,点击这里退出全屏
构建流程
创建SEA的第一步是正确打包你的JavaScript。我试用了几种打包工具,发现ESBuild在速度和易用性方面达到了最佳平衡。
使用 ESBuild 进行代码打包
ESBuild 帮助您的应用为 SEA 打包做准备,通过 CLI 或构建工具。
直接使用 ESBuild 命令
执行命令:
npx esbuild \
\ --format=cjs \
\ --platform=node \
\ --bundle \
\ --tsconfig=apps/node-sea-demo/tsconfig.app.json \
\ --outdir=dist/apps/node-sea-demo \
\ --out-extension:.js=.js \
\ --outfile=main.js \
apps/node-sea-demo/src/main.ts
生成捆绑的Node.js应用程序。
全屏模式 退出全屏
每个旗帜都有自己特定的作用:
--format=cjs
: SEA 仅支持 CommonJS(记住,目前还不支持 ESM)--platform=node
: 目标环境是 Node.js--bundle
: 将所有导入的内容打包成一个文件--outfile
: 指定打包代码输出的文件
Nx ESBuild 插件配置指南
如果你使用 Nx(和我一样),你可以在项目中的配置文件中配置构建过程:
// project.json
{
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"platform": "node",
"outputPath": "dist/apps/node-sea-demo",
"format": ["cjs"],
"bundle": true,
"thirdParty": true,
"main": "apps/node-sea-demo/src/main.ts",
"tsConfig": "apps/node-sea-demo/tsconfig.app.json",
"assets": ["apps/node-sea-demo/src/assets/**/*"],
"generatePackageJson": true,
"esbuildOptions": {
"sourcemap": true,
"outExtension": {
".js": ".js"
}
}
}
}
}
全屏模式 退出全屏
生成可执行程序:
经过单个可执行文件构建工具如 PKG 的一番折腾后,我很高兴发现 Node.js 本来就支持 SEA (Streaming Emitted Assets)。配置简单到令人惊喜:
// sea-config.json
{
"main": "dist/apps/node-sea-demo/main.js", // 主入口文件路径
"output": "dist/apps/node-sea-demo/demo.blob",
"disableExperimentalSEAWarning": true, // 禁用SEA实验警告
"useCodeCache": true, // 使用代码缓存
"assets": {
"package.json": "dist/apps/node-sea-demo/package.json" // package.json 文件路径
}
}
全屏 退出全屏
生成可执行文件的过程因平台而异,如果你在使用 Nx,可以使用我的这个插件plugin让工作更轻松:
// nx.json
{
//...
"plugins": [
// ...
{
"plugin": "@getlarge/nx-node-sea",
"include": ["apps/**/*"],
"options": {
"seaTargetName": "sea-build",
"buildTarget": "build"
}
}
]
}
全屏 退出全屏
然后就运行吧:
运行以下命令来构建sea-demo项目: nx run node-sea-demo:sea-build
全屏模式 退出全屏
另外,你可以为每个平台使用以下命令,更多详情请参阅Node.js 单文件可执行应用文档。
Linux(一种操作系统)
# 生成 blob
node --experimental-sea-config sea-config.json
# 复制 node 二进制
cp $(command -v node) dist/app/node
# 注入 blob
npx postject dist/app/node NODE_SEA_BLOB dist/app/demo.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
全屏模式/退出
macOS系统
# 生成blob
node --experimental-sea-config sea-config.json
# 复制node的二进制文件
cp $(command -v node) dist/app/node
# 移除签名信息
codesign --remove-signature dist/app/node
# 注入blob数据
npx postject dist/app/node NODE_SEA_BLOB dist/app/demo.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \
--macho段名 NODE_SEA
# 重新签名二进制
codesign --sign - dist/app/node
全屏 退出全屏
Windows
# 生成blob文件
node --experimental-sea-config sea-config.json
# 复制Node.js二进制文件
copy $env:ProgramFiles\nodejs\node.exe .\dist\app\node.exe
# 注入blob到可执行文件
npx postject dist/app/node.exe NODE_SEA_BLOB dist/app/demo.blob ^
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
切换到全屏 退出全屏
Docker 整合重要提示:你不能仅仅构建一次就能在所有平台上运行:
- 代码缓存和快照特定于平台
- 原生模块(仍然)需要在特定平台上编译
作为一个为许多Node.js应用构建过Docker镜像的人,我非常期待看看SEA能否帮助减少容器大小。
我试了三种不同的方式:
- 高清大图(235MB)
- 使用完整的 Node.js 运行时
- 包含了操作系统所需的依赖项
- 最简单进行构建和调试
- 最小镜像(156MB)
-
以空白镜像为基础
-
只包含必要的操作系统依赖
- 尽可能小的磁盘占用
虽然那个 156MB 的镜像大小看起来可能并不那么令人印象深刻,与 Go 或 Rust 应用的 Docker 镜像相比,但它相比典型的 800MB 以上的 Node.js 镜像来说已经有了很大的改进。
整张图片
- 使用完整的 Node.js 运行时 - 包含开发依赖项(操作系统)
- 创建专用用户(总是比较好)
- 复制 ESBuild 打包文件并调整权限
- 无需安装依赖项,依赖项已经打包
- 用 node 运行程序
# 类型: 从官方 Node.js 镜像拉取 LTS 版本的 Alpine Linux
FROM docker.io/node:lts-alpine
# 设置服务器的主机名和端口号
ENV HOST=0.0.0.0
ENV PORT=3000
# 改变工作目录到应用目录
WORKDIR /app
# 建立一个系统用户组和用户
RUN addgroup --system node-sea-demo && \
adduser --system -G node-sea-demo node-sea-demo
# 把应用放到容器里
COPY dist/apps/node-sea-demo node-sea-demo/
# 给应用文件设定权限
RUN chown -R node-sea-demo:node-sea-demo .
# 启动应用
CMD [ "node", "node-sea-demo" ]
全屏 退出全屏
极简图像
- 启动 Alpine 并安装构建所需的依赖项
- 安装 glibc 以正确处理库文件
- 复制捆绑的代码和 SEA 配置
- 捆绑代码并移除调试信息(# 从二进制文件中移除调试符号)
- 将 scratch 用作基础镜像,
- 移除不必要的操作系统文件
- 包含最小的操作系统依赖项(加载器)并复制 Node.js SEA 可执行文件
- 运行独立的可执行文件
FROM node:lts-alpine AS build
WORKDIR /app
RUN apk add --no-cache build-base python3
RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.35-r1/glibc-2.35-r1.apk && \
apk add --no-cache glibc-2.35-r1.apk && \
rm glibc-2.35-r1.apk
COPY dist/apps/node-sea-demo dist/apps/node-sea-demo
COPY apps/node-sea-demo/sea-config.json .
# 创建SEA包并验证其文件存在且可执行
RUN node --experimental-sea-config sea-config.json && \
cp $(command -v node) dist/apps/node-sea-demo/node && \
npx postject dist/apps/node-sea-demo/node NODE_SEA_BLOB dist/apps/node-sea-demo/node-sea-demo.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 && \
chmod +x dist/apps/node-sea-demo/node && \
# 去除二进制文件中的调试符号
strip dist/apps/node-sea-demo/node
# 创建依赖目录结构
RUN mkdir -p deps && \
# 复制所有所需的共享库:
ldd dist/apps/node-sea-demo/node | grep "=> /" | awk '{print $3}' | \
xargs -I '{}' cp -L '{}' deps/ && \
# 复制其他必要的文件
cp /lib/ld-linux-*.so.* deps/ && \
# 创建所需的符号链接文件
mkdir -p deps/lib64 && \
cp /lib/ld-linux-*.so.* deps/lib64/
FROM scratch
COPY --from=build /app/deps /lib/
# 确保lib64目录存在并包含必要的加载器
COPY --from=build /app/deps/lib64 /lib64/
# 复制Node.js SEA的可执行文件
COPY --from=build /app/dist/apps/node-sea-demo/node /app/node
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE ${PORT}
ENTRYPOINT [ "/app/node" ]
切换到全屏 退出全屏
性能基准启用 useCodeCache
选项可以带来更好的性能和略微增强的代码保护,兼具双重优势,这样的好处。
// sea-config.json
{
// ...
"useCodeCache": true // 启用代码缓存
}
全屏,退出全屏
我做了一些基准测试,以查看应用程序在以下情况下的表现如何:
- 普通的 Node.js(不使用 SEA 模式)
- 代码缓存已启用
- 未启用代码缓存
这里找到的,
如图所示的SEA性能基准图
我发现这样的结果令我惊讶。
- 代码缓存使启动时间提高了大约7%
- 启用代码缓存后,Blob大小仅增加了大约7%
- 冷启动显示出显著的波动(这是任何Node.js应用程序的典型现象)
- 启用代码缓存后,热启动速度始终更快一些
代码缓存的工作原理
当JavaScript代码运行时,V8(Node.js的JavaScript解释器)会先将其编译成字节码。编译这个过程需要时间。代码缓存保存了这些预编译的字节码,使得V8在后续运行时可以跳过编译这一步。
性能提升最明显的是以下几点:
- 应用启动时间(如我们的基准测试所示)
- 无服务器环境下的冷启动
- 代码库复杂且依赖项众多
对于大多数应用程序来说,我强烈推荐启用代码缓存,虽然这会稍微增加一点大小。
代码保护的实际状况当讨论SEA时,常会提到一个问题:
_> "我们的源代码和算法在可执行文件内是否安全?"
GIF
虽然 SEA 将你的代码嵌入到 Node.js 的二进制文件中,但重要的是要理解这并不是真正的混淆。让我来解释一下为什么不是。
从SEA(此处指特定的系统或软件名称)的可执行文件中提取源代码
如前所述,SEA 是一个包含你代码的资源部分的 Node.js 二进制文件。有了合适的工具,提取这部分相对来说比较简单。
在 macOS 上,从 SEA 可执行文件中提取代码比在 Linux 上稍微复杂一些,这主要是因为 macOS 使用了 Mach-O 二进制格式。下面是一个示例:
# 1. 使用otool命令查看Mach-O头部信息,找到NODE_SEA段落
otool -l dist/apps/node-sea-demo/node | grep -A 20 NODE_SEA
# 2. 记录NODE_SEA段落的偏移量、大小和地址
# 输出结果类似如下所示:
segname NODE_SEA
vmaddr 0x0000000104e20000
vmsize 0x0000000000254000
fileoff 81739776
filesize 2432242
maxprot 0x00000001
initprot 0x00000001
nsects 1
flags 0x0
Section
sectname __NODE_SEA_BLOB
segname NODE_SEA
# ...
# 3. 使用dd命令提取该部分,从偏移量<fileoff>处开始,提取<filesize>大小的数据内容
dd if=your-sea-app of=extracted.blob bs=1 skip=81739776 count=2432242
全屏模式/退出全屏
查看提取的内容
一旦提取后,blob包含的是可读的打包JavaScript代码。让我们看看它到底什么样。
# 找找可读的字符串
运行 strings extracted.blob | less
# 找找函数
运行 strings extracted.blob | grep -A 10 "function" | less
# 找找资源
运行 strings extracted.blob | grep -A 10 "assets" | less
你可以按以下步骤操作:进入全屏;退出全屏
代码提取的截图如下:
超棒,对吧?
正如你可以看到的,原始源代码基本上还是一目了然。这就是说,单单依靠SEA来保护代码对于真正的敏感知识产权而言是不够的。
我们该怎么保护好我们的代码呢?
对于真正敏感的代码来说,我推荐使用多层次的防护措施。
- 使用 SEA 和 Bytenode 进行基本保护(注意它已被破解,详情参见 逆向工程)
- 将真正敏感的算法存于可通过 API 访问的安全服务器上
- 考虑法律保护(例如合同和服务条款)
注意:任何发送到客户机器上的代码都无法完全防住一个有足够资源和时间的对手。
最后.
基于 Node.js 的应用改变了我交付单文件可执行程序的方式,带来了以下好处:
- 更可靠的部署
- 通过整合增强了安全性
- 减少了对环境的依赖
- 优化了资源利用
经过多年的不确定的部署策略,现在我可以将我的Node.js应用程序打包成一个文件并分发,在目标系统上可以直接运行。
想亲自试试吗?这里有一些你可以尝试的步骤。
- 试试提供的示例应用程序
- 评估SEA是否适用于您的特定场景
- 将它集成到您的CI/CD管道中
- 加入Node.js SEA社区并参与其中!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章