Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Advanced/A8
36 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSA8 · Part 3 · Advanced

Ecosystem & professional

How the tools around JS actually work, and where the language itself comes from.

8 min read5 sections2 exercises
36 of 41

In this chapter

  • TypeScript and Node, briefly
  • Beyond Node: Deno and Bun
  • What a bundler is actually doing: ASTs
  • Polyfills vs transpilation — two different problems
  • Reading the spec, and where new syntax comes from
about 8 min
05On this page
  1. TypeScript and Node, briefly
  2. Beyond Node: Deno and Bun
  3. What a bundler is actually doing: ASTs
  4. Polyfills vs transpilation — two different problems
  5. Reading the spec, and where new syntax comes from

TypeScript and Node each have their own shelf on this site, reserved for exactly this kind of depth once they're written — this section stays brief on both, just enough to place them correctly next to plain JS. Testing gets a full chapter of its own, right after this one, because unlike the other two it's squarely inside what "advanced JavaScript" actually means day to day.

TypeScript and Node, briefly

TypeScript adds a type system checked entirely at compile time and erased before anything runs — it's structural, meaning two differently-named types with the same shape are compatible, unlike languages that check by declared name. Generics, narrowing, and the built-in utility types (Partial, Pick, Omit) are where that gets interesting — reserved for the TypeScript shelf once it's written.

Node extends JS past the browser with a filesystem, a process, and no DOM — streams, cluster (multi-process scaling across CPU cores), worker threads, and AsyncLocalStorage (request-scoped context that survives across awaits without threading a parameter through every function) are the parts worth a real chapter — reserved the same way for the Node shelf.

The next chapter covers testing in full: unit vs integration vs E2E, mocking, fake timers, and what coverage does and doesn't actually tell you.

Beyond Node: Deno and Bun

NodeDenoBun
Module systemCommonJS + ESM, needs config to chooseESM by default, URL imports work directlyESM + CommonJS, both work with no config
TypeScriptneeds a transpile stepruns .ts files directly, no build stepruns .ts files directly, no build step
Security defaultfull access, no sandboxingno file/network/env access unless explicitly granted with a flagfull access, same as Node
Built-in toolingneeds separate packages for testing, bundling, lintingtest runner, formatter, linter, and bundler all built intest runner, bundler, and a much faster package manager all built in

Both were built specifically to fix things about Node its original design got locked into early. Deno's permission flags exist because Node grants a script full filesystem and network access by default, something a browser tab never would. Bun's headline pitch is raw speed: it's written in Zig on top of JavaScriptCore instead of V8, and its package installer resolves and links dependencies dramatically faster than npm across a large node_modules tree. Neither has displaced Node as the default choice for most production backends — the ecosystem of battle-tested libraries and hosting support still overwhelmingly assumes Node — but both are genuinely production-ready today, not experiments.

Dry run: picking a runtime for one concrete project

Question from the table aboveAnswer for this projectWhat it rules in or out
Scenario: a CLI tool that runs untrusted plugin scripts and should start fast, written in TypeScript.
Needs to sandbox untrusted code?Yesrules out Node and Bun — both grant full filesystem/network access by default
Needs to run .ts directly, no build step?Yesrules out Node — needs a transpile step first; Deno and Bun both qualify
Depends on a large existing npm/CommonJS ecosystem?No — few dependencies, all modernremoves the strongest reason to default to Node anyway
Conclusion: Deno — the only one of the three that satisfies the sandboxing requirement

Same three rows the table above already gives — this is just running one real scenario through them in order, instead of reading each row in isolation. Change one fact (say, a large existing npm dependency tree the team can't rewrite) and the same walk lands on Node instead.

What a bundler is actually doing: ASTs

Every tool in this chapter — a bundler, a linter, a formatter, a minifier, a codemod — starts the exact same way: parse the source into an Abstract Syntax Tree, a plain nested object describing the code's structure, then walk and transform that tree, never the raw text itself.

// A hand-built AST for: const x = 1 + 2;
     // Real parsers (Babel, Acorn) produce something like this automatically.
const ast = {
  type: "VariableDeclaration",
  kind: "const",
  declarations: [{
    type: "VariableDeclarator",
    id: { type: "Identifier", name: "x" },
    init: {
      type: "BinaryExpression",
      operator: "+",
      left: { type: "Literal", value: 1 },
      right: { type: "Literal", value: 2 },
    },
  }],
};
console.log(JSON.stringify(ast.declarations[0].init, null, 2));

That's genuinely close to what astexplorer.net shows for the real thing — every operator, every identifier, every literal is its own typed node. Editing code programmatically (a codemod, the tool behind large automated migrations like a big React version bump across a whole codebase) means finding the right node type in this tree and swapping it, then printing the tree back out to text — never regex-replacing the source directly, which breaks the instant the pattern shows up somewhere the author didn't anticipate (inside a string, a comment, a different context entirely).

A Babel plugin is exactly this walk-and-transform step, packaged: it's handed the AST, given a chance to visit specific node types (ArrowFunctionExpression, ClassDeclaration, …), and returns a modified tree that Babel then prints back to JS — this is the actual mechanism behind "transpile modern syntax down to something older browsers run."

Polyfills vs transpilation — two different problems

These get bundled together in conversation constantly, and they fix genuinely different gaps.

Transpilation Polyfill
Fixes a missing…syntax — arrow functions, optional chaining, classesruntime feature — Array.prototype.flat, Promise, fetch
Howrewrites your source into older-syntax equivalent code, before shippingships extra JS that adds the missing method/object at runtime, if it's not already there
Can it be fixed at build time alone?yes — syntax is fully resolved before the browser ever sees itno — the feature has to actually exist in the running environment, one way or another

core-js is the actual polyfill implementation most tooling pulls from; browser targeting (a browserslist config, shared by most of this toolchain) is what tells both the transpiler and the polyfill loader which engines actually need to be supported — the newer the target list, the less of either gets shipped, which is a direct, measurable bundle-size win for a team that can drop support for old browsers.

Reading the spec, and where new syntax comes from

Every JS feature in every chapter on this site started as a TC39 proposal and moved through a fixed series of stages before landing in the language:

Stage Means
0 — Strawpersonany committee member's idea, no formal backing yet
1 — Proposalthe problem is real, worth solving, has a champion
2 — Draftreal syntax and semantics written out
2.7 — Approved in principlethe spec text is agreed; it waits for conformance tests and prototype implementations before engines ship it (a stage added in 2024)
3 — Candidatespec-complete, feedback comes from real implementations, not just discussion
4 — Finishedshipped in engines, included in the next yearly ECMAScript edition

Optional chaining, nullish coalescing, and top-level await all went through exactly this pipeline before ever reaching a browser. The official spec (ecma262) reads as dense, formal pseudocode — genuinely worth being able to skim once in a while, because it's the actual final authority any time a blog post's explanation of some edge case and the engine's real behavior disagree.

Say it like this → "When I hit a genuinely ambiguous edge case — something two blog posts explain differently — I check the spec or a quick node -p/console test rather than trust either post. This whole site was built the same way: every runnable claim in it was verified against a real engine first."
Interview answer, one sentence

"A bundler parses code into an AST to follow imports and drop what is unused, a transpiler rewrites new syntax for older engines, and a polyfill supplies a missing runtime API — three different problems that are constantly confused, and knowing which one you have decides the fix."

Before you move on
  • Explain what "structural" typing means for TypeScript, in one sentence.
  • Walk the Node/Deno/Bun decision table against a project with different constraints than the dry run above, and land on a different runtime.
  • Explain why a bundler or codemod has to work on an AST instead of regex-replacing the source text directly.
  • Say the difference between what a transpiler fixes and what a polyfill fixes, and why one can't substitute for the other.
  • Name which TC39 stage a proposal needs to reach before it's safe to rely on without a transpiler.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 10 testsWrite your own Array.prototype.flatadvanced
  • 8 testsCount nodes of one type in a treeadvanced
All 31 for this level →
A8

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. A7Previous · A7Security
  2. A8you are here
  3. Next · A9TestingNot optional at this level — and this whole site's own test suite is the example.8 min read

01On this page

5 sections · 0%

  1. 01TypeScript and Node, briefly
  2. 02Beyond Node: Deno and Bun
  3. 03What a bundler is actually doing: ASTs
  4. 04Polyfills vs transpilation — two different problems
  5. 05Reading the spec, and where new syntax comes from

[] chapters · t top