🐛🔨 The Contributors Guide to webpack — Part 3🎨 🖼
Building the Dependency Graph
The Contributors Guide to webpack is a multi-part publication series outlining the many ways that you can learn about, and contribute to the webpack open source project. You can read the first article in the series here!
Recap
In Part 1 of this series we learned about the packages that reside within both the webpack and webpack-contrib GitHub organizations! In Part 2, we discovered Tapable
, learned that it is a ~230 line library similar to NodeJs’s EventEmitter
, and found that it powers the entire [webpack] plugin system.
In addition to this, we learned how webpack creates tapable instances (Classes that extend Tapable
) and how [webpack] plugins register to them and execute their functionality. Finally, we learned about each tapable instance that exists in webpack and what their purpose is.
Building the Dependency Graph
In this article, we are going to combine what we’ve learned so far, and stitch together a high level explanation of how webpack builds the dependency graph.
The dependency graph is one of the key features of webpack, and we believe understanding how it works, can bring much insight to contributors and users alike.
Throughout this information you will find diagrams that come from my talk Everything’s a plugin: Mastering webpack from the inside out. These diagrams will serve as an aide to the material but we also recommend watching the first half of the talk as a companion to this guide.
Step 1: Initialize (Compiler)
Given that we already have a valid webpack configuration (known as the compiler options), the first tapable instance that we are going to encounter when webpack runs is known as the Compiler
. Consider the Compiler
as “central dispatch” because it is only in charge of triggering high level events such as "run"
, "failed"
, "done"
. The Compiler will always return a Compilation
, and other important tapable instances are attached such as NormalModuleFactory
and ContextModuleFactory
.
After the Compiler
has instantiated all plugins and objects needed to compile, it will return a new Compilation
.
Step 2: Begin Compilation (Build the Graph)
After the Compilation
(yes another tapable instance).
We describe the Compilation
as your application’s dependency graph. To create a full graph, we must start from somewhere; a “root node” that branches out to all other “nodes”.
What we’re describing is the entry property in your configuration. Although we provide a path to the entry point, webpack still needs to confirm that path exists. Below will begin a recursive set of operations. (We’ll jump back to the recursion later)
Resolve
Any time that a raw request (path to a module) is provided — in this case its the entry point — webpack will first send this path information to the Resolver
instance. The Resolver
will use an enhanced NodeJs resolution pattern to to ensure the module at the given path exists, and then return additional information about that resolved module. This information includes file system stats, absolute path, and a unique ID for that resolved module.
- Create/Build Module: The
Resolver
then sends the resolved module information to theNormalModuleFactory
where the source information will be captured, stored in memory (and cached).
- Parse Module: The module factories will also assign the
Parser
instance to eachNormalModule
object that the factory will create. After the module source has been stored, theParser
will parse the file. in addition, it will send the module through a series of transforms known as loaders. A loader chain will always return JavaScript source in the end, and therefore, theParser
(powered by acorn) can now begin to parse the source and generate an Abstract Syntax Tree (AST). - Find Dependencies: Now that we have our modules information in an AST, we can now “walk” the AST for specific types of statements and expressions. What we are looking for are what we define as dependency statements. Therefore, the
Parser
“walks” the AST and comes across the AST information for arequire("foo")
statement, then that information is stored in aDependency
instance and is attached to that module.
- Repeat the process: Once all
Dependencies
for that module have been found, we need to then “process” them. This is where the recursion occurs. Each dependency will need to go through these bullet points above to find the module it is pointing to.
Now what?
With the information you have learned, pick one of the following Tapable instances mentioned above, and go to our core repository and find a plugin written for that instance. Observe how information and state flows between them and how the Parser
leverages events to create Dependency
sub-classes!!!
I personally love using chrome://inspect and use the Dedicated Node Debugging Tools to help me step through breakpoints and complex pieces of the source code.
Starting from somewhere that is familiar and understandable and branching out is one of the best ways to dive into a new code base!! To stick with the theme of this article, my recommendation would be Compiler.js or Compilation.js!
Learning the source, writing your own plugins, asking questions, and/or take notes!! These are all incredible ways that you can not only build your own skills, but equip yourself to be the next webpack contributor!!! And at the end of the day, have fun!!
Stay subscribed to our medium publication and stay tuned for the next part in this series as we learn what webpack does with this graph to create the bundles we see.
No time to help contribute? Want to give back in other ways? Become a Backer or Sponsor to webpack by donating to our open collective. Open Collective not only helps support the Core Team, but also supports contributors who have spent significant time improving our organization on their free time! ❤