Create cross-platform single-executables for Node.js projects. Windows is not supported.
Banderole bundles your Node.js app, all dependencies, and a portable Node binary into a single native executable. On first launch, it unpacks to a cache directory for fast subsequent executions.
Unlike Node.js SEA or pkg, banderole handles complex projects with dynamic imports and non-JavaScript files without requiring patches — it ships a stock Node binary and your real dependency tree rather than a patched runtime and a virtual filesystem.
Measured on linux-x64, Node 22.17.1, against @yao-pkg/pkg 6.22.0 building the same app. Startup is the median of 60 interleaved runs (targets rotate every round, so machine drift affects both equally).
| trivial app | app with deps (express, lodash, chalk, dayjs) |
|||
|---|---|---|---|---|
| banderole | pkg | banderole | pkg | |
| Executable size | 33,416,408 B | 74,306,703 B | 35,366,424 B | 77,346,956 B |
| Startup (median) | 16.1 ms | 25.7 ms | 53.1 ms | 78.8 ms |
| Resident processes | 1 | 1 | 1 | 1 |
| Resident memory (RSS) | 43.8 MiB | 49.4 MiB | 66.4 MiB | 76.2 MiB |
pkg's size floor is the uncompressed Node binary it embeds, which its --compress option does not touch. banderole compresses the Node binary with zstd and ships only the executable itself, so a bundle is roughly half the size.
The trade-off is the first launch, which unpacks the payload into the cache directory: 117 ms versus pkg's 30 ms for the trivial app. Every launch after that takes the warm path above.
First-launch extraction grows with the dependency tree; steady-state launch does not, because it is one file read followed by exec.
node_modules |
files | bundle time | first launch | every later launch |
|---|---|---|---|---|
| — | 0 | 16 s | 159 ms | 19.4 ms |
| 14 MB | 5,500 | 18 s | 224 ms | 22.4 ms |
| 68 MB (real npm tree) | 9,725 | 19 s | 316 ms | 71.2 ms |
| 143 MB | 27,500 | 20 s | 348 ms | 23.3 ms |
| 763 MB | 110,000 | 25 s | 862 ms | 22.9 ms |
A 763 MB dependency tree still unpacks in under a second, and warm launch is flat across the whole range. (The 68 MB row is a real npm install; its higher warm figure is Node resolving express/lodash/dayjs at runtime, not launcher overhead. The other rows are generated trees, which compress better than real ones.)
Banderole requires a Rust toolchain (cargo, rustc, rustup) and a working C linker — cc/gcc/clang on Unix, MSVC on Windows — because it compiles a small launcher for each bundle.
cargo install banderole# Bundle a project using the project name
banderole bundle /path/to/project
# Bundle with custom output path
banderole bundle /path/to/project --output /path/to/output/executable
# Bundle with custom name
banderole bundle /path/to/project --name my-app
# Bundle with both custom output and name
banderole bundle /path/to/project --output /path/to/my-app --name my-app- Support Linux, MacOS, and Windows for both x64 and arm64 architectures.
- Support custom node.js version based on project's
.nvmrcand.node-version - Support TypeScript projects with automatic detection of compiled output directories
- Support workspaces (only pnpm workspaces tested)
- Only the executable has permissions to read and execute bundled files
MIT