concept

The official link

Tapable is like a publishing and subscription center, and the basic process is

  1. Register one or more instances of one or more hooks of one or more types that you need
  2. Register consumers for this instance
  3. Trigger the hook instance at some point to execute the consumer

The end result is for us to use various hooks, tapable is the name of the library, and we actually use these hooks

The tapable library outputs 9 types of hook classes, as follows:

"use strict";

exports.__esModule = true;
exports.Tapable = require("./Tapable");
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");
Copy the code

HOOK profile

Execution efficiency

The Hook will compile a method with The most efficient way of running your plugins The most efficient way for consumers. It relies on the following factors to produce the logic for final execution:

  1. Number of registered plugins
  2. The type of plugin registered
  3. The sync Async Promise method is executed
  4. Number of parameters
  5. Whether injection is used [Intercept API]

Hook type

Execution order

Each hook can register (by Tap tapAsync tapPromise) multiple functions [consumer]. How this is executed depends on the type of hook itself:

  1. Basic Hooks simply execute all the registered functions
  2. Waterfall passes the return value of the last function to the next one
  3. The Bail allows early termination, as long as one of the functions returns a value, none of the subsequent functions will be executed
  4. Loop is executed from the first plugin as long as one plugin returns a value until all plugins return undefined

Synchronous asynchronous

In addition, hooks can be synchronous or asynchronous, including the following three types:

  1. Sync can only register synchronized functions using the TAP method
  2. AsyncSeries can be used with the Tap tapAsync tapPromise method for registering synchronized, callback, promise functions. The registered function is called line by line
  3. AsyncParallel can be used with the tap tapAsync tapPromise method for registering synchronized, callback, and promise functions. The registered function is called concurrently.

Note: Except that the consumer defined with the tapPromise method must return a promise.

Combined results and examples

Finally hook type has two aspects above, a total of nine [listed in the tapable library index. Js see 】, less than 12, such as no AsyncParallelWaterfallHook type, AsyncSeriesWaterfallHook. Here are two examples:

  1. AsyncSeriesWaterfallHook allows asynchronous functions to be executed sequentially, passing the return value of the previous function to the next function.
  2. When AsyncSeriesHook executes a call, if a step is not registered with the consumer in tapPromise, it will not be executed later because it should use something like AA and will be waiting.

Intercept API injection

All hooks provide the Intercept API. This API passes an object containing the following function fields: context【 Boolean 】 True can be used to pass any value. Triggered when the call hook is triggered, and triggered when the tap registration function is substituted for the hook trigger parameter. The tapObject passed in cannot be modified. Loop Loop hook The register function is triggered when the loop is entered. The tapObject passed in can be modified

Registered consumer

There are three ways

There are three ways of tap tapPromise tapAsync.

  1. Synchronized hooks can only be added with the tap method
  2. The asynchronous hook also supports the asynchronous plugin, that is, the plugin can be added by three methods: Tap tapPromise tapAsync

About tapPromise tapAsync

The tapPromise method call returns a promise. The tapAsync method can execute asynchronous functions internally, but requires a callback to perform the callback

Trigger consumer

Triggering these plugins defined in functions such as TAP requires a series of methods such as Call

  1. Tap corresponds to the call method
  2. TapPromise corresponds to the Promise method
  3. TapAsync corresponds to the callAsync method

However, hooks of a type such as AsyncParallelHook AsyncSeriesHook do not have a call method, only a Promise callAsync method, although a tap method can be used to define a consumer. Asynchronous hooks can be tapped synchronously to add consumers, but they cannot be called synchronously when triggered. [Explained in the source code introduction section below]

Source introduction

SyncHook, for example

A brief analysis of registered consumers

How are the tap tapPromise tapAsync functions defined for how to register consumers?

  1. throughoptions = this._runRegisterInterceptors(options);Under the transformedoptionsParameters. However, if the hook instance is not configuredintercept apiinterceptor.registerIf so, do nothing.
  2. throughthis._insert(options);Add the new consumer to the hook

On the case. Check whether the parameters such as before stage are set. If so, reorder them; if not, add new consumers directly to the back.

Trigger consumption brief analysis

In /node_modules/[email protected]@tapable/lib/Hook. Js call Promise callAsync is a function that triggers Hook execution.

  1. In the aboveconstructorWrite thethis.call=this._call this.promise = this._promise; this.callAsync = this._callAsync;Not really executed, just instantiated.
  2. And at the bottomObject.definePropertiesSet the_call _promise _callAsyncMethods, their values are functions. But why not put it directly in the class?
  3. _callIs to callthis._createCallThe generated function, andthis._createCallIs calledcompileMethod, andsyncHookClass coversHookThe base classcompileMethod [The base class method itself needs to be overwritten]
  4. SyncHookOf the classcompileMethod executesfactory.setupfactory.create.
  5. whilefactoryIt’s instantiatedSyncHookCodeFactory【 inherited fromHookCodeFactoryAn example of this,
  6. factory.setupThe effect of the implementation is totapsThe fn object in the array returns an array assignment toSyncHookThe instance_xProperties ingetTapFnThe string used in the function to compose the generated execution function.
  7. factory.createUsing thenew FunctionFunction defined in the way of the spelling string function function, according to type assync asyncpromiseTo decide how to call the registered consumer using different strings that specify the different ways to trigger the hook.

The string concatenated functions generated by factory.create are important in determining how to execute the registered consumers. They consume the sorted registered consumers described in the above registered consumer profile.

AsyncParallelHook, for example

Why is there no call method in his method of triggering the execution consumer? He is in/node_modules / _tapable @ 1.1.3 @ tapable/lib/AsyncParallelHook in js through below Object. DefineProperties (AsyncParallelHook. Prototype to _call setting value is undefined, This overwrites the _call method defined in the /node_modules/[email protected]@tapable/lib/Hook. Js base class, undefined can’t be executed.

Write a small demo with the official demo to run breakpoint tests

Slightly longer, patient to scrutinize

The code is as follows:

// https://github.com/webpack/tapable var {SyncHook, AsyncParallelHook, AsyncSeriesHook} = require('tapable') /** * * indicates how many arguments the instantiated hook writes to the tap callback function, * that is, how many arguments are passed to the registered consumer and when called. */ // const hook = new SyncHook(["arg1", "arg2", "arg3"]); // console.log('hook', hook) class Car { constructor() { /** * You won't get returned value from SyncHook or AsyncParallelHook, * to do that, Use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively * * SyncHook AsyncParallelHook does not return value, SyncWaterfallHook and AsyncSeriesWaterfallHook have **/ / put the instantiated hook together. new SyncHook(["newSpeed"]), brake: new SyncHook(), calculateRoutes: new AsyncParallelHook(["source", "target", "callback"]) }; } /* The following methods are used to trigger the hook, */ brake(newSpeed) {// Following call returns undefined even when you returned values // Brake is a SyncHook, This call returns undefined this.returns.brake.call (); } setSpeed(newSpeed) {// following call returns undefined even when you returned values // accelerate is SyncHook, This call returns undefined this. Hooks. Accelerate. Call (newSpeed); } useNavigationSystemPromise(source, Target) {/ / promise way can trigger all tapPromise tapAsync tap registered customer return this. Hooks. CalculateRoutes. Promise (source, Target, () = > {the console. The log (' useNavigationSystemPromise perform 1 ')}), then ((res) = > {/ / although all methods can trigger, But if all consumers sign up in tapPromise, Will perform here / / res is undefined for AsyncParallelHook console. The log (' useNavigationSystemPromise perform 2 res, res)}); } // Use the callAsync method to trigger all consumers registered with tapPromise tapAsync tap useNavigationSystemAsync(source, Target) {enclosing hooks. CalculateRoutes. CallAsync (source, target, err = > {the console. The log (' useNavigationSystemAsync execution ')}); } } const myCar = new Car(); /** * add a consumer to a hook using the tap method ** tap requires that a name be passed to mark the plugin or trigger cause ** / Mycar.links.brake. Tap ("WarningLampPlugin", () => {console.log(' brake lamp on')}); myCar.hooks.brake.tap("speeddown", () => { console.log('speeddown on') }); / / callback function can accept parameters can be more than one myCar. 】 hooks. Accelerate. Tap (' loggerPlugin ', NewSpeed => {console.log(' re accelerated to ${newSpeed} ')}) /** * The consumer of calculateRoutes 1 ** calculateRoutes is AsyncParallelHook * You can use the tap tapAsync tapPromise method * registered here with the tapPromise method, which should return promise * if you don't return a Promise, you'll get an Error: Tap function (tapPromise) did not return promise (returned undefined) * */ myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, callback) => { // return a promise console.log('in GoogleMapsPlugin') callback() return Promise.resolve('google') }); /** * calculateRoutes consumer 2 ** calculateRoutes is an AsyncParallelHook * You can use the Tap tapAsync tapPromise method * tapAsync here Methods to register, the back of the function is the manner in which the callback function used in the * * / myCar hooks. CalculateRoutes. TapAsync ({name: 'BingMapsPlugin, before: 'GoogleMapsPlugin', // the order in which consumers are called. Before can be a string or an array, Node_modules /[email protected]@tapable/lib/Hook. Js :117 callback) => { console.log('in BingMapsPlugin') callback() }); /** * calculateRoutes consumer 3 ** calculateRoutes is an AsyncParallelHook * Can be registered with the tap tapAsync tapPromise method * the tap method used here, * * You can still use sync plugins * asyncParallelhooks can still be used to add plugins * * Final calculateRoutes * * AsyncParallelhooks When triggered the concurrent execution of registration will be the three methods * * / myCar hooks. CalculateRoutes. Tap (" CachedRoutesPlugin ", (source, target, Callback) => {console.log('in CachedRoutesPlugin') callback()}) To trigger this hook myCar. Brake () / / myCar setSpeed (100) / / myCar useNavigationSystemPromise (" source ", 'target') myCar.useNavigationSystemAsync('source', 'target')Copy the code