Runtime#
The runtime is the engine plus the host. The same V8 engine
(used by Chrome, Edge, Node, Deno, Bun, Electron) executes the
operator’s source after a JIT-compile pass. The host wraps V8
with the APIs the script can call: DOM in the browser, fs /
net / crypto in Node, similar but distinct surfaces in
Deno and Bun.
For the language itself, see Syntax. For the toolchain that drives the runtime, see Tooling.
Runtimes#
Runtime |
Notes |
|---|---|
Node.js |
The reference. V8 + libuv. CommonJS plus ESM. Largest ecosystem, slowest startup. |
Deno |
V8 + Rust. ESM only. URL-based imports, permission
sandbox by default, |
Bun |
JavaScriptCore + Zig. Node-compatible; ships its own bundler, package manager, test runner, transpiler. Two orders of magnitude faster startup than Node. |
Browsers |
V8 (Chromium), SpiderMonkey (Firefox), JavaScriptCore (Safari). DOM, Fetch, Web Workers, IndexedDB; no filesystem. |
Embedded |
JerryScript, Espruino, Quickjs. JavaScript on microcontrollers and IoT. |
node --version / deno --version / bun --version /
process.versions print runtime identity at runtime.
Modules#
ES Modules (ESM) is the standard. CommonJS (CJS, the original Node format) is still everywhere.
ESM#
// greet.mjs (or .js with "type":"module" in package.json)
export function greet(name) { return `hello, ${name}`; }
export default greet;
// main.mjs
import greet, {greet as greetFn} from "./greet.mjs";
ESM is async-load, statically analysable, supports top-level
await, uses URL-style specifiers, and is the default in
Deno, Bun, browsers, and Node when "type":"module" is set in
package.json.
CommonJS#
// greet.cjs
function greet(name) { return `hello, ${name}`; }
module.exports = greet;
// main.cjs
const greet = require("./greet");
CommonJS is synchronous load, dynamically resolved, no top-level
await. Use it when the operator has to, prefer ESM when the
operator can.
Interop. Node can import a CommonJS module (it goes through
the default export); require of an ESM module is allowed
synchronously from CJS as of Node 22.
Resolution#
Node walks node_modules upward from the current file looking
for the package name. Subpaths read package.json’s
exports map; the bare specifier "foo" resolves to
foo/package.json’s main (or exports["."]).
{
"name": "myscan",
"type": "module",
"exports": {
".": "./src/index.js",
"./util": "./src/util.js"
}
}
import scan from "myscan"; // resolves via exports["."]
import util from "myscan/util"; // resolves via exports["./util"]
For local files write the full extension
(./util.js) under ESM in Node; CommonJS allowed extensionless
imports as a convenience.
package.json#
The metadata file every Node project carries. The operator
maintains name, version, type, main / exports,
scripts, dependencies, devDependencies, and
engines.
{
"name": "myscan",
"version": "0.1.0",
"type": "module",
"scripts": {
"start": "node src/index.js",
"test": "vitest",
"lint": "eslint src/"
},
"dependencies": {"undici": "^7.0.0"},
"devDependencies": {"vitest": "^2.0.0"},
"engines": {"node": ">=20"}
}
npm run start (or pnpm start, bun start) executes
the script.
globalThis and the global object#
globalThis is the universal handle on the global object;
window in browsers, global in Node, self in Web
Workers. Use globalThis when writing code that
must run in more than one host.
globalThis.AbortController ??= class { /* polyfill */ };
ES modules are strict and have their own module scope; top-level
var does not pollute the global. Top-level await is
allowed and the runtime resumes the rest of the module after it
settles.
process#
In Node, process is the handle on the running process:
arguments, environment, stdio, exit code, signal handlers.
process.argv; // ["node", "script.js", ...]
process.env.HOME; // env vars
process.cwd(); // working directory
process.exit(0); // exit with code
process.on("SIGINT", () => { cleanup(); process.exit(130); });
process.on("unhandledRejection", e => log.fatal(e));
Deno exposes the same surface through Deno.*; Bun adds a
Bun.* namespace alongside Node compatibility.
The event loop#
Same loop in every runtime. Node layers libuv on top of V8; browsers use the host’s loop. See Concurrency for the task / microtask model.
Avoid blocking the loop. Synchronous file reads,
unbounded JSON.parse on a huge buffer, or a long busy loop
all freeze every concurrent request.
Garbage collection#
V8 has a generational GC: young objects get collected fast,
survivors get promoted to an old space that gets collected less
often. Do not call the GC; --expose-gc plus
global.gc() exists but is for tests only.
Heap leaks usually come from a long-lived Map / Set that
keeps holding references; WeakMap and WeakRef let the GC
reclaim values when nothing else references them.
References#
Tooling for
npm/pnpm/yarnand the rest of the toolchain.Concurrency for the event loop in depth.
Variables for module scope and
globalThis.