Introduction to the
When you look at webpack source code, do you feel like reading a book, it seems that there is no way to see a file such as webpack.js from beginning to end. Webpack feels very jumpy and has no idea what’s going on when the program is running. It is not at all clear when this event occurred, such as when the Loader was executed and the plugin appeared. Webpack’s program is so convoluted that it’s completely lost in the program. Why is that? It’s really simple! Because the soul of Webpack is Tapable! This mechanism makes WebPack exceptions flexible and has a classic phrase – Everything is a plugin! . This shows that WebPack is stacked with plug-ins. The plugin mechanism is Tabable!
Events
Tapable, the spirit of Webpack, is similar to NodeJS ‘Events, where an event is registered and triggered when appropriate. The event firing here is done by binding an event to the on method and emit an event to the emit method. Tapable works similarly, with TAP registering an event and call executing that event.
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); // The first argument to on is the event name, which emit can then use to trigger the method. // The second argument to on is the callback function, MyEmitter. On ('newListener', (param1,param2) => {console.log("newListener",param1,param2)}); // The first argument to emit is the name of the event emitted. // The second subsequent argument to emit is the argument to the callback function. MyEmitter. Emit (' newListener, 111222);Copy the code
What Tapable is
If we think of a Tapable instance object as a giant tree, each branch of the tree is a mounted hook, which is the mechanism in Tapable that sorts each event, such as the one that runs in the compile.js object. There are make hooks, which are like branches that form the backbone of a tree, and the leaves on each branch are the function methods mounted on each hook. The more branches (hooks), the more leaves (functions), and the thicker the tree (the more complex the program).
Of course, this is just a simple understanding. In fact, there is more than one tree in the Webpack, and there are intricate relationships between each tree. For example, some methods, such as those in compiler.js, are executed only after the make hook in compiler.js executes. So let’s understand Tapable in Webpack by understanding the use of Tapable hooks.
Use the workday as an example to understand the use of Tapable
Even though there are intricate relationships between each tapable tree in the Webpack, the entire application has a logical thread, which is the main story in the game, and we start by building the main story of our workday.
Main story
Let’s go back and forth. Most of our working days should be divided into three stages: before work, during work and after work. During these three periods, I used three hook types, normal, flow and fuse. According to the document, they explained as follows:
- Basic: This is basic
tap
The registration sequence is executed one by one. - Water: This is different from BASIC, although it is also based on
tap
Is executed one by one, but if the previous tap had a return value, the incoming argument to the next tap is the return value of the previous tap. - Fuse Bail: This is the difference between water, if return the value of undefined, the bail will not continue to execute.
class SyncBailHookCodeFactory extends HookCodeFactory { content({ onError, onResult, resultReturns, onDone, rethrowIfPossible }) { return this.callTapsSeries({ onError: (i, err) => onError(err), onResult: (I, result, next) => // if(${result}! == undefined) {\n${onResult(result // continue)}; \n} else {\n${next()}}\n`, resultReturns, onDone, rethrowIfPossible }); }}Copy the code
Does it feel like there are so many types of subscriptions for one event? Don’t worry, every type has a role to play!
New SyncHook([“a”,”b”,”c”]); new SyncHook([“a”,”b”,”c”]); new SyncHook([” b”,”c”]); Note here that the parameter name is of type string. If you don’t have a parameter that needs to be passed in advance, you won’t be able to pass the parameter when you hang the function later. This should be designed to be manageable later, telling other developers what kind of parameters I’m passing in.
class MyDaily { constructor() { this.hooks = { beforeWork: new SyncHook(["getUp"]), atWork: new SyncWaterfallHook(["workTask"]), afterWork: new SyncBailHook(["activity"]) }; } tapTap () {} / / this is a behavior the run () {this. Hooks. BeforeWork. Call () enclosing hooks. AtWork. The call () enclosing hooks. AfterWork. The call ()}}Copy the code
We can’t do nothing all day, so add something to the hook, tap something. Let’s start with the inevitable, normal office workers, freelancing is out of the picture. What do we do in the morning? Wearing clothes to go out is a must. You can’t go out without clothes. You can’t go to work without clothes. When we get to work, let’s do some tasks. For example, we need to make a PPT, and then use this PPT to go to the meeting. After work, originally want to go home, the result of beauty about, as expected do not go home.
TapTap () {enclosing hooks. BeforeWork. Tap (" putOnCloth ", () = > {the console. The log (" wear clothes!" )}) enclosing hooks. BeforeWork. Tap (" getOut, "() = > {the console. The log (" go!" )}) enclosing hooks. AtWork. Tap (" makePPT ", () = > {the console. The log (" do PPT!" Tap ("meeting",(work)=>{console.log(" Bring your "+work+" meeting! ") Afterwork.tap ("haveADate",()=>{console.log(" date! ")}) this.links.afterwork.tap ("haveADate",()=>{console.log(" date! ") Afterwork.tap ("goHome",()=>{console.log(" console.log ")) {console.log(" console.log ");}) )})}Copy the code
From the above, we can see the usage of various synchronous hooks in the leading role, which may be difficult to understand. The meaning of this hook is to interrupt a series of things, for example, if something goes wrong, or we can finish it in time without the next operation.
So if everything we do is asynchronous, every event has a connection, then we can’t do it synchronously. At this point we can replace the sync hook with the async hook.
Async has a callback mechanism that works like this:
Enclosing hooks. BeforeWork. TapAsync (" putOnCloth ", (params, callback) = > {the console. The log (" wear clothes!" ) callback(); / / here no callback, getOut the mount function won't run}) enclosing hooks. BeforeWork. TapAsync (" getOut, "(params, callback) = > {the console. The log (" go!" ) callback()// No callback, Is beforeWork this hook callback function does not perform}) enclosing hooks. BeforeWork. CallAsync (" working ", err = > {the console. The log (err + "end!" )// If the last tap function has no callback, it will not execute})Copy the code
Here we can think of callback as the next function, which means the next tap function. Callback (“errorReason”) will return to the function that called the current hook’s callAsync binding.
Enclosing hooks. BeforeWork. TapAsync (" putOnCloth ", (params, callback) = > {the console. The log (" wear clothes!" ) callback("error"); I put in the error cause, so callAsync, Abandoned getOut}) this. Hooks. BeforeWork. TapAsync (" getOut, "(params, callback) = > {/ / skip directly to the console. The log (" go!" ) }) this.hooks.beforeWork.callAsync("working",err=>{ console.log(err+" end!" / / error end! Type the reason for the error directly. })Copy the code
Small tips
The difference between Async and sync is that Async communicates with subsequent functions via callback, while sync communicates by returning a value. So Async comes with Async bail type hook. I did a boring statistic once, because there were too many hooks, and I wrote a code that walked through the Webpack project to get the usage of all the hooks, which looked like this:
SyncHook 69
SyncBailHook 63
SyncWaterfallHook 36
SyncLoopHook 0
AsyncParallelHook 1
AsyncParallelBailHook 0
AsyncSeriesHook 14
AsyncSeriesBailHook 0
AsyncSeriesWaterfallHook 5
Copy the code
AsyncSeriesBailHook = 0. AsyncSeriesBailHook = 0. AsyncSeriesBailHook = 0. AsyncSeriesBailHook = 0. Bail repeats functions in Async and is rarely used.
Since the callback of AsyncSeriesHook uses the first err parameter to determine whether the asynchron succeeded or not, the callback will be called directly if the asynchron failed. How does water pass its argument? We all know that the difference between water and BASIC is that basic has no parameter passing between each asynchronous tap, while water is parameter passing. Simply add an argument to err as the value passed in for the next tap.
Enclosing hooks. AtWork. TapAsync (" makePPT, "(the work, the callback) = > {the console. The log (" do PPT!" Callback (" not done "," your PPT ")// The first parameter is err, submit your error, the second parameter is your custom to the next tap processing parameter. If err exists, this parameter is ignored. }) enclosing hooks. AtWork. TapAsync (" meeting, "(the work, the callback) = > {/ / because PPT haven't finished, so can't open the console. The log (" with" + work + "meeting!" ) callback ()}) this. Hooks. AtWork. CallAsync (" working ", err = > {/ / not finished here to get fined. console.log(err+" end!" )})Copy the code
subplots
Our daily life! Won’t be so monotonous, how can all the way smoothly walk down? Every day has different wonderful ah! There are bound to be minor incidents.
So we had to find a way to insert the side story into the main story. At this point, a MyDaily class can no longer accommodate our wonderful life.
After work, for example, we don’t have to go straight home but we might have a disco date or something. So here we’re going to new a class called Activity. Let’s say our night life has two activities, a party event and a homecoming. So there must be a process for the party, and we use the fuse type. Why?! If you’re not happy, don’t do it! Go home! The activity of going home is simple hook.
class Activity {
constructor() {
this.hooks = {
goParty:new SyncBailHook(),
goHome:new SyncHook()
};
}
prepare(){
this.hooks.goParty.tap("happy",()=>{
console.log("happy party")
})
this.hooks.goParty.tap("unhappy",()=>{
console.log("unhappy")
return "go home"
})
this.hooks.goParty.tap("play",()=>{
console.log("continue playing")
})
this.hooks.goHome.tap("goHome",()=>{
console.log("I'm going to sleep")
})
}
start(){
this.hooks.goParty.call()
this.hooks.goHome.call()
}
}
Copy the code
And then we’re going to attach this single class to MyDaily, which is part of our daily routine, even though it’s an informal level. We can start preparing for the evening after work and start our rich nightlife as soon as we leave work. At this point we can trigger the hook state of another class in the hook callback function, activate or run.
class MyDaily { constructor() { this.hooks = { .... }; This.activity =new Activity()// Instantiate activity} run(){... Enclosing hooks. AtWork. CallAsync (" working ", res = > {this. Activity. Prepare () / / off work you can stir up}) Enclosing hooks. AfterWork. CallAsync (" activity ", err = > {this. Activity. The start () / / wave to cough up! }}})Copy the code
conclusion
I’m just giving you a little example of what tapable is. By understanding the tapable feature, we were able to understand the mechanism of WebPack later, and it was difficult to understand the source code of WebPack because of the hook inside the hook. In the next article I will take you through the flow of webpack’s main story and main side story (loader&plugin)!