Skip to content

Commit b683385

Browse files
authored
feat(exe): add cross-platform executable building via @tsdown/exe (#786)
1 parent 5c71f67 commit b683385

File tree

24 files changed

+857
-50
lines changed

24 files changed

+857
-50
lines changed

docs/.vitepress/config/theme.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export function getLocaleConfig(lang: string) {
137137
{ text: t('Unbundle'), link: '/unbundle.md' },
138138
{ text: t('CJS Default Export'), link: '/cjs-default.md' },
139139
{ text: t('CSS'), link: '/css.md' },
140+
{ text: t('Executable'), link: '/exe.md' },
140141
{ text: t('Package Validation'), link: '/lint.md' },
141142
],
142143
},

docs/.vitepress/i18n/translate-map.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const zhCN = {
3131
'Package Exports': '包导出(Package Exports)',
3232
Unbundle: '非打包模式(Unbundle)',
3333
'CJS Default Export': 'CJS 默认导出',
34+
Executable: '可执行文件',
3435
'Package Validation': '包校验',
3536

3637
Recipes: '实践指南',

docs/options/exe.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# Executable
2+
3+
:::warning Experimental
4+
The `exe` option is experimental and may change in future releases.
5+
:::
6+
7+
tsdown can bundle your TypeScript/JavaScript code into a standalone executable using [Node.js Single Executable Applications](https://nodejs.org/api/single-executable-applications.html). The output is a native binary that runs without requiring Node.js to be installed.
8+
9+
## Requirements
10+
11+
- Node.js >= 25.5.0 (ESM support requires >= 25.7.0)
12+
- Not supported in Bun or Deno
13+
14+
## Basic Usage
15+
16+
```bash
17+
tsdown src/cli.ts --exe
18+
```
19+
20+
Or in your config file:
21+
22+
```ts [tsdown.config.ts]
23+
export default defineConfig({
24+
entry: ['src/cli.ts'],
25+
exe: true,
26+
})
27+
```
28+
29+
When `exe` is enabled:
30+
31+
- The default output format changes from `esm` to `cjs` (unless Node.js >= 25.7.0, which supports ESM)
32+
- Declaration file generation (`dts`) is disabled by default
33+
- Code splitting is disabled
34+
- Only single entry points are supported
35+
36+
## Advanced Configuration
37+
38+
You can pass an object to `exe` for more control:
39+
40+
```ts [tsdown.config.ts]
41+
export default defineConfig({
42+
entry: ['src/cli.ts'],
43+
exe: {
44+
fileName: 'my-tool',
45+
seaConfig: {
46+
disableExperimentalSEAWarning: true,
47+
useCodeCache: true,
48+
},
49+
},
50+
})
51+
```
52+
53+
### `fileName`
54+
55+
Custom output file name for the executable. Do not include `.exe`, platform suffixes, or architecture suffixes — they are added automatically.
56+
57+
Can be a string or a function:
58+
59+
```ts [tsdown.config.ts]
60+
export default defineConfig({
61+
entry: ['src/cli.ts'],
62+
// string
63+
exe: { fileName: 'my-tool' },
64+
// or function
65+
// exe: { fileName: (chunk) => `my-tool-${chunk.name}` },
66+
})
67+
```
68+
69+
### `seaConfig`
70+
71+
Passes options directly to Node.js. See the [Node.js documentation](https://nodejs.org/api/single-executable-applications.html) for full details.
72+
73+
| Option | Type | Default | Description |
74+
| ------------------------------- | -------------------------- | ------- | ------------------------------------ |
75+
| `disableExperimentalSEAWarning` | `boolean` | `true` | Disable the experimental warning |
76+
| `useSnapshot` | `boolean` | `false` | Use V8 snapshot for faster startup |
77+
| `useCodeCache` | `boolean` | `false` | Use V8 code cache for faster startup |
78+
| `execArgv` | `string[]` || Extra Node.js CLI arguments |
79+
| `execArgvExtension` | `'none' \| 'env' \| 'cli'` | `'env'` | How to extend execArgv at runtime |
80+
| `assets` | `Record<string, string>` || Assets to embed into the executable |
81+
82+
## Cross-Platform Builds
83+
84+
By default, `exe` builds for the current platform. To build executables for multiple platforms from a single machine, install the `@tsdown/exe` package and use the `targets` option:
85+
86+
::: code-group
87+
88+
```bash [pnpm]
89+
pnpm add -D @tsdown/exe
90+
```
91+
92+
```bash [npm]
93+
npm install -D @tsdown/exe
94+
```
95+
96+
```bash [yarn]
97+
yarn add -D @tsdown/exe
98+
```
99+
100+
:::
101+
102+
```ts [tsdown.config.ts]
103+
export default defineConfig({
104+
entry: ['src/cli.ts'],
105+
exe: {
106+
targets: [
107+
{ platform: 'linux', arch: 'x64', nodeVersion: '25.7.0' },
108+
{ platform: 'darwin', arch: 'arm64', nodeVersion: '25.7.0' },
109+
{ platform: 'win', arch: 'x64', nodeVersion: '25.7.0' },
110+
],
111+
},
112+
})
113+
```
114+
115+
This downloads the target platform's Node.js binary from nodejs.org, caches it locally, and uses it to build the executable. The output files are named with platform and architecture suffixes:
116+
117+
```
118+
dist/
119+
cli-linux-x64
120+
cli-darwin-arm64
121+
cli-win-x64.exe
122+
```
123+
124+
### Target Options
125+
126+
Each target in the `targets` array accepts:
127+
128+
| Field | Type | Description |
129+
| ------------- | ------------------------------ | -------------------------------------------------------- |
130+
| `platform` | `'win' \| 'darwin' \| 'linux'` | Target operating system (aligned with nodejs.org naming) |
131+
| `arch` | `'x64' \| 'arm64'` | Target CPU architecture |
132+
| `nodeVersion` | `string` | Node.js version to use (must be `>=25.7.0`) |
133+
134+
:::warning
135+
When `targets` is specified, the `seaConfig.executable` option is ignored — the downloaded Node.js binary is used instead.
136+
:::
137+
138+
### Caching
139+
140+
Downloaded Node.js binaries are cached in the system cache directory:
141+
142+
- **macOS:** `~/Library/Caches/tsdown/node/`
143+
- **Linux:** `~/.cache/tsdown/node/` (or `$XDG_CACHE_HOME/tsdown/node/`)
144+
- **Windows:** `%LOCALAPPDATA%/tsdown/Caches/node/`
145+
146+
Subsequent builds reuse cached binaries without re-downloading.
147+
148+
## Platform Notes
149+
150+
- On **macOS**, the executable is automatically codesigned (ad-hoc) for Gatekeeper compatibility. When cross-compiling for macOS from a non-macOS host, codesigning will be skipped with a warning.
151+
- On **Windows**, the `.exe` extension is automatically appended.

docs/reference/cli.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,17 +248,19 @@ An alias for `--copy`.
248248

249249
## `--exe`
250250

251-
**[experimental]** Bundle as executable using Node.js SEA (Single Executable Applications).
251+
**[experimental]** Bundle as a standalone executable using [Node.js Single Executable Applications](https://nodejs.org/api/single-executable-applications.html).
252252

253-
This will bundle the output into a single executable file using Node.js SEA. Requires Node.js 25.5.0 or later, and is not supported in Bun or Deno.
253+
This will bundle the output into a single executable file. Requires Node.js 25.5.0 or later, and is not supported in Bun or Deno. Cross-platform builds are supported via the `@tsdown/exe` package.
254254

255255
When `exe` is enabled:
256256

257-
- The default output format changes from `esm` to `cjs` (unless ESM SEA is supported, i.e., Node.js >= v25.7.0).
257+
- The default output format changes from `esm` to `cjs` (unless Node.js >= v25.7.0, which supports ESM).
258258
- Declaration file generation (`dts`) is disabled by default.
259259
- Code splitting is disabled.
260260
- Only single entry points are supported.
261261

262+
See also [Executable](../options/exe.md).
263+
262264
## `--exports`
263265

264266
Generate the `exports`, `main`, `module`, and `types` fields in your `package.json`.

docs/zh-CN/options/exe.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# 可执行文件
2+
3+
:::warning 实验性
4+
`exe` 选项目前是实验性的,可能会在未来版本中发生变化。
5+
:::
6+
7+
tsdown 可以使用 [Node.js 单可执行应用](https://nodejs.org/api/single-executable-applications.html)将你的 TypeScript/JavaScript 代码打包为独立的可执行文件。输出的是原生二进制文件,无需安装 Node.js 即可运行。
8+
9+
## 环境要求
10+
11+
- Node.js >= 25.5.0(ESM 支持需要 >= 25.7.0)
12+
- 不支持 Bun 和 Deno
13+
14+
## 基本用法
15+
16+
```bash
17+
tsdown src/cli.ts --exe
18+
```
19+
20+
或者在配置文件中使用:
21+
22+
```ts [tsdown.config.ts]
23+
export default defineConfig({
24+
entry: ['src/cli.ts'],
25+
exe: true,
26+
})
27+
```
28+
29+
启用 `exe` 时:
30+
31+
- 默认输出格式从 `esm` 变更为 `cjs`(Node.js >= 25.7.0 支持 ESM 时除外)
32+
- 默认禁用声明文件生成(`dts`
33+
- 禁用代码分割
34+
- 仅支持单入口
35+
36+
## 高级配置
37+
38+
可以传递对象给 `exe` 以获得更多控制:
39+
40+
```ts [tsdown.config.ts]
41+
export default defineConfig({
42+
entry: ['src/cli.ts'],
43+
exe: {
44+
fileName: 'my-tool',
45+
seaConfig: {
46+
disableExperimentalSEAWarning: true,
47+
useCodeCache: true,
48+
},
49+
},
50+
})
51+
```
52+
53+
### `fileName`
54+
55+
自定义可执行文件的输出名称。不需要包含 `.exe`、平台后缀或架构后缀,它们会自动添加。
56+
57+
可以是字符串或函数:
58+
59+
```ts [tsdown.config.ts]
60+
export default defineConfig({
61+
entry: ['src/cli.ts'],
62+
// 字符串
63+
exe: { fileName: 'my-tool' },
64+
// 或函数
65+
// exe: { fileName: (chunk) => `my-tool-${chunk.name}` },
66+
})
67+
```
68+
69+
### `seaConfig`
70+
71+
直接传递选项给 Node.js。详见 [Node.js 文档](https://nodejs.org/api/single-executable-applications.html)
72+
73+
| 选项 | 类型 | 默认值 | 描述 |
74+
| ------------------------------- | -------------------------- | ------- | ------------------------ |
75+
| `disableExperimentalSEAWarning` | `boolean` | `true` | 禁用实验性警告 |
76+
| `useSnapshot` | `boolean` | `false` | 使用 V8 快照加速启动 |
77+
| `useCodeCache` | `boolean` | `false` | 使用 V8 代码缓存加速启动 |
78+
| `execArgv` | `string[]` || 额外的 Node.js CLI 参数 |
79+
| `execArgvExtension` | `'none' \| 'env' \| 'cli'` | `'env'` | 运行时如何扩展 execArgv |
80+
| `assets` | `Record<string, string>` || 嵌入到可执行文件中的资产 |
81+
82+
## 跨平台构建
83+
84+
默认情况下,`exe` 仅为当前平台构建。要从一台机器上为多个平台构建可执行文件,请安装 `@tsdown/exe` 包并使用 `targets` 选项:
85+
86+
::: code-group
87+
88+
```bash [pnpm]
89+
pnpm add -D @tsdown/exe
90+
```
91+
92+
```bash [npm]
93+
npm install -D @tsdown/exe
94+
```
95+
96+
```bash [yarn]
97+
yarn add -D @tsdown/exe
98+
```
99+
100+
:::
101+
102+
```ts [tsdown.config.ts]
103+
export default defineConfig({
104+
entry: ['src/cli.ts'],
105+
exe: {
106+
targets: [
107+
{ platform: 'linux', arch: 'x64', nodeVersion: '25.7.0' },
108+
{ platform: 'darwin', arch: 'arm64', nodeVersion: '25.7.0' },
109+
{ platform: 'win', arch: 'x64', nodeVersion: '25.7.0' },
110+
],
111+
},
112+
})
113+
```
114+
115+
这会从 nodejs.org 下载目标平台的 Node.js 二进制文件,缓存到本地,并使用它来构建可执行文件。输出文件会带有平台和架构后缀:
116+
117+
```
118+
dist/
119+
cli-linux-x64
120+
cli-darwin-arm64
121+
cli-win-x64.exe
122+
```
123+
124+
### Target 选项
125+
126+
`targets` 数组中的每个 target 接受以下字段:
127+
128+
| 字段 | 类型 | 描述 |
129+
| ------------- | ------------------------------ | -------------------------------------- |
130+
| `platform` | `'win' \| 'darwin' \| 'linux'` | 目标操作系统(与 nodejs.org 命名一致) |
131+
| `arch` | `'x64' \| 'arm64'` | 目标 CPU 架构 |
132+
| `nodeVersion` | `string` | 使用的 Node.js 版本(必须 `>=25.7.0`|
133+
134+
:::warning
135+
当指定 `targets` 时,`seaConfig.executable` 选项会被忽略——将使用下载的 Node.js 二进制文件。
136+
:::
137+
138+
### 缓存
139+
140+
下载的 Node.js 二进制文件会缓存在系统缓存目录中:
141+
142+
- **macOS:** `~/Library/Caches/tsdown/node/`
143+
- **Linux:** `~/.cache/tsdown/node/`(或 `$XDG_CACHE_HOME/tsdown/node/`
144+
- **Windows:** `%LOCALAPPDATA%/tsdown/Caches/node/`
145+
146+
后续构建会复用缓存的二进制文件,无需重新下载。
147+
148+
## 平台说明
149+
150+
-**macOS** 上,可执行文件会自动进行临时签名(ad-hoc)以兼容 Gatekeeper。从非 macOS 主机交叉编译 macOS 目标时,签名会被跳过并显示警告。
151+
-**Windows** 上,会自动添加 `.exe` 扩展名。

docs/zh-CN/reference/cli.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,17 +248,19 @@ tsdown --copy public
248248

249249
## `--exe`
250250

251-
**[实验性]** 使用 Node.js SEA(单可执行应用)将输出打包为可执行文件
251+
**[实验性]** 使用 [Node.js 单个可执行程序](https://nodejs.org/api/single-executable-applications.html)将输出打包为独立可执行文件
252252

253-
此选项会使用 Node.js SEA 将输出打包为单个可执行文件。需要 Node.js 25.5.0 或更高版本,不支持 Bun 和 Deno 环境。
253+
此选项会将输出打包为单个可执行文件。需要 Node.js 25.5.0 或更高版本,不支持 Bun 和 Deno 环境。通过 `@tsdown/exe` 包支持跨平台构建
254254

255255
启用 `exe` 时:
256256

257-
- 默认输出格式从 `esm` 变更为 `cjs`除非支持 ESM SEA,即 Node.js >= v25.7.0)。
257+
- 默认输出格式从 `esm` 变更为 `cjs`(Node.js >= v25.7.0 支持 ESM 时除外)。
258258
- 默认禁用声明文件生成(`dts`)。
259259
- 禁用代码分割。
260260
- 仅支持单入口。
261261

262+
另请参阅 [可执行文件](../options/exe.md)
263+
262264
## `--exports`
263265

264266
自动生成 `package.json` 中的 `exports``main``module``types` 字段。

dts.snapshot.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@
103103
"DepPlugin": "declare function DepPlugin(_: ResolvedConfig, _: TsdownBundle): Plugin",
104104
"DepsConfig": "interface DepsConfig {\n neverBundle?: ExternalOption\n alwaysBundle?: Arrayable<string | RegExp> | NoExternalFn\n onlyAllowBundle?: Arrayable<string | RegExp> | false\n skipNodeModulesBundle?: boolean\n}",
105105
"DevtoolsOptions": "interface DevtoolsOptions extends NonNullable<InputOptions['devtools']> {\n ui?: boolean | Partial<StartOptions>\n clean?: boolean\n}",
106-
"ExeOptions": "interface ExeOptions {\n seaConfig?: Omit<SeaConfig, 'main' | 'output' | 'mainFormat'>\n fileName?: string | ((_: RolldownChunk) => string)\n}",
106+
"ExeOptions": "interface ExeOptions extends ExeExtensionOptions {\n seaConfig?: Omit<SeaConfig, 'main' | 'output' | 'mainFormat'>\n fileName?: string | ((_: RolldownChunk) => string)\n}",
107107
"ExportsOptions": "interface ExportsOptions {\n devExports?: boolean | string\n packageJson?: boolean\n all?: boolean\n exclude?: (RegExp | string)[]\n legacy?: boolean\n customExports?: Record<string, any> | ((_: Record<string, any>, _: { pkg: PackageJson; chunks: ChunksByFormat; isPublish: boolean }) => Awaitable<Record<string, any>>)\n inlinedDependencies?: boolean\n}",
108108
"Format": "type Format = ModuleFormat",
109109
"globalLogger": "Logger",

0 commit comments

Comments
 (0)