rollup/rollup

Next-generation ES module bundler

26,268 stars JavaScript 9 components

13 hidden assumptions · 8-stage pipeline · 9 components

Like any codebase, this cli tool makes assumptions it never checks — most are routine. The ones worth your attention are below.

Transforms ES modules into optimized bundles by parsing, analyzing dependencies, and generating output

Source files enter through the CLI or API and are parsed by a Rust-based parser into serialized AST buffers. These buffers are converted to TypeScript AST nodes which undergo dependency analysis and scope resolution. A module graph is built by following imports, then tree-shaking eliminates unused code by analyzing variable references and side effects. Finally, the remaining modules are chunked and rendered into optimized bundle files.

Under the hood, the system uses 4 feedback loops, 4 data pools, 4 control points to manage its runtime behavior.

A 9-component cli tool. 12625 files analyzed. Data flows through 8 distinct pipeline stages.

Hidden Assumptions

Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here. The rest are minor; they're under "Show everything".

Worth your attention first

If the Rust parser changes its serialization format or the TypeScript deserializer misreads the buffer, nodes will be created with wrong types, corrupted positions, or malformed structures, causing silent AST corruption throughout the bundling process

Worth your attention first

If scope resolution fails (circular imports, temporal dead zones, complex destructuring), this.variable remains null but the code continues processing, leading to null pointer exceptions when the identifier is later used for tree-shaking or rendering

Worth your attention first

If plugins return inconsistent module IDs for the same import or return malformed objects instead of strings, the module cache becomes corrupted and the same module may be loaded multiple times with different IDs, breaking dependency analysis

Show everything (10 more)
Temporal

The EntityPathTracker and DiscriminatedPathTracker instances created in HasEffectsContext remain valid for the entire tree-shaking analysis phase and aren't modified concurrently by other parts of the system

If this fails: If these trackers are mutated during analysis by parallel processing or plugin interference, the tree-shaking algorithm will make incorrect inclusion decisions, potentially including dead code or excluding live code

src/ast/ExecutionContext.ts:createHasEffectsContext
Resource

The Node.js event loop can handle the Rust async parsing tasks without blocking, and that system memory is sufficient to hold both the original source code string and the serialized AST buffer simultaneously

If this fails: For very large files, the system may run out of memory when both the input string and output buffer exist at the same time, or the event loop may be starved if too many parse tasks are queued, causing the bundler to hang or crash

rust/bindings_napi/src/lib.rs:parse_async
Domain

JavaScript well-known symbols (Symbol.toStringTag, Symbol.hasInstance, etc.) have stable identities across all environments and that future ECMAScript versions won't introduce conflicting symbol semantics

If this fails: If symbol behavior changes in newer JavaScript engines or if polyfills create different symbol instances, the symbol-based property tracking will fail to correctly identify well-known symbol accesses, breaking tree-shaking for code using these symbols

src/ast/utils/PathTracker.ts:WELL_KNOWN_SYMBOLS
Scale

The nested Map structure in EntityPathTracker can efficiently handle deeply nested object path tracking without hitting JavaScript's recursion limits or memory constraints, even for complex codebases with deep property access chains

If this fails: For extremely deep object property accesses (obj.a.b.c...z), the tracker's nested Map structure may consume excessive memory or hit stack overflow during traversal, causing tree-shaking to fail or produce incorrect results

src/ast/utils/PathTracker.ts:EntityPathTracker
Environment

The WebAssembly runtime environment supports the Rust-compiled parser with sufficient stack space and linear memory, and that WASM's memory model is compatible with the AST buffer serialization format

If this fails: If the WASM runtime has insufficient memory or stack space, or if the memory layout differs from native execution, the parser will crash or produce corrupted AST data, breaking the entire bundling process in browser environments

rust/bindings_wasm/src/lib.rs:parse
Contract

The scope.addDeclaration method correctly handles all variable kinds (var, let, const, parameter) and that treeshake options remain consistent throughout the declaration phase - specifically that treeshake.correctVarValueBeforeDeclaration doesn't change mid-build

If this fails: If treeshake options change during bundling or if scope.addDeclaration fails to properly handle certain variable kinds, variable hoisting and initialization timing will be incorrect, leading to wrong tree-shaking decisions and potentially broken output code

src/ast/nodes/Identifier.ts:declare
Ordering

The includedCallArguments Set maintains insertion order for function call argument processing and that arguments are processed in the same order they appear in the source code

If this fails: If argument processing order is inconsistent, side effects from function calls may be analyzed in the wrong sequence, potentially marking pure functions as having effects or missing side effects from impure arguments

src/ast/ExecutionContext.ts:InclusionContext
Temporal

The mem::take(&mut self.code) operation assumes that the code string won't be needed again after parsing and that the ParseTask instance lifecycle aligns with the async task completion

If this fails: If the parsing task needs to retry or if the code is accessed after mem::take, the operation will panic or produce empty results, but this is more of a design constraint than a hidden failure mode

rust/bindings_napi/src/lib.rs:ParseTask
Domain

The symbolic representation of unknown object keys (UnknownKey, UnknownInteger, etc.) covers all possible dynamic property access patterns in JavaScript code and that no new access patterns will emerge

If this fails: If new JavaScript features introduce property access patterns not covered by the existing unknown key symbols, those accesses won't be properly tracked during tree-shaking, potentially causing over-aggressive elimination of code that has dynamic dependencies

src/ast/utils/PathTracker.ts:UnknownKey
Resource

The mimalloc allocator is available and compatible with the target platform, and that it provides better performance than the system allocator for the AST parsing workload

If this fails: If mimalloc fails to initialize or has compatibility issues on certain platforms, the Rust parser will fall back to system allocation, which may cause performance degradation but shouldn't break functionality

rust/bindings_napi/src/lib.rs:ALLOC

Open the standalone hidden-assumptions report for rollup →

How Data Flows Through the System

Source files enter through the CLI or API and are parsed by a Rust-based parser into serialized AST buffers. These buffers are converted to TypeScript AST nodes which undergo dependency analysis and scope resolution. A module graph is built by following imports, then tree-shaking eliminates unused code by analyzing variable references and side effects. Finally, the remaining modules are chunked and rendered into optimized bundle files.

  1. Parse source code — The parseAsync function in Rust bindings takes JavaScript/TypeScript source strings and uses swc to parse them into serialized AST buffers, handling syntax errors and modern language features like JSX and import attributes
  2. Convert to TypeScript AST — The convertProgram function deserializes the binary AST buffer into TypeScript Node objects by reading the custom buffer format and instantiating appropriate node classes from the nodeConstructors registry [SerializedAstBuffer → Node]
  3. Load and parse files — ModuleLoader resolves import paths using plugins and file system, loads source content, creates Module instances with parsed AST, and populates import/export information by analyzing declaration nodes
  4. Build dependency graph — Graph walks through modules starting from entry points, follows import declarations to load dependencies, creates ExternalModule instances for external dependencies, and builds a connected graph of all modules [Module → Module]
  5. Analyze dependencies — Each Module analyzes its AST to create Variable instances for declarations, binds Identifier nodes to their variables through scope resolution, and tracks import/export relationships between modules [Module → Variable]
  6. Tree-shake unused code — Starting from entry points and explicitly included statements, the system marks Variables and Nodes as included by following reference chains, using EntityPathTracker to detect side effects and HasEffectsContext to analyze execution flow [Variable → Variable]
  7. Generate chunks — Graph partitions included modules into Chunks based on entry points and dynamic imports, assigns modules to chunks to minimize duplication, and creates chunk-level import/export manifests [Variable → Chunk]
  8. Generate output code — Each Chunk renders its modules to code by calling render() on included AST nodes, applies finalisers for format-specific transformations like UMD wrapping, and Bundle combines chunks with source maps into final output files [Chunk → Bundle output files]

Data Models

The data structures that flow between stages — the contracts that hold the system together.

SerializedAstBuffer rust/parse_ast/src/lib.rs
Vec<u8> containing binary-encoded AST with custom format — includes node types, positions, and nested structures serialized for efficient transfer from Rust to JavaScript
Generated by Rust parser from source strings, transferred to JavaScript, then converted to Node objects for analysis
Node src/ast/nodes/shared/Node.ts
TypeScript interface with type: string, start: number, end: number, parent: Node, scope: ChildScope, included: boolean, plus node-specific properties
Created from AST buffers, analyzed for effects and dependencies, marked for inclusion during tree-shaking, then rendered to output
Module src/Module.ts
Class with id: string, code: string, ast: Program, imports: ImportDescription[], exports: ExportDescription[], dependencies: Set<Module>, isIncluded: boolean
Created when files are loaded, populated with parsed AST and import/export info, linked in dependency graph, then marked for inclusion based on usage
Variable src/ast/variables/Variable.ts
Class with name: string, isReassigned: boolean, included: boolean, references: Set<Identifier>, deoptimizationTracker: EntityPathTracker
Created during scope analysis from declarations, tracks all references and mutations, marked for inclusion during tree-shaking
RollupOptions src/rollup/types.ts
Interface with input: string | string[], plugins: Plugin[], external: string[] | function, output: OutputOptions, treeshake: TreeshakingOptions
Parsed from config files or API calls, normalized and validated, then used to configure the entire bundling process
Chunk src/Chunk.ts
Class with facadeModule: Module, dynamicEntryModules: Set<Module>, imports: Set<Variable>, exports: Set<Variable>, renderedSource: string
Created during chunking phase to group related modules, populated with included variables, then rendered to final bundle code

System Behavior

How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.

Data Pools

Module Cache (cache)
Caches loaded modules by their resolved ID to avoid re-parsing the same file multiple times during dependency resolution
Plugin Context State (state-store)
Maintains per-plugin state and context information including emitted files, resolved dependencies, and plugin-specific data throughout the build process
AST Node Registry (registry)
Maps AST node type strings to their corresponding TypeScript class constructors for deserializing AST buffers into proper node instances
Scope Chain (state-store)
Hierarchical scope structure that tracks variable declarations and bindings across nested scopes for proper identifier resolution

Feedback Loops

Delays

Control Points

Technology Stack

Rust (runtime)
High-performance JavaScript/TypeScript parsing using swc parser with custom AST serialization for efficient transfer to JavaScript runtime
TypeScript (framework)
Primary implementation language providing type safety for complex AST manipulation, module resolution, and bundling logic
NAPI (runtime)
Native addon interface for exposing Rust parser functionality to Node.js with async task support and memory-efficient buffer transfer
WASM (runtime)
WebAssembly compilation target for Rust parser to enable browser-based bundling without Node.js dependencies
MagicString (library)
Efficient source code transformation library for applying changes to code while maintaining accurate source maps
SWC (library)
Fast JavaScript/TypeScript parser in Rust that handles modern syntax, JSX, and provides detailed AST with location information
Mocha (testing)
Test framework for comprehensive test suites covering parsing, bundling, tree-shaking, and integration scenarios

Key Components

Explore the interactive analysis

See the full architecture map, data flow, and code patterns visualization.

Analyze on CodeSea

Related Cli Tool Repositories

Frequently Asked Questions

What is rollup used for?

Transforms ES modules into optimized bundles by parsing, analyzing dependencies, and generating output rollup/rollup is a 9-component cli tool written in JavaScript. Data flows through 8 distinct pipeline stages. The codebase contains 12625 files.

How is rollup architected?

rollup is organized into 6 architecture layers: Native Parser Layer, AST Processing Layer, Module System Layer, Bundling Layer, and 2 more. Data flows through 8 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.

How does data flow through rollup?

Data moves through 8 stages: Parse source code → Convert to TypeScript AST → Load and parse files → Build dependency graph → Analyze dependencies → .... Source files enter through the CLI or API and are parsed by a Rust-based parser into serialized AST buffers. These buffers are converted to TypeScript AST nodes which undergo dependency analysis and scope resolution. A module graph is built by following imports, then tree-shaking eliminates unused code by analyzing variable references and side effects. Finally, the remaining modules are chunked and rendered into optimized bundle files. This pipeline design reflects a complex multi-stage processing system.

What technologies does rollup use?

The core stack includes Rust (High-performance JavaScript/TypeScript parsing using swc parser with custom AST serialization for efficient transfer to JavaScript runtime), TypeScript (Primary implementation language providing type safety for complex AST manipulation, module resolution, and bundling logic), NAPI (Native addon interface for exposing Rust parser functionality to Node.js with async task support and memory-efficient buffer transfer), WASM (WebAssembly compilation target for Rust parser to enable browser-based bundling without Node.js dependencies), MagicString (Efficient source code transformation library for applying changes to code while maintaining accurate source maps), SWC (Fast JavaScript/TypeScript parser in Rust that handles modern syntax, JSX, and provides detailed AST with location information), and 1 more. A focused set of dependencies that keeps the build manageable.

What system dynamics does rollup have?

rollup exhibits 4 data pools (Module Cache, Plugin Context State), 4 feedback loops, 4 control points, 3 delays. The feedback loops handle recursive and recursive. These runtime behaviors shape how the system responds to load, failures, and configuration changes.

What design patterns does rollup use?

5 design patterns detected: Visitor Pattern, Plugin Architecture, Async Task Orchestration, Native Bridge Pattern, Dependency Injection.

Analyzed on April 20, 2026 by CodeSea. Written by .