docs/parsing/lazy-parsing-and-preparser.md
This document explains V8's strategy for fast JavaScript parsing, focusing on lazy parsing and the preparser, as described in the "Blazingly fast parsing" series on v8.dev.
Web pages often ship much more JavaScript than is needed for initial startup. Eagerly parsing and compiling all this code has significant costs:
To mitigate this, V8 uses Lazy Parsing.
When V8 encounters a function during top-level parsing, it often decides to "pre-parse" it instead of fully parsing and compiling it.
When the function is actually called, it is fully parsed and compiled on-demand.
The main complexity in pre-parsing is variable allocation for closures.
If an inner function references a variable declared in an outer function, that variable cannot be allocated on the stack (since the outer function's frame will disappear). It must be allocated in a heap-based Context.
To know whether to allocate a variable on the stack or in a context, the preparser must track variable declarations and references across scopes, even though it is skipping the function body.
JavaScript syntax can be ambiguous (e.g., arrow functions vs. destructuring). The preparser must correctly resolve whether a reference actually targets an outer variable or a shadowed inner one.
ParserBase and CRTPTo avoid code duplication and divergence between the full parser and the preparser, V8 uses the Curiously Recurring Template Pattern (CRTP).
Both Parser and PreParser inherit from a shared ParserBase:
template <typename Impl>
class ParserBase { ... };
class Parser : public ParserBase<Parser> { ... };
class PreParser : public ParserBase<PreParser> { ... };
This allows maximum code sharing for the complex logic of variable tracking and spec-compliance checks while allowing specific overrides for AST building vs. skipping.
In early versions of V8, when a lazy function was finally called and needed to be compiled, V8 had to re-preparse its inner functions to figure out scope resolution again. In deeply nested code (common with module bundlers), this led to non-linear parse times.
To solve this, V8 produces serialized log data during pre-parsing, known as PreparseData (handled by PreparseDataBuilder). When the outer function is finally compiled, it reads this metadata to know where variables live, without needing to re-preparse the inner functions.
To avoid pre-parsing functions that are likely to be executed immediately, V8 uses heuristics to detect them and eagerly parses and compiles them.
(function(){...})) is a strong hint that it will be called immediately.! before a function (e.g., !function(){...}()), common in minifiers like UglifyJS, also triggers eager compilation.V8 also supports "magic comments" to guide eager vs. lazy compilation (when enabled by flags):
//# allFunctionsCalledOnLoad: Hints that all functions in the script should be eagerly compiled.//# functionsCalledOnLoad=<data>: Contains VLQ Base64 encoded data specifying the positions of functions that should be eagerly compiled.src/parsing/preparser.h: Implementation of the preparser.src/parsing/parser-base.h: The shared base class using CRTP.