source: frontend/node_modules/tapable/lib/HookCodeFactory.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 12.8 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7class HookCodeFactory {
8 constructor(config) {
9 this.config = config;
10 this.options = undefined;
11 this._args = undefined;
12 }
13
14 create(options) {
15 this.init(options);
16 let fn;
17 switch (options.type) {
18 case "sync":
19 fn = new Function(
20 this.args(),
21 `"use strict";\n${this.header()}${this.contentWithInterceptors({
22 onError: (err) => `throw ${err};\n`,
23 onResult: (result) => `return ${result};\n`,
24 resultReturns: true,
25 onDone: () => "",
26 rethrowIfPossible: true
27 })}`
28 );
29 break;
30 case "async":
31 fn = new Function(
32 this.args({
33 after: "_callback"
34 }),
35 `"use strict";\n${this.header()}${this.contentWithInterceptors({
36 onError: (err) => `_callback(${err});\n`,
37 onResult: (result) => `_callback(null, ${result});\n`,
38 onDone: () => "_callback();\n"
39 })}`
40 );
41 break;
42 case "promise": {
43 let errorHelperUsed = false;
44 const content = this.contentWithInterceptors({
45 onError: (err) => {
46 errorHelperUsed = true;
47 return `_error(${err});\n`;
48 },
49 onResult: (result) => `_resolve(${result});\n`,
50 onDone: () => "_resolve();\n"
51 });
52 let code = "";
53 code += '"use strict";\n';
54 code += this.header();
55 code += "return new Promise((function(_resolve, _reject) {\n";
56 if (errorHelperUsed) {
57 code += "var _sync = true;\n";
58 code += "function _error(_err) {\n";
59 code += "if(_sync)\n";
60 code +=
61 "_resolve(Promise.resolve().then((function() { throw _err; })));\n";
62 code += "else\n";
63 code += "_reject(_err);\n";
64 code += "};\n";
65 }
66 code += content;
67 if (errorHelperUsed) {
68 code += "_sync = false;\n";
69 }
70 code += "}));\n";
71 fn = new Function(this.args(), code);
72 break;
73 }
74 }
75 this.deinit();
76 return fn;
77 }
78
79 setup(instance, options) {
80 const { taps } = options;
81 const { length } = taps;
82 const fns = Array.from({ length });
83 for (let i = 0; i < length; i++) {
84 fns[i] = taps[i].fn;
85 }
86 instance._x = fns;
87 }
88
89 /**
90 * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options
91 */
92 init(options) {
93 this.options = options;
94 // `_args` is only read (length / join / [0]) - never mutated - so we
95 // can share the caller's array directly instead of paying for a copy
96 // on every compile.
97 this._args = options.args;
98 this._joinedArgs = undefined;
99 }
100
101 deinit() {
102 this.options = undefined;
103 this._args = undefined;
104 this._joinedArgs = undefined;
105 }
106
107 contentWithInterceptors(options) {
108 if (this.options.interceptors.length > 0) {
109 const { onError, onResult, onDone } = options;
110 let code = "";
111 for (let i = 0; i < this.options.interceptors.length; i++) {
112 const interceptor = this.options.interceptors[i];
113 if (interceptor.call) {
114 code += `${this.getInterceptor(i)}.call(${this.args({
115 before: interceptor.context ? "_context" : undefined
116 })});\n`;
117 }
118 }
119 code += this.content(
120 Object.assign(options, {
121 onError:
122 onError &&
123 ((err) => {
124 let code = "";
125 for (let i = 0; i < this.options.interceptors.length; i++) {
126 const interceptor = this.options.interceptors[i];
127 if (interceptor.error) {
128 code += `${this.getInterceptor(i)}.error(${err});\n`;
129 }
130 }
131 code += onError(err);
132 return code;
133 }),
134 onResult:
135 onResult &&
136 ((result) => {
137 let code = "";
138 for (let i = 0; i < this.options.interceptors.length; i++) {
139 const interceptor = this.options.interceptors[i];
140 if (interceptor.result) {
141 code += `${this.getInterceptor(i)}.result(${result});\n`;
142 }
143 }
144 code += onResult(result);
145 return code;
146 }),
147 onDone:
148 onDone &&
149 (() => {
150 let code = "";
151 for (let i = 0; i < this.options.interceptors.length; i++) {
152 const interceptor = this.options.interceptors[i];
153 if (interceptor.done) {
154 code += `${this.getInterceptor(i)}.done();\n`;
155 }
156 }
157 code += onDone();
158 return code;
159 })
160 })
161 );
162 return code;
163 }
164 return this.content(options);
165 }
166
167 header() {
168 let code = "";
169 code += this.needContext() ? "var _context = {};\n" : "var _context;\n";
170 code += "var _x = this._x;\n";
171 if (this.options.interceptors.length > 0) {
172 code += "var _taps = this.taps;\n";
173 code += "var _interceptors = this.interceptors;\n";
174 }
175 return code;
176 }
177
178 needContext() {
179 const { taps } = this.options;
180 for (let i = 0; i < taps.length; i++) {
181 if (taps[i].context) return true;
182 }
183 return false;
184 }
185
186 callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
187 let code = "";
188 let hasTapCached = false;
189 for (let i = 0; i < this.options.interceptors.length; i++) {
190 const interceptor = this.options.interceptors[i];
191 if (interceptor.tap) {
192 if (!hasTapCached) {
193 code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
194 hasTapCached = true;
195 }
196 code += `${this.getInterceptor(i)}.tap(${
197 interceptor.context ? "_context, " : ""
198 }_tap${tapIndex});\n`;
199 }
200 }
201 code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
202 const tap = this.options.taps[tapIndex];
203 switch (tap.type) {
204 case "sync":
205 if (!rethrowIfPossible) {
206 code += `var _hasError${tapIndex} = false;\n`;
207 code += "try {\n";
208 }
209 if (onResult) {
210 code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
211 before: tap.context ? "_context" : undefined
212 })});\n`;
213 } else {
214 code += `_fn${tapIndex}(${this.args({
215 before: tap.context ? "_context" : undefined
216 })});\n`;
217 }
218 if (!rethrowIfPossible) {
219 code += "} catch(_err) {\n";
220 code += `_hasError${tapIndex} = true;\n`;
221 code += onError("_err");
222 code += "}\n";
223 code += `if(!_hasError${tapIndex}) {\n`;
224 }
225 if (onResult) {
226 code += onResult(`_result${tapIndex}`);
227 }
228 if (onDone) {
229 code += onDone();
230 }
231 if (!rethrowIfPossible) {
232 code += "}\n";
233 }
234 break;
235 case "async": {
236 let cbCode = "";
237 cbCode += onResult
238 ? `(function(_err${tapIndex}, _result${tapIndex}) {\n`
239 : `(function(_err${tapIndex}) {\n`;
240 cbCode += `if(_err${tapIndex}) {\n`;
241 cbCode += onError(`_err${tapIndex}`);
242 cbCode += "} else {\n";
243 if (onResult) {
244 cbCode += onResult(`_result${tapIndex}`);
245 }
246 if (onDone) {
247 cbCode += onDone();
248 }
249 cbCode += "}\n";
250 cbCode += "})";
251 code += `_fn${tapIndex}(${this.args({
252 before: tap.context ? "_context" : undefined,
253 after: cbCode
254 })});\n`;
255 break;
256 }
257 case "promise":
258 code += `var _hasResult${tapIndex} = false;\n`;
259 code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
260 before: tap.context ? "_context" : undefined
261 })});\n`;
262 code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
263 code += ` throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
264 code += `_promise${tapIndex}.then((function(_result${tapIndex}) {\n`;
265 code += `_hasResult${tapIndex} = true;\n`;
266 if (onResult) {
267 code += onResult(`_result${tapIndex}`);
268 }
269 if (onDone) {
270 code += onDone();
271 }
272 code += `}), function(_err${tapIndex}) {\n`;
273 code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
274 code += onError(
275 `!_err${tapIndex} ? new Error('Tap function (tapPromise) rejects "' + _err${tapIndex} + '" value') : _err${tapIndex}`
276 );
277 code += "});\n";
278 break;
279 }
280 return code;
281 }
282
283 callTapsSeries({
284 onError,
285 onResult,
286 resultReturns,
287 onDone,
288 doneReturns,
289 rethrowIfPossible
290 }) {
291 const { taps } = this.options;
292 const tapsLength = taps.length;
293 if (tapsLength === 0) return onDone();
294 // Inlined findIndex to avoid the callback allocation.
295 let firstAsync = -1;
296 for (let i = 0; i < tapsLength; i++) {
297 if (taps[i].type !== "sync") {
298 firstAsync = i;
299 break;
300 }
301 }
302 const somethingReturns = resultReturns || doneReturns;
303 // doneBreak doesn't depend on the loop variable - hoist to allocate once.
304 const doneBreak = (skipDone) => {
305 if (skipDone) return "";
306 return onDone();
307 };
308 let code = "";
309 let current = onDone;
310 let unrollCounter = 0;
311 for (let j = tapsLength - 1; j >= 0; j--) {
312 const i = j;
313 const unroll =
314 current !== onDone && (taps[i].type !== "sync" || unrollCounter++ > 20);
315 if (unroll) {
316 unrollCounter = 0;
317 code += `function _next${i}() {\n`;
318 code += current();
319 code += "}\n";
320 current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
321 }
322 const done = current;
323 const content = this.callTap(i, {
324 onError: (error) => onError(i, error, done, doneBreak),
325 onResult:
326 onResult && ((result) => onResult(i, result, done, doneBreak)),
327 onDone: !onResult && done,
328 rethrowIfPossible:
329 rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
330 });
331 current = () => content;
332 }
333 code += current();
334 return code;
335 }
336
337 callTapsLooping({ onError, onDone, rethrowIfPossible }) {
338 if (this.options.taps.length === 0) return onDone();
339 const syncOnly = this.options.taps.every((t) => t.type === "sync");
340 let code = "";
341 if (!syncOnly) {
342 code += "var _looper = (function() {\n";
343 code += "var _loopAsync = false;\n";
344 }
345 code += "var _loop;\n";
346 code += "do {\n";
347 code += "_loop = false;\n";
348 for (let i = 0; i < this.options.interceptors.length; i++) {
349 const interceptor = this.options.interceptors[i];
350 if (interceptor.loop) {
351 code += `${this.getInterceptor(i)}.loop(${this.args({
352 before: interceptor.context ? "_context" : undefined
353 })});\n`;
354 }
355 }
356 code += this.callTapsSeries({
357 onError,
358 onResult: (i, result, next, doneBreak) => {
359 let code = "";
360 code += `if(${result} !== undefined) {\n`;
361 code += "_loop = true;\n";
362 if (!syncOnly) code += "if(_loopAsync) _looper();\n";
363 code += doneBreak(true);
364 code += "} else {\n";
365 code += next();
366 code += "}\n";
367 return code;
368 },
369 onDone:
370 onDone &&
371 (() => {
372 let code = "";
373 code += "if(!_loop) {\n";
374 code += onDone();
375 code += "}\n";
376 return code;
377 }),
378 rethrowIfPossible: rethrowIfPossible && syncOnly
379 });
380 code += "} while(_loop);\n";
381 if (!syncOnly) {
382 code += "_loopAsync = true;\n";
383 code += "});\n";
384 code += "_looper();\n";
385 }
386 return code;
387 }
388
389 callTapsParallel({
390 onError,
391 onResult,
392 onDone,
393 rethrowIfPossible,
394 onTap = (i, run) => run()
395 }) {
396 const { taps } = this.options;
397 const tapsLength = taps.length;
398 if (tapsLength <= 1) {
399 return this.callTapsSeries({
400 onError,
401 onResult,
402 onDone,
403 rethrowIfPossible
404 });
405 }
406 // done and doneBreak don't depend on the loop variable - hoist them
407 // so they're allocated once per compile instead of once per tap.
408 const done = () => {
409 if (onDone) return "if(--_counter === 0) _done();\n";
410 return "--_counter;";
411 };
412 const doneBreak = (skipDone) => {
413 if (skipDone || !onDone) return "_counter = 0;\n";
414 return "_counter = 0;\n_done();\n";
415 };
416 let code = "";
417 code += "do {\n";
418 code += `var _counter = ${tapsLength};\n`;
419 if (onDone) {
420 code += "var _done = (function() {\n";
421 code += onDone();
422 code += "});\n";
423 }
424 for (let i = 0; i < tapsLength; i++) {
425 code += "if(_counter <= 0) break;\n";
426 code += onTap(
427 i,
428 () =>
429 this.callTap(i, {
430 onError: (error) => {
431 let code = "";
432 code += "if(_counter > 0) {\n";
433 code += onError(i, error, done, doneBreak);
434 code += "}\n";
435 return code;
436 },
437 onResult:
438 onResult &&
439 ((result) => {
440 let code = "";
441 code += "if(_counter > 0) {\n";
442 code += onResult(i, result, done, doneBreak);
443 code += "}\n";
444 return code;
445 }),
446 onDone: !onResult && (() => done()),
447 rethrowIfPossible
448 }),
449 done,
450 doneBreak
451 );
452 }
453 code += "} while(false);\n";
454 return code;
455 }
456
457 args({ before, after } = {}) {
458 // Hot during code generation. Join `_args` once and cache the result,
459 // then build the customized variants via string concat instead of
460 // allocating temporary `[before, ...allArgs]` / `[...allArgs, after]`
461 // arrays and re-joining.
462 let joined = this._joinedArgs;
463 if (joined === undefined) {
464 joined = this._args.length === 0 ? "" : this._args.join(", ");
465 this._joinedArgs = joined;
466 }
467 if (!before && !after) return joined;
468 if (joined.length === 0) {
469 if (before && after) return `${before}, ${after}`;
470 return before || after;
471 }
472 if (before && after) return `${before}, ${joined}, ${after}`;
473 if (before) return `${before}, ${joined}`;
474 return `${joined}, ${after}`;
475 }
476
477 getTapFn(idx) {
478 return `_x[${idx}]`;
479 }
480
481 getTap(idx) {
482 return `_taps[${idx}]`;
483 }
484
485 getInterceptor(idx) {
486 return `_interceptors[${idx}]`;
487 }
488}
489
490module.exports = HookCodeFactory;
Note: See TracBrowser for help on using the repository browser.