Skip to content

exe support: roadmap & tracking #776

@hyf0

Description

@hyf0
  • Decouple exe from format — make it a boolean option (--exe) layered on top of --format
  • Native addon / WASM support — map non-JS output to sea-config assets
  • Cross-platform builds — download target platform's Node.js binary
  • Code signing — macOS notarization, Windows Authenticode
  • Watch mode — rebuild exe on file changes
  • Copy-package — automatically include unbundleable packages and their dependencies (low priority)

1. Decouple exe from format

Currently exe is a format (--format exe) that's internally mapped to cjs. Instead, exe should be a standalone boolean option:

tsdown ./src/index.ts --format esm --exe
tsdown ./src/index.ts --format cjs --exe

This way the user controls the actual module format, and --exe just enables the SEA post-build step. With Node.js landing ESM SEA support (nodejs/node#61813), tsdown would pass "mainFormat": "module" or "commonjs" to sea-config.json based on the chosen format. No special mapping needed.

Note: ESM mode currently cannot be used with useCodeCache or useSnapshot — tsdown should warn when these conflict.

2. Native addon / WASM support

It's Rolldown's responsibility to handle .node and .wasm files during bundling. What tsdown needs to do is map the non-JS output chunks into sea-config assets and inject a runtime shim so they're loaded via sea.getAsset() + process.dlopen() instead of filesystem reads.

3. Cross-platform builds

Build for linux/mac/windows from a single host by downloading the target platform's Node.js binary. For best startup performance, compiling on the target platform is still recommended. When cross-compiling, tsdown should warn and force-disable useCodeCache and useSnapshot since they are platform-specific and would crash on a different platform.

4. Code signing

Currently only ad-hoc macOS codesign. Industry-standard signing for distributable binaries:

  • macOS: Developer ID signing + Apple notarization (codesign --sign "Developer ID" + notarytool submit). Without notarization, Gatekeeper blocks the binary for end users.
  • Windows: Authenticode signing via signtool.exe with an EV/OV code signing certificate. Unsigned exes trigger SmartScreen "unknown publisher" warnings.
  • Linux: No mandatory signing, but GPG signatures are common for package distribution.

5. Watch mode

Currently disabled. Could rebuild the exe on file changes, though SEA build time (seconds) makes this less practical. Might still be useful for CLI tool iteration.

6. Copy-package — automatic handling of unbundleable packages

Some packages can't be bundled — they contain native .node addons, .wasm modules, or use patterns like dynamic require() that break during bundling. These packages also have their own dependencies, so at runtime the entire dependency branch needs to be present in node_modules.

Rolldown's copy module type works at the single file level — it copies one file and rewrites one import path. It can't handle this case because:

  • A package is a directory with package.json, multiple entry points, and internal require() calls between its own files
  • The package's dependencies also need to be present, forming a tree that must be resolved from node_modules
  • The node_modules layout matters — packages resolve their own deps relative to their location

This requires package-manager-level dependency resolution, which is beyond what a bundler plugin should do. copy-package automates this at the tsdown layer:

  1. User marks a package for copy (API/naming TBD)
  2. tsdown externalizes the package from the bundle
  3. tsdown walks its dependency graph from node_modules, following the full branch
  4. The entire branch (pkg-a → pkg-b → pkg-c → ...) is copied into the output's node_modules
  5. The bundled code's require('pkg-a') resolves to the copied location at runtime

In exe mode, these copied packages would additionally be mapped into sea-config.json assets.

tsdown handles the complexity here — dependency graph resolution, node_modules layout, and ensuring the copied branch is self-contained.

Low priority. The complexity of walking and copying dependency trees may not justify the use case. Worth revisiting once the more common exe scenarios (sections 1–5) are solid.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions