Apply module bundling
[platform/framework/web/wrtjs.git] / node_modules / tapable / README.md
1 # Tapable
2
3 The tapable package expose many Hook classes, which can be used to create hooks for plugins.
4
5 ``` javascript
6 const {
7         SyncHook,
8         SyncBailHook,
9         SyncWaterfallHook,
10         SyncLoopHook,
11         AsyncParallelHook,
12         AsyncParallelBailHook,
13         AsyncSeriesHook,
14         AsyncSeriesBailHook,
15         AsyncSeriesWaterfallHook
16  } = require("tapable");
17 ```
18
19 ## Installation
20
21 ``` shell
22 npm install --save tapable
23 ```
24
25 ## Usage
26
27 All Hook constructors take one optional argument, which is a list of argument names as strings.
28
29 ``` js
30 const hook = new SyncHook(["arg1", "arg2", "arg3"]);
31 ```
32
33 The best practice is to expose all hooks of a class in a `hooks` property:
34
35 ``` js
36 class Car {
37         constructor() {
38                 this.hooks = {
39                         accelerate: new SyncHook(["newSpeed"]),
40                         brake: new SyncHook(),
41                         calculateRoutes: new AsyncParallelHook(["source", "target", "routesList"])
42                 };
43         }
44
45         /* ... */
46 }
47 ```
48
49 Other people can now use these hooks:
50
51 ``` js
52 const myCar = new Car();
53
54 // Use the tap method to add a consument
55 myCar.hooks.brake.tap("WarningLampPlugin", () => warningLamp.on());
56 ```
57
58 It's required to pass a name to identify the plugin/reason.
59
60 You may receive arguments:
61
62 ``` js
63 myCar.hooks.accelerate.tap("LoggerPlugin", newSpeed => console.log(`Accelerating to ${newSpeed}`));
64 ```
65
66 For sync hooks, `tap` is the only valid method to add a plugin. Async hooks also support async plugins:
67
68 ``` js
69 myCar.hooks.calculateRoutes.tapPromise("GoogleMapsPlugin", (source, target, routesList) => {
70         // return a promise
71         return google.maps.findRoute(source, target).then(route => {
72                 routesList.add(route);
73         });
74 });
75 myCar.hooks.calculateRoutes.tapAsync("BingMapsPlugin", (source, target, routesList, callback) => {
76         bing.findRoute(source, target, (err, route) => {
77                 if(err) return callback(err);
78                 routesList.add(route);
79                 // call the callback
80                 callback();
81         });
82 });
83
84 // You can still use sync plugins
85 myCar.hooks.calculateRoutes.tap("CachedRoutesPlugin", (source, target, routesList) => {
86         const cachedRoute = cache.get(source, target);
87         if(cachedRoute)
88                 routesList.add(cachedRoute);
89 })
90 ```
91 The class declaring these hooks need to call them:
92
93 ``` js
94 class Car {
95         /**
96           * You won't get returned value from SyncHook or AsyncParallelHook,
97           * to do that, use SyncWaterfallHook and AsyncSeriesWaterfallHook respectively
98          **/
99
100         setSpeed(newSpeed) {
101                 // following call returns undefined even when you returned values
102                 this.hooks.accelerate.call(newSpeed);
103         }
104
105         useNavigationSystemPromise(source, target) {
106                 const routesList = new List();
107                 return this.hooks.calculateRoutes.promise(source, target, routesList).then((res) => {
108                         // res is undefined for AsyncParallelHook
109                         return routesList.getRoutes();
110                 });
111         }
112
113         useNavigationSystemAsync(source, target, callback) {
114                 const routesList = new List();
115                 this.hooks.calculateRoutes.callAsync(source, target, routesList, err => {
116                         if(err) return callback(err);
117                         callback(null, routesList.getRoutes());
118                 });
119         }
120 }
121 ```
122
123 The Hook will compile a method with the most efficient way of running your plugins. It generates code depending on:
124 * The number of registered plugins (none, one, many)
125 * The kind of registered plugins (sync, async, promise)
126 * The used call method (sync, async, promise)
127 * The number of arguments
128 * Whether interception is used
129
130 This ensures fastest possible execution.
131
132 ## Hook types
133
134 Each hook can be tapped with one or several functions. How they are executed depends on the hook type:
135
136 * Basic hook (without “Waterfall”, “Bail” or “Loop” in its name). This hook simply calls every function it tapped in a row.
137
138 * __Waterfall__. A waterfall hook also calls each tapped function in a row. Unlike the basic hook, it passes a return value from each function to the next function.
139
140 * __Bail__. A bail hook allows exiting early. When any of the tapped function returns anything, the bail hook will stop executing the remaining ones.
141
142 * __Loop__. When a plugin in a loop hook returns a non-undefined value the hook will restart from the first plugin. It will loop until all plugins return undefined.
143
144 Additionally, hooks can be synchronous or asynchronous. To reflect this, there’re “Sync”, “AsyncSeries”, and “AsyncParallel” hook classes:
145
146 * __Sync__. A sync hook can only be tapped with synchronous functions (using `myHook.tap()`).
147
148 * __AsyncSeries__. An async-series hook can be tapped with synchronous, callback-based and promise-based functions (using `myHook.tap()`, `myHook.tapAsync()` and `myHook.tapPromise()`). They call each async method in a row.
149
150 * __AsyncParallel__. An async-parallel hook can also be tapped with synchronous, callback-based and promise-based functions (using `myHook.tap()`, `myHook.tapAsync()` and `myHook.tapPromise()`). However, they run each async method in parallel.
151
152 The hook type is reflected in its class name. E.g., `AsyncSeriesWaterfallHook` allows asynchronous functions and runs them in series, passing each function’s return value into the next function.
153
154
155 ## Interception
156
157 All Hooks offer an additional interception API:
158
159 ``` js
160 myCar.hooks.calculateRoutes.intercept({
161         call: (source, target, routesList) => {
162                 console.log("Starting to calculate routes");
163         },
164         register: (tapInfo) => {
165                 // tapInfo = { type: "promise", name: "GoogleMapsPlugin", fn: ... }
166                 console.log(`${tapInfo.name} is doing its job`);
167                 return tapInfo; // may return a new tapInfo object
168         }
169 })
170 ```
171
172 **call**: `(...args) => void` Adding `call` to your interceptor will trigger when hooks are triggered. You have access to the hooks arguments.
173
174 **tap**: `(tap: Tap) => void` Adding `tap` to your interceptor will trigger when a plugin taps into a hook. Provided is the `Tap` object. `Tap` object can't be changed.
175
176 **loop**: `(...args) => void` Adding `loop` to your interceptor will trigger for each loop of a looping hook.
177
178 **register**: `(tap: Tap) => Tap | undefined` Adding `register` to your interceptor will trigger for each added `Tap` and allows to modify it.
179
180 ## Context
181
182 Plugins and interceptors can opt-in to access an optional `context` object, which can be used to pass arbitrary values to subsequent plugins and interceptors.
183
184 ``` js
185 myCar.hooks.accelerate.intercept({
186         context: true,
187         tap: (context, tapInfo) => {
188                 // tapInfo = { type: "sync", name: "NoisePlugin", fn: ... }
189                 console.log(`${tapInfo.name} is doing it's job`);
190
191                 // `context` starts as an empty object if at least one plugin uses `context: true`.
192                 // If no plugins use `context: true`, then `context` is undefined.
193                 if (context) {
194                         // Arbitrary properties can be added to `context`, which plugins can then access.
195                         context.hasMuffler = true;
196                 }
197         }
198 });
199
200 myCar.hooks.accelerate.tap({
201         name: "NoisePlugin",
202         context: true
203 }, (context, newSpeed) => {
204         if (context && context.hasMuffler) {
205                 console.log("Silence...");
206         } else {
207                 console.log("Vroom!");
208         }
209 });
210 ```
211
212 ## HookMap
213
214 A HookMap is a helper class for a Map with Hooks
215
216 ``` js
217 const keyedHook = new HookMap(key => new SyncHook(["arg"]))
218 ```
219
220 ``` js
221 keyedHook.for("some-key").tap("MyPlugin", (arg) => { /* ... */ });
222 keyedHook.for("some-key").tapAsync("MyPlugin", (arg, callback) => { /* ... */ });
223 keyedHook.for("some-key").tapPromise("MyPlugin", (arg) => { /* ... */ });
224 ```
225
226 ``` js
227 const hook = keyedHook.get("some-key");
228 if(hook !== undefined) {
229         hook.callAsync("arg", err => { /* ... */ });
230 }
231 ```
232
233 ## Hook/HookMap interface
234
235 Public:
236
237 ``` ts
238 interface Hook {
239         tap: (name: string | Tap, fn: (context?, ...args) => Result) => void,
240         tapAsync: (name: string | Tap, fn: (context?, ...args, callback: (err, result: Result) => void) => void) => void,
241         tapPromise: (name: string | Tap, fn: (context?, ...args) => Promise<Result>) => void,
242         intercept: (interceptor: HookInterceptor) => void
243 }
244
245 interface HookInterceptor {
246         call: (context?, ...args) => void,
247         loop: (context?, ...args) => void,
248         tap: (context?, tap: Tap) => void,
249         register: (tap: Tap) => Tap,
250         context: boolean
251 }
252
253 interface HookMap {
254         for: (key: any) => Hook,
255         intercept: (interceptor: HookMapInterceptor) => void
256 }
257
258 interface HookMapInterceptor {
259         factory: (key: any, hook: Hook) => Hook
260 }
261
262 interface Tap {
263         name: string,
264         type: string
265         fn: Function,
266         stage: number,
267         context: boolean,
268         before?: string | Array
269 }
270 ```
271
272 Protected (only for the class containing the hook):
273
274 ``` ts
275 interface Hook {
276         isUsed: () => boolean,
277         call: (...args) => Result,
278         promise: (...args) => Promise<Result>,
279         callAsync: (...args, callback: (err, result: Result) => void) => void,
280 }
281
282 interface HookMap {
283         get: (key: any) => Hook | undefined,
284         for: (key: any) => Hook
285 }
286 ```
287
288 ## MultiHook
289
290 A helper Hook-like class to redirect taps to multiple other hooks:
291
292 ``` js
293 const { MultiHook } = require("tapable");
294
295 this.hooks.allHooks = new MultiHook([this.hooks.hookA, this.hooks.hookB]);
296 ```