nodejs/node
Bootstrap and startup
This page traces what happens between int main() and the first line of app.js running. Owners: @nodejs/startup (per .github/CODEOWNERS).
Purpose
Boot Node.js: parse arguments, initialize V8, OpenSSL, ICU, and libuv, build the V8 isolate / context / Realm, run a JS bootstrap that sets up process and the global object, then dispatch to a "main script" appropriate for the invocation mode (file, REPL, eval, worker, test runner, etc.).
Directory layout
src/
node_main.cc // platform main(); calls node::Start
node.cc // node::Start, InitializeOncePerProcess, Bootstrap
node_main_instance.{cc,h} // NodeMainInstance: per-isolate driver
node_realm.{cc,h} // Realm and PrincipalRealm
node_snapshotable.{cc,h} // SnapshotBuilder + SnapshotData
node_snapshot_builder.h // ABI for embedders
node_v8_platform-inl.h // V8 platform shim
node_options.{cc,h} // CLI flag definitions
node_dotenv.{cc,h} // .env loader
node_config_file.{cc,h} // node-config-schema.json reader
node_task_runner.{cc,h} // npm-style script runner
node_builtins.{cc,h} // BuiltinLoader: compiles lib/**/*.js
node_revert.h // --security-revert flags
node_credentials.cc // POSIX uid/gid setup
lib/
internal/per_context/ // per-Realm per-Context JS init
primordials.js // captures JS builtins into `primordials`
domexception.js // DOMException
messageport.js // MessagePort glue
internal/bootstrap/
realm.js // sets up internalBinding, BuiltinModule
node.js // top-level bootstrap; sets up process, globals
shadow_realm.js // ShadowRealm boot
web/ // exposeLazy + Web API setup
switches/ // optional bootstrap (deprecation warnings,
// freeze intrinsics, etc.)
internal/process/
pre_execution.js // post-snapshot, pre-main setup
per_thread.js // bootstrap shared by main + workers
execution.js // runMain, executeUserEntryPoint
finalization.js // process.finalization
promises.js // unhandledRejection plumbing
task_queues.js // microtask + nextTick
warning.js // process.emitWarning
permission.js // permission model wiring
signal.js // signal handler bridge
internal/main/
run_main_module.js // node app.js
eval_string.js // node -e
eval_stdin.js // node piped stdin
repl.js // interactive REPL
check_syntax.js // node --check
inspect.js // node inspect
print_help.js // --help
test_runner.js // node --test
watch_mode.js // node --watch
worker_thread.js // worker bootstrap
embedding.js // embedder entry
mksnapshot.js // snapshot generator
prof_process.js // --prof-processKey abstractions
| Type / file | Role |
|---|---|
node::Start (src/node.cc) |
Top-level entry. Calls InitializeOncePerProcess, builds NodeMainInstance, runs. |
node::InitializeOncePerProcess (src/node.cc) |
Init V8 platform, OpenSSL, ICU, dotenv, large-pages, parses CLI flags. |
node::NodeMainInstance (src/node_main_instance.cc) |
Owns the V8 isolate and the principal Environment/Realm. |
node::Environment (src/env.cc) |
Per-thread state: libuv loop, options, inspector, performance state. |
node::Realm (src/node_realm.cc) |
Per-V8-context JS state: bindings, builtin loader, process. |
node::SnapshotBuilder::Generate (src/node_snapshotable.cc) |
Builds the embedded V8 startup snapshot at build time. |
node::builtins::BuiltinLoader (src/node_builtins.cc) |
Loads lib/**/*.js from the binary; backs internalBinding JS-side resolver. |
lib/internal/bootstrap/realm.js |
Creates internalBinding, BuiltinModule, process.binding allow list. |
lib/internal/bootstrap/node.js |
Sets up process, the global object, timers, async hooks dispatch. |
lib/internal/process/pre_execution.js |
Runs after snapshot deserialization; reads env/argv, installs inspector, runs --require. |
lib/internal/main/*.js |
Selected based on argv/flags; each is a tiny bootstrapper for a mode. |
How it works
sequenceDiagram
participant OS
participant nm as node_main.cc
participant Start as node::Start
participant InitOnce as InitializeOncePerProcess
participant Inst as NodeMainInstance::Run
participant Realm as Realm::BootstrapRealm
participant RJ as bootstrap/realm.js
participant NJ as bootstrap/node.js
participant PE as process/pre_execution.js
participant Main as main/<entry>.js
OS->>nm: argc, argv, envp
nm->>Start: node::Start
Start->>InitOnce: parse argv, init V8/OpenSSL/ICU
InitOnce-->>Start: shared platform ready
Start->>Inst: NodeMainInstance::Run
Inst->>Realm: enter principal Realm
alt Snapshot enabled (default)
Realm-->>Realm: deserialize V8 heap from embedded snapshot
Realm->>NJ: continue from post-bootstrap state
else --no-node-snapshot
Realm->>RJ: compile + run bootstrap/realm.js
RJ->>NJ: compile + run bootstrap/node.js
end
NJ->>PE: setupPreExecution(...)
PE-->>PE: parse env, set up inspector, --require
PE->>Main: choose main script
Main-->>OS: run user code, drive event loopThe snapshot path matters because most of the code in lib/internal/bootstrap/realm.js and lib/internal/bootstrap/node.js runs at build time when generating the snapshot. The deserialized heap already has process, the global object, the timer machinery, and the async-hook dispatch wired up.
Realm::BootstrapRealm
src/node_realm.cc is where C++ → JS handover happens:
- Loads
lib/internal/per_context/primordials.jsand friends into the V8 context. - Compiles
internal/bootstrap/realm.jsviaBuiltinLoader::CompileAndCall. That installsinternalBinding,process.binding, and theBuiltinModuleregistry on the realm. - Compiles
internal/bootstrap/node.js(the "principal" path; a parallel path for ShadowRealms goes tobootstrap/shadow_realm.js). - Calls into
bootstrap/switches/based on CLI flags (e.g.--frozen-intrinsics). - Hands control back to C++, which calls
setupPreExecutionand thenloadEnvironment, which dispatches to one oflib/internal/main/*.js.
pre_execution.js
Anything that depends on runtime state lives here, not in bootstrap:
- Read
--inspect,--inspect-brkand start the inspector agent. - Read
--requiremodules and run them. - Set up the source-map cache (
lib/internal/source_map/). - Apply
--frozen-intrinsicsif requested. - Honour
process.dlopenoverrides for the legacyaddonsAPI.
This is also the file you edit when adding a new CLI flag that needs an early hook.
Main script selection
src/node.cc (LoadEnvironment and friends) decides which script in lib/internal/main/ to run based on Environment::Options:
| Condition | Main script |
|---|---|
--eval provided |
lib/internal/main/eval_string.js |
--check |
lib/internal/main/check_syntax.js |
--print |
eval_string.js with print=true |
| Stdin script (no positional) | lib/internal/main/eval_stdin.js |
--interactive or no script and stdin is a TTY |
lib/internal/main/repl.js |
--inspect subcommand |
lib/internal/main/inspect.js |
--help |
lib/internal/main/print_help.js |
--test |
lib/internal/main/test_runner.js |
--watch |
lib/internal/main/watch_mode.js |
| Inside Worker | lib/internal/main/worker_thread.js |
| Embedder API | lib/internal/main/embedding.js |
--build-snapshot |
lib/internal/main/mksnapshot.js |
--prof-process |
lib/internal/main/prof_process.js |
| Default | lib/internal/main/run_main_module.js |
Each main script is small (≤ a couple hundred lines) and orchestrates the entry-point resolution + invocation in terms of internal/modules/run_main and friends.
Snapshot pipeline
graph LR cfg[node.gyp + tools/snapshot/] --> mks[node_mksnapshot binary] perc[lib/internal/per_context/*.js] --> mks rjs[lib/internal/bootstrap/realm.js] --> mks njs[lib/internal/bootstrap/node.js] --> mks mks --> blob[startup snapshot blob] blob --> nodebin[node binary] nodebin --> deser[Realm deserializes blob]
When a snapshot is present (default for the principal Realm), Realm::DeserializeProperties swaps in the prebuilt heap. --no-node-snapshot forces the slow path. --build-snapshot lets users build their own snapshots via the Snapshot Builder API; the JS side lives in v8.startupSnapshot.* (lib/v8.js).
Integration points
- CLI flags: defined in
src/node_options.ccviaaddOption(...). Most flags are also exposed to JS throughinternalBinding('options'). Any new CLI flag goes here. - Embedded JS:
tools/js2c.ccconsumeslib/**/*.jsanddeps/**/*.jslisted innode.gypand producesnode_javascript.cc. The list of bundled files is innode.gyp'slibrary_filesvariable. processobject: assembled inlib/internal/bootstrap/node.jsandlib/internal/process/per_thread.js. Worker threads runper_thread.jsseparately so they get a parallelprocessshape.- dotenv:
src/node_dotenv.ccparses--env-filebefore bootstrap; results are exposed asprocess.env. - Config file:
--experimental-default-config-fileand--config-filego throughsrc/node_config_file.ccagainstdoc/node-config-schema.json. - Task runner:
node --run <script>runs npm-style scripts; implementation insrc/node_task_runner.cc.
Entry points for modification
To add a CLI flag: edit src/node_options.cc and src/node_options.h; if the flag affects bootstrap-only state, also touch lib/internal/process/pre_execution.js and doc/api/cli.md.
To add a new main script mode: create lib/internal/main/<name>.js, register it in node.gyp's library_files, dispatch from src/node.cc (LoadEnvironment).
To change what gets snapshot-frozen: anything in lib/internal/bootstrap/** and the internal/per_context/** files runs while building the snapshot. Read those files' headers — they list strict rules.
For the JS side of snapshots used by user code, see lib/v8.js startupSnapshot.addDeserializeCallback/addSerializeCallback and src/node_snapshotable.cc.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.