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 dynamic import()
  • CommonJS statements: require('...')
  • AMD syntax: define and require
  • Asset references: CSS @import, url(...), or HTML src attributes (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:

  1. A CSS file is processed by sass-loader into standard CSS.
  2. css-loader interprets @import and url() statements, transforming them into JavaScript module requests.
  3. style-loader or 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:

  1. The dependency is identified in the parent's AST.
  2. The file path is resolved to an absolute path.
  3. The file contents are loaded and processed through matching loaders.
  4. The output is parsed into a new AST to discover child dependencies.
  5. 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 (NormalModule instances).
  • Edges represent connections established by Dependency objects.

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.