This is part of an ongoing series I am writing as I work my way through the modern web stack from a WordPress developer’s perspective. The series is aimed at WordPress veterans who, like me, have built things on the web for years and feel quietly behind the curve. The goal is broad literacy, not deep mastery. By the end you should be able to read any modern stack list and know what each piece is doing.
Each post comes with an audio companion (10-15 minutes, generated via NotebookLM) for gym or commute listening. Press play below if that suits you better than reading.
Hook
The first time I opened a Next.js codebase I closed it again within ten minutes. Not because the JavaScript was hard. Because I had no idea what most of the files were for.
A folder called node_modules with what looked like 40,000 directories inside. A package.json with strange version syntax. A tsconfig.json I could not parse. A vite.config.ts doing things to imports. Files ending in .tsx that looked like HTML had crawled inside JavaScript and died there.
I have been writing PHP since the early WordPress days. I know what a composer.json is. I know require_once. I know vendor/. None of that knowledge transferred. Modern JavaScript looks like PHP from a parallel universe where every decision went the other way.
So this module is the cheat sheet I needed. What modern JavaScript actually is, what TypeScript adds, and how the toolchain maps onto the PHP world you already know.
Core Concept
There are really three things going on, and they are easy to confuse.
JavaScript the language is what you write. The version of JavaScript that runs in browsers today is wildly more capable than the JavaScript you remember from the jQuery era. It has modules, async functions, arrow syntax, destructuring, optional chaining, classes that actually work, and dozens of other features that show up on every line of a modern codebase.
TypeScript is JavaScript with a type system bolted on top. You write code that looks almost identical to JavaScript, but with type annotations. A separate tool strips the types and compiles it to plain JavaScript before it runs. The runtime never sees the types.
The toolchain is everything else: the package manager that pulls in dependencies, the bundler that combines your code into shippable assets, the runtime that executes server-side code, and the configuration files that glue it all together.
You can write modern JavaScript without understanding any of this. You just cannot read a Next.js codebase without understanding all of it. So let me walk through each piece.
Modern JavaScript: NOT the JavaScript You Remember
If your last serious encounter with JavaScript was jQuery selectors and $.ajax, brace yourself. The language grew up. Some of the patterns you will see constantly:
<span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">// Imports and exports: like require_once but they declare what's actually used</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { useState<span class="op">,</span> useEffect } <span class="im">from</span> <span class="st">'react'</span><span class="op">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="kw">function</span> <span class="fu">MyComponent</span>() { <span class="op">...</span> }</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co">// Arrow functions: like PHP's short closures, but they're everywhere</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> double <span class="op">=</span> x <span class="kw">=></span> x <span class="op">*</span> <span class="dv">2</span><span class="op">;</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="co">// Destructuring: pulling values out of objects or arrays</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> { name<span class="op">,</span> email } <span class="op">=</span> user<span class="op">;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> [first<span class="op">,</span> second] <span class="op">=</span> items<span class="op">;</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co">// Async/await: the cure for callback hell</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="kw">async</span> <span class="kw">function</span> <span class="fu">fetchUser</span>(id) {</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> response <span class="op">=</span> <span class="cf">await</span> <span class="fu">fetch</span>(<span class="vs">`/api/users/</span><span class="sc">${</span>id<span class="sc">}</span><span class="vs">`</span>)<span class="op">;</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> response<span class="op">.</span><span class="fu">json</span>()<span class="op">;</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="co">// Optional chaining and nullish coalescing</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> city <span class="op">=</span> user<span class="op">?.</span><span class="at">address</span><span class="op">?.</span><span class="at">city</span> <span class="op">??</span> <span class="st">'Unknown'</span><span class="op">;</span></span>None of this is exotic. All of it is shipping in production today. You will see all of it within the first ten lines of any modern codebase you open.
TypeScript: PHP Type Hints That Actually Get Checked
PHP 7 and 8 gave you type hints. They mostly work. You can declare that a function expects an integer, and PHP will throw at runtime if you call it with a string.
TypeScript does the same thing for JavaScript, but with two big differences. First, the checking happens before the code ever runs, at build time. Second, the type system is dramatically more expressive than PHP’s. You can describe shapes of objects, arrays of specific types, functions whose return types depend on their arguments, and unions of multiple types in the same slot.
<span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> User <span class="op">=</span> {</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> id<span class="op">:</span> <span class="dt">number</span><span class="op">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> email<span class="op">:</span> <span class="dt">string</span><span class="op">;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> role<span class="op">:</span> <span class="st">'admin'</span> <span class="op">|</span> <span class="st">'editor'</span> <span class="op">|</span> <span class="st">'subscriber'</span><span class="op">;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> posts<span class="op">?:</span> Post[]<span class="op">;</span> <span class="co">// ? means optional</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>}<span class="op">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">getUserName</span>(user<span class="op">:</span> User)<span class="op">:</span> <span class="dt">string</span> {</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> user<span class="op">.</span><span class="at">email</span><span class="op">.</span><span class="fu">split</span>(<span class="st">'@'</span>)[<span class="dv">0</span>]<span class="op">;</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>}</span>In 2026, almost every serious modern web project uses TypeScript by default. If you join a team that does not, you are joining a team that will be migrating in the next year. Treat TS as the language, not as an option.
The Toolchain: What Every Config File Actually Does
The rough mapping a PHP developer can hold in their head:
| Modern JS | PHP equivalent |
|---|---|
package.json |
composer.json |
npm install / pnpm install |
composer install |
node_modules/ |
vendor/ |
package-lock.json / pnpm-lock.yaml |
composer.lock |
| Node.js (or Bun, Deno) runtime | The PHP interpreter |
| A bundler (Vite, Webpack, Turbopack) | No real PHP equivalent |
TypeScript compiler (tsc) |
Roughly PHP’s optional static analyzers (Psalm, PHPStan), but enforced |
The “no PHP equivalent for the bundler” line is worth pausing on. PHP files run as you wrote them. JavaScript for the browser needs to be packaged: many small source files combined, dead code removed, modern syntax compiled down to what older browsers understand, assets fingerprinted for caching. That is the bundler’s job. Vite is the modern default. Webpack is the old guard. Turbopack is the next-generation tool from the Next.js team. You rarely have to configure any of them by hand because frameworks handle the config for you.
The WordPress Analogue
Mental model:
npmis to JavaScript what Composer is to PHP.package.jsonis yourcomposer.json.node_modules/is yourvendor/. TypeScript is what PHP type hints would look like if Rasmus Lerdorf had been a Haskell programmer.The one thing that does not map: the bundler. WordPress and PHP do not have one because they do not need one. The browser cannot execute your raw modern JavaScript directly across hundreds of files, so a bundler stitches it all together before deployment. Once you accept that one extra step exists, everything else is just better-tooled versions of what Composer already gave you.
The Landscape
The lay of the land for the JavaScript toolchain in 2026. You do not need to learn each one in depth. You need to know which row each tool fits on so when someone says “we use Bun and Vite” you can mentally place them.
| Layer | What it does | Default in 2026 | Other options |
|---|---|---|---|
| Runtime | Executes server-side JS | Node.js | Bun, Deno |
| Package manager | Installs and locks dependencies | pnpm or npm | yarn, bun |
| Bundler | Packages JS for the browser | Vite or Turbopack | Webpack (legacy), esbuild, Rollup |
| Type system | Static type checking | TypeScript | Plain JS with JSDoc types (rare) |
| Linter | Catches errors before runtime | ESLint | Biome (newer, faster) |
| Formatter | Auto-formats code | Prettier | Biome |
| Test runner | Runs your tests | Vitest | Jest (legacy), Playwright (browser tests) |
If you are reading a job ad or a repo’s README and you see one of these names, you can now pin it on the map.
A few opinions worth holding. Use pnpm, not npm. It is faster, uses disk space more sensibly, and almost every modern project defaults to it now. TypeScript is the language. Plain JavaScript is the exception. Bun is interesting but not yet the safe default for production work. Node.js is still where the boring infrastructure lives.
What This Changes for WordPress People
Three practical wins once you have the JavaScript mental model in place.
The first is that reading code stops being scary. The reason a Next.js repo felt impenetrable was not the React, the Drizzle queries, or the deploy pipeline. It was the JavaScript syntax. Once you can parse arrow functions, destructuring, async/await, and ES modules at a glance, half the code in any modern project becomes legible.
The second is that you can finally evaluate AI-generated code. If you ask an AI agent to “add Supabase auth to my Next.js app,” it will produce a hundred lines of TypeScript across five files. Without TypeScript fluency you have no way to tell if the result is competent or garbage. Modern stack literacy starts here. Everything else builds on top.
The third is that you start to see the design choices in PHP differently. JavaScript has spent fifteen years catching up to a particular idea of what a programming language should be, and it has overshot in some places. PHP 8 quietly got there too. Once you can read both, you can pick the right tool for each job without religion, which is a small superpower in client work.
Watch Out
Five gotchas to brace for as you start reading JavaScript code.
=== not ==. JavaScript has loose equality (==) that does type coercion in strange ways. 0 == '' is true. null == undefined is true. Use === everywhere unless you have a specific reason not to. Modern linters will flag it for you.
this is weird. What this refers to inside a function depends on how the function was called, not on where it was written. Arrow functions inherit this from the surrounding scope, which is usually what you want. Regular functions do not, which is why people fall in love with arrow functions.
null and undefined are different. Almost no other language makes this distinction. null means “explicitly empty.” undefined means “no one has assigned anything here.” In practice you mostly handle them together with ?. and ??, but the difference will bite you when you start using third-party APIs.
Everything async eventually. Anything involving the network, the filesystem, or a database returns a Promise. You handle Promises with await inside an async function, or with .then(). Forget this and you will have functions that return Promise<unknown> instead of the data you wanted.
Hot reloading is a different debugging experience. When you save a file in modern development, the browser updates in place without reloading. This is wonderful when it works and confusing when it does not. State sticks around. Old code is sometimes cached. When something feels haunted, a hard reload usually fixes it.
Going Deeper
If you want to internalise modern JavaScript rather than just recognise it, a few resources worth your time.
YouTube, gym or commute friendly:
- “Modern JavaScript Tutorial” by JavaScript Mastery (~3 hours, watchable in pieces). Covers the modern syntax features in one place, with examples that map nicely to other languages.
- “TypeScript in 100 Seconds” by Fireship, followed by “TypeScript Tutorial” by The Net Ninja. Short version of TS for orientation, then a longer playlist if it sticks.
Official docs worth bookmarking:
- MDN’s JavaScript guide. The canonical reference. Skim the “Modern JavaScript” sections and treat everything else as on-demand lookup.
- TypeScript Handbook. Start with the “Everyday Types” page. Most production TS uses maybe 20 percent of the language and that page covers it.
- Total TypeScript by Matt Pocock. The free tier has enough to take you from beginner to competent. The paid tier is where you would go if TS becomes a daily tool.
Next post in the series moves from language to framework: React, in WordPress terms, where I show you why components are basically template parts with a JavaScript brain.

Leave a Reply