🐛🔨 The Contributors Guide to webpack — Part 2 🎨 🖼
Diving Deeper: Tapable, plugins and design patterns!
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!
🌊 Diving Deeper 🌊
Although this and the previous guides are about contributing to webpack as a whole, I want to spend time covering some paradigms you will see across multiple projects first.
BONUS: For those who are interested in contributing to webpack core, the webpack resolver, or just want to start writing your own plugins, then this article is especially applicable for you!!
🍻 Tapable 🍻
As mentioned above, Tapable
is the core building block for webpack. Class
‘s and Object
‘s that extend Tapable
are known as “tapable instances”. Here is a very simplified example seen below:
const Tapable = require('tapable');class Compiler extends Tapable {
constructor() {
this.foo = "43";
this.applyPluginsAsync("run", this, function(err, done) {
if (done) {this.doRunStuffAfterSuccess()}
}this.applyPlugins("done", stats);
}
}
Tapable
adds event emitting functionality that plugins can hook into. Hooks are easily identified by searching for this.applyPlugins
in whatever codebase you are working in.
🔎 Anatomy of a plugin 🔍
The shape of a [webpack] Plugin is best described as a class
that implements an apply()
method. This method is called by the tapable instance to initialize the plugin’s event hooks. Take the following example:
class MyFirstPlugin {
apply(compiler) {
compiler.plugin("run", function(compiler, cb) {
console.log("webpack is about to start bundling");
cb(); // cb() signals to the compiler that this asnyc hook is finished.
});compiler.plugin("done", function(stats) {
console.log("webpack is finished bundling");
});
}
}
There are a few things going on here:
- The
compiler
instance is passed into theapply()
method. This gives you access to the tapable instance so you can “tap” into the events it emits. - To hook into an event, one must call
.plugin()
on the tapable instance, and then specify the name of the event and a callback function where we will write our custom logic. IE:.plugin("eventName", function(){})
- If the event is asynchronous, the last parameter received from the event callback, will be a callback hook which signals when our custom logic is completed.
How to register a plugin
A tapable instance can register a plugin, by calling apply()
and passing in a `new`
instance of that plugin. For example, to register the example plugin above, we would write compiler.apply(new MyFirstPlugin());
Design patterns with plugins
[webpack] Plugins are designed to be single purpose. You will often find this pattern throughout the core source code. Observe the example below:
EntryOptionPlugin
’s only purpose is to hook into the compiler to obtain all entry points defined in the configuration object and convert those entry values to SingleEntryPlugin
instance or MultiEntryPlugin
instance. These plugins are then passed back to the compiler to be registered with the apply()
method.
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!!
Tapable instances (classes you can plugin to) in webpack
Now that you have an idea of how webpack and its plugin system works, let’s look at the tapable instances that are used by webpack:
- Compiler — The compiler runtime. Contains all of the top level compiler hooks. Plugins can hook into events like
run
andemit
. - Compilation — The product of the webpack
Compiler
class. TheCompiler
returnsCompilation
‘s. This represents the entire dependency graph for your application!Compilation
contains hooks likeoptimize-modules
,seal
andoptimize-chunk-assets
. Any time optimizations and overall full program transforms and utilities will be performed on theCompilation
. - Resolver(s) — webpack resolvers are created from
enhanced-resolve
. Plugins that tap into the webpack resolvers, are used to customize the module resolution strategy. Any property inside of theresolve
object in your webpack configuration corresponds to specific resolver plugins that are applied. - NormalModuleFactory — The
NormalModuleFactory
is the glue that binds the resolver, loaders, and the creation of theNormalModule
instances together. Lifecycle hooks that you would plugin to may includebefore-resolve
,after-resolve
, andcreate-module
. In addition to this, loaders are run against every module and transforms them from their current file format into something that can be added into a webpackChunk
. - ContextModuleFactory — The purpose of the
ContextModuleFactory
is identical to theNormalModuleFactory
except it is forContextModules
a special webpack module type that enables “dynamic” requires. - Template — Because webpack performs code generation when it bundles, the
Template
instance is responsible for binding module data (via multiple template subclasses) to generate the shape and structure of the output bundles produced. - Template Subclasses — There are many levels of templates:
MainTemplate
(the runtime bundle wrapper), andChunkTemplate
(the template that controls features likes the shape and format of theChunk
wrapper itself). Each source abstraction will contain its own template as well (ModuleTemplate
,DependencyTemplate
). Because of this, everyTemplate
subclass can be tapped into withmodule
,render
, andpackage
hooks. - Parser — The webpack
Parser
is one of the most unique tapable instances in the webpack source code. The parser instance is a tapable instance powered byacorn
AST parser. A reference to the parser lives on everyNormalModule
instance. Plugins that use the parser will utilize hooks for every lexical symbol type for the AST. Any of the plugins in our webpack core source code ending inParserPlugin
hooks into these events.
Any of the aforementioned classes can emit Tapable
events and therefore have plugins written for them!
Now what?
With the information you have learned, pick one of the following tapable instances, and goto http://github.com/webpack/webpack and find a plugin written for that instance. Starting from somewhere that is familiar and understandable and branching out is one of the best ways to dive into a new codebase!! Then feel free to take a look at our work-in-progress Plugin API documentation for more examples!
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 tuned for our next installment as we put both Part 1, and Part 2 together to bring you the overall “System Design”!!
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! ❤