How Node.js Single Executable Applications Work
This article explains how the Single Executable Applications (SEA)
feature in Node.js allows developers to bundle a JavaScript application
and the Node.js runtime into a single, redistributable binary. It covers
the underlying mechanism of the node:sea built-in module,
the step-by-step process of generating a standalone executable, and how
the runtime executes the embedded code without requiring an external
Node.js installation on the target machine.
Understanding Single Executable Applications (SEA)
Historically, running a Node.js application required the target machine to have the Node.js runtime installed. The Single Executable Applications (SEA) feature, introduced natively in Node.js, eliminates this dependency. It allows developers to package their JavaScript code directly into a copy of the Node.js binary itself. When a user runs the resulting executable, the embedded Node.js runtime launches and immediately executes the bundled JavaScript code.
The Architecture of SEA
The SEA mechanism relies on injecting a preparation blob containing the application code into a dedicated section of the Node.js executable.
Instead of compiling JavaScript into machine code, SEA leverages the
fact that the Node.js binary is designed to look for an embedded asset
block inside its own executable file. If this resource block is present,
Node.js bypasses its default behavior (which is to look for a
command-line script argument or an index.js file) and
instead executes the code stored within the embedded block.
Step-by-Step Execution Workflow
Creating a single executable application involves a distinct four-step pipeline.
1. Bundling the Source Code
Before creating an executable, all application code and its
dependencies must be bundled into a single JavaScript file. Since the
executable only reads a single entry point, tools like esbuild, Webpack,
or Rollup are typically used to consolidate the project into a single
dist/index.js file.
2. Creating the Configuration File
Node.js requires a JSON configuration file to define how the
executable should be built. This file specifies the input path of the
bundled JavaScript and the output path for the preparation blob. A
typical sea-config.json looks like this:
{
"main": "dist/index.js",
"output": "sea-prep.blob"
}3. Generating the Preparation Blob
Using the configuration file, developers run a Node.js command to compile the JavaScript code into a binary preparation blob:
node --experimental-sea-config sea-config.jsonThis command processes the entry point, checks for syntax, and
packages the code into the sea-prep.blob file.
4. Injecting the Blob into the Binary
To create the final executable, a copy of the official Node.js executable is made. The preparation blob is then injected into this copy.
Depending on the operating system, the injection process varies: *
Windows: The blob is resource-aligned and written into
the binary using tools like signtool or PowerShell scripts.
* macOS / Linux: A utility called postject
is used to inject the blob into the binary’s asset segment (such as
__bundle on macOS or .note.NODE_JS_LAND on
Linux).
Once injected, the executable is ready to run. On macOS and Windows, the final binary must be re-signed to satisfy OS-level security policies.
How the Binary Runs
When the final executable is launched, the embedded Node.js runtime initializes. During initialization, Node.js checks its binary headers for the injected SEA segment.
If the segment is detected: 1. Node.js extracts the bundled
JavaScript code into memory. 2. It configures the V8 engine and
registers internal APIs. 3. It executes the JavaScript code within the
embedded environment. 4. Command-line arguments passed to the executable
are forwarded directly to the running JavaScript application via
process.argv.
Through this process, Node.js delivers a self-contained, dependency-free executable suitable for distribution.