If you like, please follow my blog or subscribe to RSS feed. Previous articles:
- 【 Webpack advanced 】 Modular design and implementation of front-end runtime
- Use Babel to avoid webPack compilation runtime module dependencies
The introduction
Webpack’s success lies not only in its strong packaging build capability, but also in its flexible plug-in mechanism.
You probably know webPack’s plugins and hooks; But what you may not know is that webPack has more than 180 hooks inside, and the “create”, “register” and “call” relationships between these hooks and modules (built-in plug-ins) are very complicated. Therefore, understanding the relationship between plug-ins and hooks within WebPack will help us further understand the internal execution of WebPack.
“Webpack module/built-in plug-in and hook diagram 📈” : Complexity is also visible.
The first part of this article introduces the important concept of hooks and how the WebPack plug-in works. However, familiar friends will find that this flexible mechanism makes the connection between WebPack modules more loose and uncoupled, while making it more difficult to sort out the source code structure and connection within WebPack.
So, part two will introduce a visual presentation of the plug-in and hook relationships within WebPack, 📈, using a diagram to clarify the intricacies within WebPack.
Visualization tool effect drawing:
1. Plugin mechanism for Webpack
Before getting into the details of the built-in plug-in and hook visualization tools for WebPack, let’s take a look at the plug-in mechanism in WebPack.
Webpack implements the plugin mechanism in a general way:
- “Create” – WebPack creates various hooks on its internal objects;
- “Register” – plug-ins register their methods with the corresponding hooks and hand them to WebPack;
- “Call” – During WebPack compilation, the appropriate hooks are triggered and therefore the plug-in’s methods are triggered.
1.1. Tapable
Tapable is the library that WebPack uses to create hooks.
The tapable packages exposes many Hook classes, which can be used to create hooks for plugins.
Tapable allows you to quickly create hooks. The following are the class functions for the various hooks:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
Copy the code
The simplest SyncHook, for example, allows you to create a synchronized hook. To help understand how Tapable creates hooks, let’s use a “walk home from work” simulation to show how Tapable is used.
Now we have a welcome.js module that sets out our “coming in and coming home” behaviors (opening doors, taking off shoes…). :
// welcome.js
const {SyncHook} = require('tapable');
module.exports = class Welcome {
constructor(words) {
this.words = words;
this.sayHook = new SyncHook(['words']);
}
// A series of behaviors of entering and returning home
begin() {
console.log('open');
console.log('take off their shoes');
console.log('Take off your coat');
/ / say "hello"
this.sayHook.call(this.words);
console.log('shut down'); }}Copy the code
First, we create a synchronous hook, sayHook, in the constructor, which is used for subsequent greetings.
Then, the begin() method describes the sequence of actions we take as soon as we get home and enter the door: open the door, take off our shoes, take off our coat, and close the door. Between “take off your coat” and “close the door” is a greeting, where we trigger a sayHook and pass words as an argument.
Note that the.call() method here is Tapable’s hook trigger method, not js’s native call method.
Triggering this sequence of processes is also very simple:
// run.js
const Welcome = require('./welcome');
const welcome = new Welcome('I'm back! ');
welcome.begin();
/* output: * open door * take off shoes * take off coat * close door * /Copy the code
Next, we want to have different greetings — “normal greetings” and “shoutouts.”
We will have two modules say.js and shout. Js, which are registered on sayHook with the.tap() method.
// say.js
module.exports = function (welcome) {
welcome.sayHook.tap('say', words => {
console.log('Whisper :', words);
});
};
// shout.js
module.exports = function (welcome) {
welcome.sayHook.tap('shout', words => {
console.log('With a sudden cry :', words);
});
};
Copy the code
Finally, we modify run.js to apply the welcome module shout.
// run.js
const Welcome = require('./welcome');
const applyShoutPlugin = require('./shout');
const welcome = new Welcome('I'm back! ');
applyShoutPlugin(welcome);
welcome.begin();
/* output: * open the door * take off your shoes * take off your coat * Surprise shout: I'm back! * Close the door * /Copy the code
Thus, we decouple the implementation of greeting from welcome. We can also use the say.js module, or even use both together with shout. It’s like creating a “pluggable” system — I can choose whether or not to say hello, and how I want to say hello, depending on my needs.
Although the above example is very simple, it helps to understand the use of Tapable and the idea of plug-ins.
1.2. Plug-ins in Webpack
Before introducing the plugin mechanism for WebPack, let’s briefly review the “in and out” example above:
- our
Welcome
Classes are the primary function classes that contain concrete function functionsbegin()
With a hooksayHook
; run.js
The module is responsible for executing the process and controlling the code flow;- In the end,
say.js
andshout.js
Are separate “pluggable” modules. We can attach to the main process as needed.
With this example in mind, a good analogy can be made to WebPack:
For example, Compiler, an important class in Webpack, creates lots of hooks that will be called “all over the place.” It’s like our Welcome class.
// Compiler class
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
……
}
Copy the code
The plug-in in webpack then registers the functions to be executed with the corresponding hooks via methods such as.tap() /.tapasync () /.tappromise (). This way, when WebPack calls the corresponding hook, the functions in the plug-in are automatically executed.
So, one more question: how does WebPack call the plug-in to register the methods in the plug-in with the hooks at compile time?
For this problem, WebPack specifies that every instance of a plug-in must have a.apply() method, which all plug-ins call before webPack is packaged, and plug-ins can register their hooks in that method.
In webpack’s lib/webpack.js, there is the following code:
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin ofoptions.plugins) { plugin.apply(compiler); }}Copy the code
The code above fetches all the plug-in instances from the Plugins field in the WebPack configuration, then calls the.apply() method, passing in the Compiler instance as an argument. That’s why WebPack requires that all of our plug-ins provide.apply() methods and register hooks in them.
Note that.apply(), like.call(), is not a native js method. You’ll see a lot of.call() and.apply() in the source code, but they’re not nearly the same methods you recognize.
2. Process of triggering hooks at compile time (in Compiler)
There are already some excellent articles on the web that parse WebPack. There are also some articles on the compilation process of Webpack and introduction.
However, due to my recent work and interests, I need to do some in-depth research on the execution steps and details inside Webpack, including the registration of various hooks and methods, trigger timing, conditions and so on. The current content of some articles may not be enough to support, so we have done some sorting accordingly.
2.1. A diagram to be perfected
The following is the execution flow of the.run() method and the trigger of the hooks in the Compiler I sorted out earlier.
But there are some difficulties in the process of combing. If you’ve ever tried to peruse the WebPack source code and comb through the internal module and plug-in execution processes and relationships, you may have encountered the same trouble I did. Here’s a look:
2.2. Problems with plugins and hooks
First of all, you can see that the diagram is more complex than the overall flow chart common on the Internet because it is smaller. However, even with webPack’s common plug-ins, Compiler hooks, and compilation hooks, this diagram is only a small part of the picture. Not to mention a hundred other hooks you probably never touched. These modules and hooks weave together a complex Webpack system.
Secondly, in the process of reading and arranging source code, there will be several problems:
-
Loose connections. From the example above, you can see that while tapable hooks are effectively decoupled, hook registration is almost completely independent of the call, making it difficult to effectively relate a hook’s “create, register, call” process.
-
Module interactions are based on hooks. Webpack internal modules and plug-ins in many cases, through the hook mechanism to contact and call. However, the hook-based pattern is loose. For example, you can see that a module provides several hooks in the source code, but you don’t know when and where the hook will be called, and what methods are registered on the hook when and where. All of this used to be solved by searching for keywords in the code base.
-
Hooks are numerous. Webpack has a large number of hooks, up to 180+, and a wide variety of types. In addition to those commonly used in the Compiler and compilation listed on the website, there are many other hooks that can be used. There are some useful hooks that you may not know about, such as localVars, requireExtensions, and others that I used recently.
-
There are many built-in plug-ins. Webpack V4 + itself comes with a number of plug-ins built in. Even without plug-ins, webpack modules themselves often interact with tapable hooks. You can even think of each module in a WebPack project as “plug-in.” This also causes almost every module to “deal” with various hooks.
These issues make it difficult to fully understand the relationship between modules/plug-ins in WebPack (the core is the relationship with hooks). To help you understand and read the WebPack source code, I created a small tool that visualizes the relationship between built-in plug-ins and hooks, and supports further source information through interactive operations.
3. Webpack Internal Plugin Relation
Webpack-internal-plugin-relation is a tool that can show the relationship between Webpack modules (plug-ins) and hooks. The GIF shown at the beginning of the article is its function and use effect.
Github storage address: github.com/alienzhou/w… You can view the online demo here
3.1. Relationship types
The relationships between modules/plug-ins and hooks fall into three main categories:
- Module/plug-in Create hook, such as
this.hooks.say = new SyncHook()
; - Modules/plug-ins “register” methods to hooks, as in
obj.hooks.say.tap('one', () => {... })
; - A module/plug-in “calls” hook events, such as
obj.hooks.say.call()
.
3.2. Effect demonstration
You can demonstrate the relationship between modules/plug-ins and hooks:
You can display the hook information in the module by clicking and so on. Double-click to jump directly to the corresponding source code of Webpack:
Because relationships are very complex (600+ relationships), you can filter the relationship type to show only what you care about:
3.3. Functions included in the tool
Specifically, this tool includes the following functions:
-
Relationship collection:
- Collect hook creation information in the module, that is, hook creation information;
- Collect registration information of hooks in modules and record which modules register which hooks;
- Collect information about hook calls in the module, that is, on which line of code the hook is fired.
- Generate files containing raw data such as module information, hook information, source location information, etc.
-
Visual display:
- Visualize the relationships between plug-ins and hooks using force guides. You can see that there are currently over 180 hooks and over 130 modules in WebPack V4;
- Displays a list of all modules and hooks.
-
Interactive information:
- Support the screening of nodes in the force guide diagram;
- Click the javascript Module class node to view details of the module in the lower left corner;
- Double-click javascript Module class node, can directly open webpack corresponding source view;
- Double-click the relationship between nodes, can directly open and locate the specific number of lines of source code, to view;
- You can select the relationship to view: create -contain/register -register/call -call.
3.4. Customize your own functionality based on raw data
Currently, the tool retains the original collection results. So, if you don’t need visualizations or have your own customization needs, you can work with this information and use it where you need it. The original information structure of the module is as follows:
"lib/MultiCompiler.js": {
"hooks": [{"name": "done"."line": 17
},
{
"name": "invalid"."line": 18
},
{
"name": "run"."line": 19
},
{
"name": "watchClose"."line": 20
},
{
"name": "watchRun"."line": 21}]."taps": [{"hook": "done"."type": "tap"."plugin": "MultiCompiler"."line": 37
},
{
"hook": "invalid"."type": "tap"."plugin": "MultiCompiler"."line": 48}]."calls": [{"hook": "done"."type": "call"."line": 44}}]Copy the code
End of 4.
This webpack-internal-plugin-relation widget works by:
- Walk through the WebPack source module file
- Parsing retrieves hook information
- Process the original collected information and convert it into the format required by the guidance chart
- Build front-end Web visualization service based on force – guided graph data
- Finally, there are some interactive functions
I am currently using it to help read and organize the webapck source code and compilation process. Maybe some friends have a similar problem and share it in the hope that it can help you in some way. If you are also interested in Webpack or this tool, I hope you can support my articles and tools, exchange and learn ~ 😊
Farewell to webPack Configuration Engineer
Write it at the end.
Webpack is a powerful and complex front-end automation tool. One of these features is the complexity of configuration, which has led to the popularity of the tongue-in-mouth term “WebPack configuration engineer” 🤷 But are you really content with just playing around with WebPack configuration?
Apparently not. In addition to learning how to use WebPack, we need to explore the design and implementation of various parts of Webpack. Even when WebPack is “out of fashion,” some of its designs and implementations will still be worth learning from. Therefore, in the process of learning Webpack, I will summarize a series of [Webpack advanced] articles and share with you.
Welcome interested students more exchanges and attention!
Previous articles:
- 【 Webpack advanced 】 Modular design and implementation of front-end runtime
- Use Babel to avoid webPack compilation runtime module dependencies