JavaScript — Reference
Source: https://tc39.es/ecma262/
JavaScript
- Created: 1995 by Brendan Eich at Netscape (originally “Mocha”, then “LiveScript”); standardized as ECMAScript by Ecma International TC39
- Latest stable: ECMAScript 2025 ratified mid-2025; ES2026 in flight; latest editor’s draft tracks https://tc39.es/ecma262/. Node.js 26 (Current, 2026-05-05), Node.js 24 LTS “Krypton” (2025-05-06)
- Paradigms: multi-paradigm — prototype-based OO, functional, imperative, event-driven; classes are syntactic sugar over prototypes
- Typing: dynamic, weakly typed (with implicit coercion). Static layer via TypeScript or JSDoc.
- Memory: garbage collected — generational, mark-sweep + scavenger; per-engine details (V8, SpiderMonkey, JavaScriptCore)
- Compilation: JIT-compiled in modern engines (V8 Ignition+TurboFan/Maglev, SpiderMonkey IonMonkey/WarpMonkey, JSC LLInt+Baseline+DFG+FTL). Originally interpreted.
- Primary domains: browser frontend, server-side (Node.js, Deno, Bun), desktop (Electron, Tauri), mobile (React Native, Capacitor), serverless / edge, embedded scripting
- Official docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript
At a glance
- Standard: ECMA-262, annual yearly snapshots since ES2015. Living spec at https://tc39.es/ecma262/.
- Engines: V8 (Chrome, Node.js, Deno, Edge), SpiderMonkey (Firefox), JavaScriptCore / Nitro (Safari, Bun uses JSC), Hermes (React Native), QuickJS (embedded).
- Runtimes: browsers, Node.js, Deno (TS-first, secure-by-default), Bun (Zig + JSC, npm-compatible, fast), Cloudflare Workers (V8 isolates), edge platforms (Vercel, Netlify, Fastly Compute).
- Governance: TC39 with stage process 0-4; Stage 4 = in the next yearly snapshot.
Getting started
Install:
- Browsers: built in.
- Node.js: https://nodejs.org/ — installer, or version manager:
nvm(POSIX),fnm(Rust),volta,nvs(Windows). Bun and Deno include their own version managers (bun upgrade,deno upgrade). - For Node 24+ projects, prefer Corepack to pin pnpm/yarn versions.
Hello world (browser):
<script>console.log("Hello, world!");</script>Hello world (Node.js):
// hello.mjs
console.log("Hello, world!");Run: node hello.mjs.
Project layout (Node, ESM):
myapp/
package.json # "type": "module"
src/
index.js
test/
index.test.js
node_modules/
package-lock.json
Package managers:
npm(bundled with Node),pnpm(content-addressed store, fast),yarn(Berry / v4, PnP),bun(built-in).- Lockfiles:
package-lock.json,pnpm-lock.yaml,yarn.lock,bun.lockb.
REPL / playground: node or node --experimental-repl-await. Browser DevTools console. Online: https://playcode.io, https://stackblitz.com, MDN’s “Try it” boxes.
Basics
Primitives: number (IEEE 754 double, 53-bit safe int), bigint (123n), string (UTF-16), boolean, symbol, undefined, null. Plus object (incl. arrays, functions, dates, maps).
Variables / scope:
const x = 10; // block-scoped, immutable binding (value can mutate if object)
let y = 1; // block-scoped, reassignable
var z = 2; // function-scoped, hoisted (legacy — avoid)Closures capture by reference; this is dynamic (fn.call, arrow functions inherit lexical this).
Control flow: if/else, for, for...of (iterables), for...in (enumerable keys, avoid for arrays), while, do/while, switch, try/catch/finally, throw.
Functions:
function add(a, b = 0, ...rest) { return a + b + rest.length; }
const sub = (a, b) => a - b; // arrow — no own `this`, no `arguments`
async function fetchJson(url) { return (await fetch(url)).json(); }
function* range(n) { for (let i = 0; i < n; i++) yield i; } // generatorStrings: template literals (backtick) with ${expr}, tagged templates, raw strings via String.raw.
const name = "world";
console.log(`Hello, ${name}!`);Built-in collections: Array, Object (string-keyed), Map, Set, WeakMap, WeakSet, WeakRef, Date, RegExp, typed arrays (Uint8Array, Float32Array, etc.), ArrayBuffer, SharedArrayBuffer, DataView.
Intermediate
Types & inference: none natively. JSDoc + // @ts-check gives editor-grade typing without TS. Most modern Node/Deno projects use TypeScript directly. Type erasure proposal (Stage 1) would let JS engines parse-and-strip TS-style annotations.
Modules: ESM (import/export, .mjs or "type":"module") is the standard. CommonJS (require/module.exports) is legacy but pervasive in Node. Dynamic import() returns a Promise. Import attributes for non-JS resources: import data from "./d.json" with { type: "json" }.
Errors: throw any value, but throw Error subclasses (TypeError, RangeError, SyntaxError, custom class MyError extends Error). try/catch (err) — err is unknown style; check with instanceof. AggregateError for Promise.any. Error.cause for chaining.
Concurrency:
- Single-threaded event loop per realm. Tasks vs microtasks (
queueMicrotask). Promise(.then/await/Promise.all/Promise.allSettled/Promise.race/Promise.any/Promise.try).- Async iterators (
for await ... of) and async generators. - Workers:
Worker(browsers),worker_threads(Node),Web Worker/Service Worker,SharedWorker. SharedArrayBuffer+Atomicsfor shared-memory parallelism.AbortController/AbortSignalfor cancellation.
File I/O / networking:
- Browser:
fetch,WebSocket,XMLHttpRequest(legacy),IndexedDB,OPFS. - Node:
node:fs/promises,node:fs,node:net,node:http,node:https,node:dgram, plus a built-infetchsince 18,node:testrunner,node:sqlite(24+). - Deno/Bun: web-standard APIs first (
fetch,Request,Response).
Stdlib highlights: Intl (i18n: Intl.NumberFormat, Intl.DateTimeFormat, Intl.Segmenter, Intl.Collator, Intl.DurationFormat ES2024), URL / URLPattern, TextEncoder/TextDecoder, crypto.subtle (Web Crypto), structuredClone, Temporal (Stage 3, shipping in Firefox 139+/Safari TP/Node 24+ behind flag — finally fixes Date).
ES2024 / ES2025 highlights:
Promise.withResolvers()— get{ promise, resolve, reject }without the new-Promise dance.Array.fromAsync(asyncIterable)— sibling ofArray.fromfor async iteration.Object.groupBy(items, keyFn)/Map.groupBy(items, keyFn)— built-ingroupby.Setmethods:union,intersection,difference,symmetricDifference,isSubsetOf,isSupersetOf,isDisjointFrom.await using/using(Stage 3, ES2026 likely) — explicit resource management; like Pythonwith.using db = await connect()callsdb[Symbol.dispose]()(or async variant) at scope exit.- Iterator helpers (ES2025) —
.map() / .filter() / .take() / .drop() / .flatMap() / .reduce() / .toArray()onIterator.prototype. Lazy by default. - Float16Array (ES2025) — half-precision typed array for ML / WebGPU interop.
- RegExp
/vflag (ES2024) — set notation, intersection, set difference within character classes. - JSON modules + import attributes (ES2025):
import data from "./d.json" with { type: "json" }. - Top-level
awaitin ES modules — load configs / await connections before exports settle. - Decorators Stage 3 (shipping behind flags) — see God Mode below.
Advanced
Memory model & GC:
- V8: generational (young / old), Scavenger for young, Mark-Compact for old, Orinoco concurrent / parallel collector. Pointer compression on 64-bit (4-byte tagged pointers within a 4 GB cage — 30-40% heap size reduction). Conservative stack scanning since 11.2. Major GC pauses typically 1-10 ms in production Node.
- Tune Node:
--max-old-space-size=4096(heap cap in MB),--max-semi-space-size(new-gen size; bigger = fewer scavenges, more memory),--gc-interval,--expose-gcforglobal.gc(). Container-aware since Node 12 — respects cgroup memory limits. - Inspect heap:
--inspect, Chrome DevTools Heap Snapshot,clinic.js,0x(flamegraphs),heapdump,memwatch-next. - V8 tier-up pipeline (2024-26): Ignition (bytecode interpreter) → Sparkplug (non-optimizing baseline JIT, fastest tier-up) → Maglev (mid-tier optimizing JIT, enabled by default in Chrome 117+) → TurboFan (top-tier optimizing JIT, Sea-of-Nodes IR). Liftoff handles WASM warmup before TurboFan. Each tier trades compile time vs steady-state perf.
Concurrency deep dive:
- Microtask queue (Promises) drains between macrotasks.
Atomics.wait/notifyfor futex-style sync overSharedArrayBuffer.- Worker pools via
piscina(Node). AsyncContext(Stage 3 TC39) — async-aware context propagation.- Node
node:clusterfor multi-process;worker_threadsfor CPU-bound.
FFI / interop:
- Node: N-API / node-addon-api (stable C ABI), Node-API with
napi-rs(Rust),neon(Rust), legacynan. WASI for portable native modules. - Deno:
Deno.dlopendirect FFI to.so/.dll/.dylib. - Bun:
bun:ffidirect FFI. - Browser: WebAssembly (
WebAssembly.instantiate),Emscripten,wasm-bindgen(Rust).
Reflection / introspection: Object.keys/getOwnPropertyDescriptors/getPrototypeOf/getOwnPropertyNames, Reflect.*, Symbol.iterator/asyncIterator/hasInstance/toPrimitive, instanceof, typeof, Function.prototype.toString returns source.
Performance tuning:
- V8 inspection:
--trace-opt,--trace-deopt,--prof,--prof-process,--print-bytecode,--allow-natives-syntaxthen%OptimizeFunctionOnNextCall(fn). - Tools: Chrome DevTools Performance / Memory,
node --inspect,clinic.js doctor/flame/bubbleprof,0x,autocannon(HTTP load),mitata/tinybench(microbenchmarks). - Avoid hidden-class polymorphism — initialize objects with the same property order; don’t add/delete props on hot paths.
God mode
Proxy & Reflect: intercept any object operation.
const audited = new Proxy(target, {
get(t, p, r) { console.log("get", p); return Reflect.get(t, p, r); },
set(t, p, v, r) { console.log("set", p, v); return Reflect.set(t, p, v, r); },
});Underpins Vue 3 reactivity, immer, MobX, observable proxies.
Iteration & generator protocols: any object implementing [Symbol.iterator]() or [Symbol.asyncIterator]() works in for...of. Generators support .return() / .throw() for cooperative cancellation.
V8 internals: hidden classes (Maps), inline caches (ICs), CodeStubAssembler / Torque for builtins, Sea-of-Nodes IR. --print-code dumps machine code for hot functions. Sparkplug → Maglev → TurboFan tier-up.
AsyncContext: propagate context across await boundaries without monkeypatching. Replaces domain / async-local-storage hacks.
SharedArrayBuffer + Atomics: the only path to true shared-memory parallelism. Requires COOP/COEP cross-origin isolation in browsers. Atomics.waitAsync is non-blocking.
BigInt + Atomics: BigInt64Array works with Atomics; useful for lock-free 64-bit counters and futexes from WASM.
Eval scope tricks: direct eval("x = 1") sees / mutates caller’s local scope; indirect (0, eval)("x = 1") runs in global scope. new Function(...) always global. Both are usually the wrong answer.
Realms / iframes: different realms have different Array, so arr instanceof Array can lie. Use Array.isArray(arr).
Decorators (Stage 3, ES2023+ shipping):
function logged(value, { kind, name }) {
if (kind === "method") return function (...args) { console.log(name); return value.apply(this, args); };
}
class C { @logged greet() {} }Engine embedding: V8 embedder API (v8::Isolate, v8::Context), JSC API (JSContext, JSValue), QuickJS for tiny embeds, Hermes for AOT-compiled bytecode (React Native).
Bytecode:
- V8 Ignition: stack-based, viewable via
--print-bytecode. - JSC LLInt: low-level interpreter, also visible.
- Hermes: AOT to
.hbcbytecode, ship that instead of JS.
Runtime landscape (2024-2026):
| Runtime | Engine | Differentiator | Use when |
|---|---|---|---|
| Node.js 24 LTS / 26 | V8 | npm ecosystem, deepest API surface, built-in test runner + --watch + --env-file + node:sqlite (24+) | Anything that needs broad pkg compat |
| Bun 1.2+ | JSC + Zig | Native TS, all-in-one (runtime + bundler + test + pm), 3-10x faster install, native SQLite + Redis + S3 in 1.2+, full Node compat | Greenfield, dev speed matters |
| Deno 2.0+ | V8 + Rust | Secure-by-default (perms), npm compat (Oct 2024 v2), native TS, web-standard APIs, JSR registry | Scripts, edge, security-first |
| Cloudflare Workers | V8 isolates | <1ms cold start, runs at 300+ POPs, no Node API surface | Edge functions |
| Hermes | (custom) | AOT bytecode, no JIT, tiny runtime | React Native (default since 0.70) |
QuickJS / txiki | QuickJS | <1MB embeddable interpreter | Embedded scripting |
Build tooling (2024-2026):
- Vite 6+ — dominant dev server; Rolldown (Rust port of Rollup) replacing Rollup as the production bundler in Vite 6.0+. ~5-10x faster prod builds.
- esbuild — Go-based, the speed standard, still used inside many tools.
- Rspack (ByteDance) — Rust port of webpack, drop-in compatibility, used by ByteDance + Microsoft.
- Turbopack (Vercel) — Rust, incremental, Next.js default in 15+ (stable for dev, beta for build through early 2026).
- swc — Rust-based TS/JS compiler, used by Next.js, Deno, Parcel.
- oxc / oxlint — Boshen’s Rust toolchain; oxlint is 50-100x faster than ESLint on the same rule subset.
- Biome (formerly Rome) — Rust, all-in-one fmt + lint, near-Prettier compatibility, 25x faster.
- Bun’s bundler — built in, JSC + Zig, comparable speed to esbuild.
Framework state (2024-2026):
- React 19 (Dec 2024 stable): Server Components GA, Actions /
useActionState/useFormStatus,use(promise)hook, native ref-as-prop,<Context>directly as provider, document metadata support. Compiler (react-compiler) auto-memoizes — replaces mostuseMemo/useCallback/memo. - Vue 3.5+: reactive props destructuring stable,
useTemplateRef, deferred teleport, generic components. - Svelte 5 (Oct 2024): runes (
$state,$derived,$effect) — universal reactivity model, used in components AND.svelte.jsmodules. - SolidJS 1.9+ — fine-grained reactivity, no VDOM; SolidStart 1.0 (2024) ships.
- Qwik 2.0 — resumability instead of hydration; partnered with Builder.io.
- Astro 5+ (Nov 2024) — Server Islands, Content Layer API, server-rendered partial hydration.
- Remix → React Router 7 (Nov 2024) — merged, framework mode = Remix, library mode = RR.
- Next.js 15 — async request APIs, React 19 baseline, Turbopack stable for dev, stable Partial Prerendering.
- Nuxt 3.14+ — Nitro 2.10, hybrid rendering, shared types across server/client/edge.
- TanStack Start — type-safe full-stack on TanStack Router (beta through 2026).
Idioms & style
- Naming:
camelCasefor vars/functions,PascalCasefor classes/constructors,SCREAMING_SNAKEfor module-level constants,_private(convention),#truly-private(class private fields, ES2022). - Formatters: Prettier (de facto), Biome (Rust, all-in-one fmt+lint, fast),
dprint. - Linters: ESLint (v9+ flat config), Biome, oxlint (Rust, fast subset). Common configs:
eslint-config-airbnb,@typescript-eslint,eslint-plugin-import. - Idiomatic patterns:
constby default,letonly when reassigning, nevervar.===/!==over==/!=(avoid coercion).- Destructuring + default params:
function f({ a = 1, b } = {}) {}. - Async/await over raw Promise chains.
- Optional chaining
?.and nullish coalescing??over&&/||for null-vs-falsy distinctions. - Module side effects only when intentional; prefer pure exports.
- Avoid
classwhen a closure-returning factory is enough.
- What reviewers flag:
var, missingawait, floating Promises (no-floating-promisesrule),JSON.parse(JSON.stringify(x))for cloning (usestructuredClone), mutating shared state in arrow callbacks, broadcatch (e) {}.
Ecosystem
| Domain | Tools |
|---|---|
| Frontend | React, Vue, Svelte / SvelteKit, SolidJS, Angular, Qwik, Astro, Lit, Preact |
| Meta-frameworks | Next.js, Nuxt, SvelteKit, Remix / React Router 7, Astro, Qwik City |
| Server | Express, Fastify, Koa, Hono, NestJS, AdonisJS, ElysiaJS (Bun) |
| Build / bundle | Vite (Rolldown-based 2026), webpack, esbuild, Rolldown, Turbopack, Parcel |
| Type-aware build | tsc, swc, Babel, oxc |
| Test | Vitest, Jest, node:test, Mocha, Playwright (e2e), Cypress, Testing Library |
| State | Zustand, Redux Toolkit, Jotai, MobX, Pinia (Vue), Effector |
| ORM / DB | Prisma, Drizzle, Kysely, TypeORM, MikroORM, Mongoose |
| Mobile | React Native, Expo, Capacitor, NativeScript |
| Desktop | Electron, Tauri (Rust shell, JS frontend), Neutralino |
| Notable users | Google (V8, Angular), Meta (React, RN, Hermes), Netflix, Microsoft (TS, VS Code), Vercel, Stripe |
Performance & profiling deep dive
- Microbenchmarks:
mitata(Bun/Node, sub-ns precision),tinybench,benchmark.js(legacy). Always run on--predictableV8 builds for low noise. - Load testing:
autocannon(HTTP, Node-based, 100k+ req/s),k6(Go, scriptable in JS),wrk2,oha(Rust). Bombardier replaced wrk for many teams. - Flame graphs:
0x(Node, drop-in flamegraphs),clinic.js(doctor/flame/bubbleproffor async hot spots),speedscope(web viewer for any sampling profile),node --cpu-prof+ Chrome DevTools. - Hidden-class polymorphism: V8 builds a tree of “Maps” (hidden classes) per property-add sequence. Initialize all props in the constructor, in the same order, with the same types — keeps inline caches monomorphic. Adding props after
newcreates a transition; deleting props falls to “dictionary mode” (~10x slower property access). - Megamorphic call sites: if a
.method()call sees >4 receiver shapes, V8 gives up specializing. Profile with--trace-ic. - Avoid try/catch in hot loops pre-2024; Maglev + TurboFan now optimize through
tryin most cases, but deopts on uncommon throws can still hurt — measure.
Node.js modern features (24+)
- Built-in
node --watch(file watcher, 22+). node --env-file=.envloads dotenv-style files natively.node:testtest runner with subtests,describe/it, snapshot, coverage (--experimental-test-coverage).fetch/Request/Response/FormData/Blobglobal since 18, stable in 22+.node --experimental-strip-types(22.6+) and--experimental-transform-types(23+) for native TS without compile step.node:sqlitebuilt-in (24+) — embedded SQLite, no native deps.- WebSocket client built in since 21.
- Permission model (
node --permission --allow-read=./data, 20+, experimental) — Deno-style sandboxing. - Single-executable applications (SEA,
node --experimental-sea-config) — bundle Node + app into one binary.
Gotchas
==coercion:[] == falseis true,0 == "0"is true,null == undefinedis true. Use===.typeof null === "object"— historical bug, never fixed.NaN !== NaN— useNumber.isNaN(x)orObject.is(x, NaN).- Floating point:
0.1 + 0.2 === 0.3is false. Use BigInt or fixed-point for money. thisrebinding: lost when you pass a method as a callback. Bind it (fn.bind(this)) or use arrows.for...inon arrays — iterates keys as strings, plus inherited enumerable props. Usefor...oforforEach.- Hoisting:
varand function declarations hoist;let/constare in the temporal dead zone until initialized. - Implicit globals: assignment to undeclared name in non-strict mode creates a global. Use
"use strict"or modules (always strict). - Floating Promises: unhandled rejections crash Node by default in 22+. Always
awaitor.catch(). Dateis awful — month is 0-indexed, mutable, no timezone. UseTemporal(Stage 3) ordate-fns/Luxon.- JSON limitations: no BigInt, no
undefined, no functions, no circular refs, dates become strings. - Newcomers from Java/C#: no method overloading, no real privacy except
#field, no compile-time type errors without TS. - Newcomers from Python: truthiness differs (
"",0,null,undefined,NaN,falseare falsy;[]and{}are truthy). Object key order is “integer-like keys ascending, then string keys insertion-order.” Number.MAX_SAFE_INTEGER= 2^53 - 1 — JSON parsers will silently lose precision on bigger ints. UseBigIntor string-encode ({"id":"12345678901234567"}).- Module caching:
importandrequirecache by resolved path. Two import specifiers resolving differently (e.g.,node_modulesvs symlink) give you two separate instances of the same module — confusing for singletons. Array(5)vsArray.of(5)—Array(5)creates a length-5 hole-array;Array.of(5)creates[5].new Array(5).map(f)doesn’t call f because holes are skipped; useArray.from({length: 5}, f).Object.create(null)— true dictionary, no prototype, notoStringcollisions. Use for user-supplied keys.- Spread copies are shallow —
{...obj}and[...arr]only copy one level; nested objects share refs. UsestructuredClone(x)for deep clone (supports cycles, Map/Set, typed arrays). asyncfunctions always return a Promise — evenasync function f() { return 1 }returnsPromise<1>. Forgettingawaitreturns the Promise itself.- WeakMap keys must be objects (and now also Symbols since ES2023) — primitives won’t work.
WeakReffor general weak references; finalizer runs inFinalizationRegistry.
Node.js LTS / release timeline
| Version | Codename | Release | LTS until | Highlights |
|---|---|---|---|---|
| 18 | Hydrogen | Apr 2022 | Apr 2025 (EOL) | Fetch / FormData built-in, prefix-only built-in node: |
| 20 | Iron | Apr 2023 | Apr 2026 | Test runner stable, single-executable apps |
| 22 | Jod | Apr 2024 | Apr 2027 | --watch flag, node:sqlite, --experimental-strip-types, V8 12.4 + Maglev |
| 24 | Krypton | May 2025 | Apr 2028 | --experimental-transform-types (enums/decorators), URLPattern global, npm 11 |
| 26 | (TBD) | May 2026 | (Apr 2029) | Bundled node:test updates, continuing TS work |
Even-numbered releases promote to LTS in October of their release year; odd-numbered (Current only) are short-lived.
Async patterns + cancellation (modern)
// AbortController for fetch + custom async ops
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), 5_000);
try {
const res = await fetch(url, { signal: ctrl.signal });
} catch (e) {
if (e.name === "AbortError") { /* timeout */ }
} finally { clearTimeout(t); }
// AbortSignal.timeout (ES2024+, Node 20+): one-liner
await fetch(url, { signal: AbortSignal.timeout(5_000) });
// AbortSignal.any() combines multiple cancellation sources
const any = AbortSignal.any([userCancel.signal, AbortSignal.timeout(5000)]);
// Promise.withResolvers (ES2024) — useful for queues / event-to-Promise bridges
const { promise, resolve, reject } = Promise.withResolvers();
emitter.once("ready", resolve);
emitter.once("error", reject);
return promise;WebAssembly and browser advanced
- WebAssembly runs in every modern browser; baseline since 2017. Compile from C/C++ (emscripten, Cheerp), Rust (
wasm-bindgen+wasm-pack), Go (GOOS=js GOARCH=wasm/wasip1), AssemblyScript (TS-like), Zig, .NET (Blazor). - WASM threads (
SharedArrayBuffer+ atomics) require COOP/COEP cross-origin isolation headers. Enables true multi-core in the browser. - WebGPU (W3C stable in Chrome 113+, Firefox 141+, Safari 26+) — modern graphics + compute. Used by ML inference (transformers.js, WebLLM, Web-stable-diffusion).
- OPFS (Origin Private File System) — true POSIX-like filesystem in browser; replaces IndexedDB for large structured local data.
- Service Workers + Background Sync + Web Push + Background Fetch for offline-first PWAs.
- WASI Preview 2 + Component Model — composable Wasm components across languages, running on Wasmtime, WasmEdge, Wasmer, Spin, fermyon, Fastly Compute@Edge.
Code examples — modern patterns
React 19 server actions + useActionState
"use server";
async function updateProfile(prev, formData) {
const name = formData.get("name");
try {
await db.user.update({ where: { id: 1 }, data: { name } });
return { ok: true, message: "Saved" };
} catch (e) {
return { ok: false, message: e.message };
}
}
// Client component
"use client";
import { useActionState } from "react";
export function ProfileForm() {
const [state, formAction, pending] = useActionState(updateProfile, null);
return (
<form action={formAction}>
<input name="name" />
<button disabled={pending}>{pending ? "Saving..." : "Save"}</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}Hono / Bun edge HTTP server with type-safe RPC
import { Hono } from "hono";
import { z } from "zod";
import { zValidator } from "@hono/zod-validator";
const app = new Hono()
.post(
"/users",
zValidator("json", z.object({ name: z.string(), age: z.number() })),
(c) => {
const { name, age } = c.req.valid("json");
return c.json({ id: 1, name, age });
}
);
export type AppType = typeof app;
export default app;Web Worker with structured cloning
// main.js
const worker = new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
worker.postMessage({ cmd: "compute", payload: new Float32Array(1_000_000) });
worker.onmessage = (e) => console.log("result:", e.data);
// worker.js
self.onmessage = (e) => {
const { cmd, payload } = e.data;
if (cmd === "compute") {
let sum = 0;
for (let i = 0; i < payload.length; i++) sum += payload[i];
self.postMessage(sum);
}
};End-to-end stack recipes (2026)
A “modern” full-stack JS app in 2026 is rarely just a framework — it’s a curated stack. The combinations that ship:
Next.js 15 + React 19 + Drizzle + Auth.js + Stripe + Vercel — the dominant SaaS recipe.
- App Router with Server Actions for mutations.
- Drizzle ORM (TS-first, lighter than Prisma, no shadow DB) talking to Neon / Supabase / PlanetScale.
- Auth.js v5 (formerly NextAuth) handles OAuth + email + magic links; works in Edge runtime.
- Stripe webhook handler as a Route Handler with
runtime = 'nodejs'for raw-body verification. - Deploy:
vercel --prod— ~30s build for a medium app, fluid compute pricing.
Astro 5 + Astro DB + Astro Actions + content collections — content-first sites with surgical hydration.
getStaticPathsfor SSG;prerender = falseper-route for SSR.- Astro DB (libSQL/Turso under the hood) for typed schema + lightweight ORM in
db/config.ts. - Actions (
actions/index.ts) give server functions callable from any island — typed end-to-end. - Server Islands (
server:defer) defer heavy components until after first paint. - Adapters for Vercel / Netlify / Cloudflare / Node — one codebase, multi-target.
Remix → React Router 7 migration — the merge as of Nov 2024.
- “Framework mode” = the old Remix (loaders, actions, file routing, full SSR).
- “Library mode” = classic React Router (client-side declarative routing).
- Migration:
npx codemod remix/2/react-router/upgraderewrites imports. No data-loader semantic changes. - File-based routing under
app/routes/; flat-routes convention. - Deploy: any Node host, Vercel, Cloudflare Workers, Deno Deploy.
SvelteKit + Svelte 5 (runes) + Lucia v3 + better-sqlite3 — minimal, fast, no-framework-lock.
- Runes (
$state,$derived,$effect) replace store boilerplate; reactive in.svelte.jsmodules. - Lucia v3 (now framework-agnostic) handles sessions; works with adapter-static or adapter-node.
better-sqlite3for local-first apps; LiteFS / Turso for replicated edge SQLite.- Deploy: Vercel, Netlify, Cloudflare Pages, Node, or bun.
Bun.serve native HTTP — when you don’t want a framework.
Bun.serve({
port: 3000,
routes: { // pattern matching (Bun 1.2+)
"/api/users/:id": req => Response.json({ id: req.params.id }),
"/api/health": new Response("OK"),
},
fetch(req) { // fallback
return new Response("Not found", { status: 404 });
},
});Bun’s HTTP server hits ~700k req/s on a single core — 3-4x Node’s http.createServer baseline, and Bun’s router uses radix tree matching natively.
Performance numbers (cold start + steady state, 2026 measurements)
Cold-start times for “hello world” HTTP servers on warm Linux box (M2 Pro, EC2 c7i.large equivalents):
| Runtime | Cold start | Steady-state req/s (1 core) | Notes |
|---|---|---|---|
| V8 (raw, no Node) | ~50ms | n/a | Isolate creation only |
| Node 22 LTS | ~150ms | ~75k (http.createServer) | --watch and --env-file builtin |
| Node 24 LTS | ~120ms | ~80k | Maglev enabled, V8 12.4 |
| Deno 2.0 | ~80ms | ~150k (Deno.serve) | Hyper Rust HTTP under the hood |
| Bun 1.2 | ~30ms | ~700k (Bun.serve) | JSC + uWebSockets-like routing |
| Cloudflare Workers | <1ms | ~50k per isolate | V8 isolates, no Node API |
| AWS Lambda Node 22 | ~250ms (cold) | n/a | SnapStart cuts to ~100ms |
| AWS Lambda + LLRT | ~50ms | n/a | Amazon’s QuickJS-based runtime; no JIT, smaller surface |
Event loop blocking: a single CPU-bound for loop (1M iterations) blocks the entire event loop for ~5-15ms. Offload to worker_threads (Node) or Worker (browser) for anything >10ms. The --inspect-brk profiler shows this as a “long task” red flag.
Common JS-specific gotchas with worked examples
1. === vs == coercion — == invokes ToPrimitive and silently coerces:
0 == "" // true (both → 0)
0 == "0" // true
[] == false // true (both → 0)
[1] == "1" // true ([1].toString() === "1")
null == undefined // true (special case)
NaN == NaN // false (NaN compares unequal to itself)
{} == "[object Object]" // false (object on LHS never coerces with this rule)Lint with eqeqeq (ESLint) or noEqEq (Biome) — disallow == except for == null.
2. Microtask vs macrotask ordering — Promises always drain before the next setTimeout:
console.log("1");
setTimeout(() => console.log("2"), 0); // macrotask
Promise.resolve().then(() => console.log("3")); // microtask
queueMicrotask(() => console.log("4"));
console.log("5");
// Output: 1, 5, 3, 4, 2
// Sync code first; then microtask queue drained entirely; then next macrotaskA long microtask chain (.then().then().then()) can starve macrotasks indefinitely — UI freezes, timers don’t fire.
3. Prototype pollution — user-controlled keys reaching Object.assign / spread / lodash.set:
// Vulnerable
const opts = JSON.parse(userInput);
const cfg = { ...defaults, ...opts }; // if opts = {"__proto__": {"isAdmin": true}}
// ALL plain objects now have isAdmin = true
// Safe: Object.create(null) — no prototype, no __proto__ pollution route
const cfg = Object.assign(Object.create(null), defaults, opts);
// Safe: Map for user-keyed data
const cfg = new Map(Object.entries(opts));CVEs in lodash, jQuery, Mongoose all traced to this pattern. ESLint no-proto + structured cloning + Object.create(null) mitigate.
Citations
- MDN Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript
- ECMA-262 (living spec): https://tc39.es/ecma262/
- TC39 finished proposals: https://github.com/tc39/proposals/blob/main/finished-proposals.md
- TC39 process / stages: https://tc39.es/process-document/
- Node.js docs: https://nodejs.org/docs/latest/api/
- Node.js release schedule: https://nodejs.org/en/about/previous-releases
- V8 blog: https://v8.dev/blog
- Deno manual: https://docs.deno.com/runtime/manual
- Bun docs: https://bun.sh/docs
- Temporal proposal: https://tc39.es/proposal-temporal/docs/
- AsyncContext proposal: https://github.com/tc39/proposal-async-context
- Decorators proposal: https://github.com/tc39/proposal-decorators