How Does Webpack Build a Dependency Graph?
Webpack constructs an internal dependency graph by recursively mapping every module your application needs, starting from defined entry points. During this compilation phase, it parses files into Abstract Syntax Trees (ASTs) to locate import statements, runs them through configured loaders, resolves file paths, and tracks relationships between assets. The resulting directed acyclic graph serves as the blueprint Webpack uses to optimize, bundle, and emit static assets for deployment.
Understanding the Entry Point and the Graph Origin
The compilation pipeline begins at the entry point specified in
webpack.config.js. This file, commonly
index.js or main.js, acts as the root node of
the dependency graph. Webpack's internal compiler initiates a
Compilation instance that passes this root file to a module
factory.
The factory reads the source code from the file system and converts
it into a NormalModule object. This initial module serves
as the anchor from which Webpack starts discovering all downstream
dependencies across the project.
Parsing Code and Constructing the AST
Once Webpack loads the source code of an entry file, it uses an internal JavaScript parser based on Acorn to generate an Abstract Syntax Tree (AST). The AST converts raw code into a structured tree representation of syntax nodes, allowing Webpack to safely inspect code semantics without executing it.
While traversing the AST, the parser searches for explicit module import syntax, including:
- ES Module statements:
import ... from '...'and dynamicimport() - CommonJS statements:
require('...') - AMD syntax:
defineandrequire - Asset references: CSS
@import,url(...), or HTMLsrcattributes (when loaders enable them)
Each time the parser encounters a dependency, it registers a
Dependency object on the current module. This object
contains the raw request string (such as './utils/math.js')
and the exact location where the dependency was declared in the
source.
Resolving Paths with Enhanced-Resolve
A raw string like 'react' or
'../components/Button' cannot be loaded until Webpack
resolves its precise file system location. Webpack delegates this task
to its internal resolver package, enhanced-resolve.
The resolver checks configured extensions, path aliases, node modules
directories, and package entry fields (like exports or
main in package.json). Once
enhanced-resolve returns the absolute canonical path on
disk, Webpack checks whether a module with that identifier has already
been processed. If it has, Webpack reuses the existing module reference
to avoid duplicate work and prevent circular dependency loops.
Applying Loaders and Transforming Non-JavaScript Modules
Webpack natively parses only JavaScript and WebAssembly. When non-JavaScript files—such as TypeScript, Sass, or raw images—are encountered as dependencies, Webpack executes loaders before passing the code to the AST parser.
Loaders run in reverse order (bottom-to-top, right-to-left) to transform arbitrary assets into valid JavaScript or data URLs. For example:
- A CSS file is processed by
sass-loaderinto standard CSS. css-loaderinterprets@importandurl()statements, transforming them into JavaScript module requests.style-loaderor asset extractors inject or bundle the resulting styles.
Because loaders transform non-code assets into JavaScript modules that can declare dependencies of their own, non-code files participate directly as nodes within the dependency graph.
Recursive Graph Traversal
Building the dependency graph is an asynchronous, breadth-first or depth-first recursive operation. The entire pipeline repeats for each newly discovered dependency:
- The dependency is identified in the parent's AST.
- The file path is resolved to an absolute path.
- The file contents are loaded and processed through matching loaders.
- The output is parsed into a new AST to discover child dependencies.
- Child dependencies are queued for processing.
The graph traversal continues recursively until all branches reach leaf nodes—modules with zero remaining unparsed dependencies. If two modules import each other, Webpack’s internal module registry detects the existing absolute path, links the edge, and halts infinite recursion.
Module Graph Representation and Chunk Creation
At the end of the build phase, Webpack holds a complete
representation of the project structure known internally as the
ModuleGraph. In this structure:
- Nodes represent unique modules (
NormalModuleinstances). - Edges represent connections established by
Dependencyobjects.
Webpack then converts the ModuleGraph into a
ChunkGraph. Static dependencies are grouped into initial
chunks, while dynamic import() calls define split points
that create asynchronous chunks. After tree shaking (dead code
elimination) and scope hoisting plugins analyze the connections in the
graph, Webpack translates these chunk groups into the final bundled
files ready for production.