tsdown `exe` 选项实战:把 TypeScript CLI 打包成跨平台独立可执行文件
本文以 airi 仓库内置的 tsdown 技能文档 option-exe.md 为主体,系统讲解 tsdown 实验性的 exe 选项:基于 Node.js 单可执行应用(Single Executable Applications, SEA)技术,把一份 TypeScript 入口文件打包为无需用户预装 Node.js 环境的独立可执行文件,并支持通过 @tsdown/exe 在一台机器上交叉构建 Linux / macOS / Windows 三种平台的产物。读完后你将掌握 exe 选项的启用方式、对构建行为的自动调整、seaConfig 各参数含义、跨平台目标配置与缓存机制,以及 airi 这类大型 monorepo 中 tsdown 工程体系的落地背景。
背景:SEA 与 airi 中的 tsdown 体系
tsdown 是基于 Rolldown 的 TypeScript/JavaScript 库打包器,其技能总览见 SKILL.md。exe 是其"输出增强"能力之一,文档明确标注为 [experimental] 实验特性:它利用 Node.js 官方提供的 Single Executable Applications 机制,把打包后的 JS 产物注入 Node.js 二进制内部,产出一个独立可执行文件。
在 airi 仓库中,tsdown 已通过 pnpm catalog 统一管理版本(根 package.json 中 "tsdown": "catalog:",pnpm-lock.yaml 锁定为 0.22.14),并且至少 29 个子包各自携带 tsdown.config.ts,例如 packages/better-ws/tsdown.config.ts、services/computer-use-mcp/tsdown.config.ts。从这些配置的结构看,airi 目前的产出仍以库分发(entry + dts + 多入口 exports)为主,exe 能力尚未在任何包的配置中启用——但像 computer-use-mcp 的 bin/runner 入口这类带 bin/ 脚本的 CLI 工具,正是 exe 选项的典型适用对象。
前置要求
启用 exe 有两条硬性约束,来自文档 "Requirements" 一节:
- Node.js >= 25.5.0;若要产出 ESM 格式的二进制(而不是 CJS),需要 >= 25.7.0;
- 不支持在 Bun 或 Deno 运行时下构建(SEA 注入依赖 Node.js 官方二进制文件本身)。
这与 tsdown 本身的运行时要求是两回事:tsdown 运行只需要 Node.js >= 22.18.0(见 SKILL.md 的 "Runtime Requirement"),而 exe 选项因为要操作目标平台的 Node.js 二进制,把门槛抬升到了 25.x。
基础用法
最小配置只需两个字段:
export default defineConfig({
entry: ['src/cli.ts'],
exe: true,
})
entry 必须收敛为单一入口(原因见下文"启用后的行为变化")。运行 tsdown 后,dist/ 下会产出一个与入口同名的可执行文件(Windows 下自动追加 .exe 扩展名)。
启用 exe 后的自动行为变化
这是文档中容易被忽视、却直接影响构建产物的一节。开启 exe 后 tsdown 会调整以下构建行为,理解它们能帮你预判产物差异:
| 变化项 | 说明 |
|---|---|
| 默认输出格式 | 从 esm 变为 cjs(除非运行环境 Node.js >= 25.7.0,此时可保持 ESM) |
dts 声明文件生成 |
默认禁用——可执行文件面向最终用户,不需要 .d.ts |
| 代码分割 | 禁用——SEA 二进制只能注入单份 JS payload,无法运行时再按 chunk 加载 |
| 入口数量 | 仅支持单入口,多入口会违反 SEA 注入模型 |
| Legacy CJS 警告 | 被静默压制(因为 CJS 正是默认产物格式) |
从源码结构看,这些约束都源自 SEA 的注入模型:可执行文件内部是一份完整的、随进程一次性加载的 JS bundle,因此"单入口、不分割、CJS/ESM 二选一"都是结构性必然,而非 tsdown 的额外限制。
高级配置:ExeOptions 与 SeaConfig
需要定制时,exe 接受对象形式:
export default defineConfig({
entry: ['src/cli.ts'],
exe: {
fileName: 'my-tool',
seaConfig: {
disableExperimentalSEAWarning: true,
useCodeCache: true,
useSnapshot: false,
},
},
})
ExeOptions 完整字段:
| 选项 | 类型 | 说明 |
|---|---|---|
seaConfig |
Omit<SeaConfig, 'main' | 'output' | 'mainFormat'> |
Node.js SEA 注入配置;main / output / mainFormat 由 tsdown 托管,不允许覆盖 |
fileName |
string | ((chunk) => string) |
自定义产物文件名(不含 .exe 与平台后缀,后缀由工具链追加) |
targets |
ExeTarget[] |
跨平台构建目标(需要额外安装 @tsdown/exe) |
seaConfig 透传给 Node.js SEA 流程,常用参数:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
disableExperimentalSEAWarning |
boolean |
true |
关闭 SEA 实验性警告 |
useSnapshot |
boolean |
false |
使用 V8 快照,加速冷启动 |
useCodeCache |
boolean |
false |
使用 V8 代码缓存 |
execArgv |
string[] |
- | 追加传给 Node.js 的启动参数 |
execArgvExtension |
'none' | 'env' | 'cli' |
'env' |
运行时如何扩展 execArgv |
assets |
Record<string, string> |
- | 需要一并嵌入二进制的静态资源 |
其中 useSnapshot 与 useCodeCache 都是针对启动性能的手段:快照把 V8 初始堆状态固化进二进制,代码缓存则持久化编译产物,二者按启动场景取舍即可。assets 则适合那些"随包携带、读取后不再变化"的小文件(如内嵌模板、默认配置),避免运行时去磁盘查找。
跨平台构建:@tsdown/exe 与 targets
默认情况下 exe 只能构建当前平台的产物。若要在一台开发机上产出一整套分发物,先安装配套工具:
pnpm add -D @tsdown/exe
然后在配置中声明 targets:
export default defineConfig({
entry: ['src/cli.ts'],
exe: {
targets: [
{ platform: 'linux', arch: 'x64', nodeVersion: '25.7.0' },
{ platform: 'darwin', arch: 'arm64', nodeVersion: '25.7.0' },
{ platform: 'win', arch: 'x64', nodeVersion: '25.7.0' },
],
},
})
ExeTarget 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
platform |
'win' | 'darwin' | 'linux' |
目标操作系统(沿用 nodejs.org 的命名) |
arch |
'x64' | 'arm64' |
目标 CPU 架构 |
nodeVersion |
string |
目标 Node.js 版本(必须 >=25.7.0) |
构建时 tsdown 会下载目标平台对应的 Node.js 二进制、本地缓存、逐个完成 SEA 注入,最终产出带平台后缀的文件:
dist/
cli-linux-x64
cli-darwin-arm64
cli-win-x64.exe
二进制缓存
下载来的目标平台 Node.js 二进制会缓存在系统缓存目录,避免重复网络请求:
- macOS:
~/Library/Caches/tsdown/node/ - Linux:
~/.cache/tsdown/node/ - Windows:
%LOCALAPPDATA%/tsdown/Caches/node/
airi 的 lockfile 中已经登记了 @tsdown/exe@0.22.14(作为 tsdown 0.22.14 的同版本 peer 依赖项出现在 pnpm-lock.yaml),说明当前 catalog 版本链路上该工具链是可即时启用的。
平台行为细节
三条来自 "Platform Notes" 的行为约定,分发前值得确认:
- macOS:产物自动做 ad-hoc 代码签名,保证 Gatekeeper 下可以本地运行;
- Windows:
.exe扩展名自动追加,无需在fileName中手写; - 指定了
targets时:seaConfig.executable字段被忽略——因为目标二进制由targets[].nodeVersion统一下载决定,手动指定 executable 会造成版本冲突。
CLI 用法
不写配置文件时,--exe 标志可以直接走命令行(与 reference-cli.md 中 --exe 条目的描述一致):
tsdown --exe
tsdown src/cli.ts --exe
CLI 方式下无法传 seaConfig 细节,适合快速验证;正式配置建议落回 tsdown.config.ts。
在 airi monorepo 中落地的适用边界
把 exe 引入 airi 时,有几个基于仓库现状的判断点:
- 适用对象:带
bin/入口、希望用户零 Node 环境直接运行的工具最契合,例如 services/computer-use-mcp 这类已有bin/run、bin/runner双入口的 MCP 服务。注意exe仅支持单入口,多 bin 场景需要拆成多份配置(可参照 SKILL.md "Multiple Configs" 的数组写法,为每个 bin 单独声明一条exe配置); - 构建环境:CI 需要把构建机 Node 升到 25.5.0+(ESM 产物需 25.7.0+),这与仓库现有
target: 'node18'之类的低版本运行目标不冲突——target控制产物语法降级,nodeVersion控制注入用的宿主二进制,两者独立; - 产物定位:
exe产物面向终端分发而非 npm 发布,dts默认关闭符合预期;同一包若要兼顾"发 npm + 出二进制",仍应保留原有库配置,把exe配置单独成文件管理。
小结
exe 选项把 tsdown 从"npm 库打包器"延伸到"终端产物分发":单入口打包 + SEA 注入 + 跨平台 targets 构成了完整链路,代价是实验性状态、Node 25.x 的构建门槛,以及"单入口、不分割、默认 CJS"三项结构性约束。对于 airi 中的 CLI 型工具包,启用路径清晰——确认 Node 版本、安装 @tsdown/exe(跨平台时)、按上文配置即可;而它当前在仓库中尚属"已就绪未启用"的能力,lockfile 中同版本依赖的存在意味着随时可以按本文流程接入。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00