Part 2 · 2 chapters · ~12 min

Parsing

From bytes to a DOM and from source to running code: tokenising, tree construction, the scripts and stylesheets that block, lifecycle events, JS compilation and hydration.

6

HTML: bytes to DOM

The terms between the first byte of HTML and a document the browser can style: encoding, tokens, the tree, and the scripts and stylesheets that stop the parser.

Tokeniser

bytes<p class=a>…tokeniserstate machi…StartTag…class=aCharacterHi

The state machine that turns the HTML byte stream into tokens: start tags, end tags, attributes, text and comments.

in practiceWhy a stray "<" in text or an unclosed attribute quote swallows the rest of a page.

deep dive: Browser P2

Tree construction

htmlheadbodypdiv<p><div> becomes siblings: the parser closes p

The stage that takes tokens and builds the DOM, applying the HTML spec's error recovery so malformed markup still produces a tree.

in practiceWhy a <div> inside a <p> closes the paragraph, and the Elements panel shows a tree different from your source.

deep dive: Browser P2

DOM

documenthtmlheadbodynodes with parents, children, attributes

The Document Object Model: the live tree of nodes the parser builds and scripts read and change; the page's structure in memory.

in practicedocument.querySelector, the Elements panel, every framework's render target.

deep dive: Browser P2

Character encoding

é in UTF-8C3 A9read as Latin-1Ã ©₦ in UTF-8E2 82 A6declare<meta charset="utf-8"> first

How bytes map to characters; the parser must know it (UTF-8 by default, from the header or a meta charset in the first 1024 bytes) before it can read text.

in practiceMojibake ("é" for "é") when a server sends the wrong charset; meta charset first in head.

deep dive: Algorithms P9

Parser-blocking script

parsefetch scriptrun scriptparse (rest)260 msnothing below the tag is parsed while it waits

A classic script without async or defer: the HTML parser stops, fetches and runs it, then continues, because the script might call document.write.

in practiceA long white screen while a third-party script in head downloads; the Performance panel's "Parse HTML" gap.

deep dive: Browser P2

async vs defer

asyncfetch in parallelrun on arrivalany ordermay interrupt parsedeferfetch in parallelrun after parsedocument orderbefore DOMContentLoaded

Both fetch in parallel with parsing; async runs as soon as it arrives (any order), defer runs after parsing finishes, in document order.

in practicedefer for app code that needs the DOM; async for independent scripts like analytics; type=module is deferred by default.

deep dive: Browser P2

Preload scanner

main parserblocked on scriptpreload scan…reads aheadfetch img, c…early

A secondary lightweight parser that looks ahead in the HTML for resource URLs and starts fetching them while the main parser is blocked.

in practiceWhy resources in markup load fast and ones injected by script or set in CSS backgrounds load late.

deep dive: Browser P2

Render-blocking CSS

htmlstyles.cssfirst paint210 msno paint until the CSSOM exists

Stylesheets in head block first render: the browser will not paint until it has the CSSOM, to avoid a flash of unstyled content.

in practiceThe "Render-blocking requests" Lighthouse audit; why critical CSS is inlined and the rest loaded later.

deep dive: Browser P3

DOMContentLoaded

parse HTMLdefer scriptsDOMContentLoadedimagesload284 ms

The event fired when the HTML is fully parsed and deferred scripts have run; images and iframes may still be loading.

in practiceThe blue line in the Network waterfall; the old place to start app code.

deep dive: Browser P10

load event

HTML parsedDCLimages, ifra…loadingloadall done

Fired when the page and all its subresources (images, iframes, stylesheets) have loaded.

in practiceThe red line in the waterfall; rarely the right signal for "ready" because lazy content keeps arriving after it.

deep dive: Browser P10

Document.readyState

loadinginteractivecompletecheck it; the event may already have fired

loading, then interactive (parsed: DOMContentLoaded next), then complete (load next).

in practiceScripts that may run late check it before waiting for an event that already fired.

deep dive: Browser P10

Speculative parsing

script runni…speculate ah…tokenise, fetchdocument.wri…discard

Continuing to tokenise and fetch ahead while a script runs, discarding the work if the script changes the document.

in practiceThe reason document.write is hostile to performance: it can invalidate the speculation.

deep dive: Browser P2
THE PARSING LAYER, IN ORDER
from bytes on the wire to a DOM, a running script and an interactive page
swipe the figure sideways, or tap expand for full screen
1/6
decode, tokenise
Decode and tokenise: bytes become characters by the declared encoding, then tokens by the tokeniser's state machine; the doctype decides standards or quirks.
7

JavaScript: source to running code

The terms between a script's bytes and an interactive page: parse, compile, cache, modules and hydration.

JavaScript parse

sourcetextscannertokensparserASTbytecodeIgnition

Turning JS source into an abstract syntax tree; costs CPU in proportion to the source size, before any code runs.

in practiceThe "Compile Script" and "Parse" slices in a Performance trace; why 1 MB of JS is slower than 1 MB of images.

deep dive: JavaScript P1

Lazy parsing (preparse)

preparsefind boundariessyntax errorsno AST keptcheapfull parseAST + scopeson first callthen bytecodeexpensive

V8 only fully parses functions when they are first called; it preparses the rest just enough to find their boundaries and errors.

in practiceWhy wrapping a function in parentheses (PIFE) once hinted eager parsing; why unused code still costs a preparse.

deep dive: JavaScript P1

AST

VariableDeclarationIdentifier xBinaryExpress…12const x = 1 + 2

Abstract syntax tree: the program as a tree of nodes (declarations, expressions, statements) that compilers, linters and bundlers all work on.

in practiceESLint rules, Babel and SWC transforms, codemods, the TypeScript compiler.

Bytecode

LdaSmi [1]Star r0LdaSmi [2]Add r0, [0] ; feedback slotReturnregister machine with feedback slots

A compact instruction format V8's Ignition interpreter runs; generated from the AST, cached, and the input to the optimising compilers.

in practicenode --print-bytecode; code caching makes repeat visits skip parse and compile.

deep dive: JavaScript P2

Code cache

first visitdownloadparsecompilerunrepeat visitfrom HTTP cachebytecode from cache(skip)run

V8 storing compiled bytecode for a script so the next load skips parsing and compiling it.

in practiceFaster repeat visits for stable, hashed script URLs; service-worker-cached scripts get it eagerly.

deep dive: JavaScript P2

JSON.parse fast path

JS literalJSON.parse100 relativefor large payloads only

Parsing a large object literal from a JSON.parse string is faster than from a JS object literal, because JSON's grammar is simpler.

in practiceLarge embedded state (initial data in a script tag) delivered as JSON rather than as JS.

deep dive: JavaScript P1

Module graph

main.jsapp.jsapi.jsui.jsrouter.jsroutes.…fetch → parse → link → evaluate

The tree of ES module imports the browser must fetch, parse and link before any module code runs.

in practiceWaterfalls of module requests in dev servers; modulepreload to flatten them; bundlers flatten them in production.

Hydration

server H…visibleJS loadshydrateadopt DOMinteract…visible ≠ interactive until this finishes

Attaching a client framework's event handlers and state to server-rendered HTML, by rendering the same tree and adopting the existing DOM.

in practiceHydration mismatch warnings; the gap between seeing a button and it working; islands and partial hydration.

deep dive: React P6 · React P13

Streaming HTML

serverbrowserhead + shellparse, paint shellslow section (later)swap in

Sending the document in chunks as the server produces them, so the browser parses and renders the head and shell before the body is done.

in practiceReact renderToPipeableStream with Suspense boundaries; Transfer-Encoding: chunked.

deep dive: React P13 · Browser P2

Declarative Shadow DOM

<my-card> <template shadowrootmode="open"> <style>…</style><slot></slot> </template> Content</my-card>

A template with shadowrootmode in HTML that the parser turns into a shadow root, so web components render server-side without JS.

in practiceSSR for web components; style encapsulation without a script.

deep dive: Browser P2

Quirks mode

document starts withmode<!doctype html>standards modemissing doctypequirks modeold HTML4 doctypequirks or limited quirks

A legacy rendering mode triggered by a missing or old doctype, in which layout follows pre-standards bugs.

in practiceOdd box sizing and table layout bugs in emails and old pages; always start with <!doctype html>.

deep dive: Browser P2

innerHTML parsing

string"<b>hi</b>"fragment par…replace chil…a sink: never with untrusted strings

Assigning a string to innerHTML runs the HTML parser on it in fragment mode and replaces the element's children.

in practiceAn XSS sink (see Security); slower than DOM APIs for small changes; scripts inserted this way do not run.