runtime.js (14605B)
1 /* 2 MIT License http://www.opensource.org/licenses/mit-license.php 3 Author Tobias Koppers @sokra 4 */ 5 6 "use strict"; 7 8 const SortableSet = require("./SortableSet"); 9 10 /** @typedef {import("../Compilation")} Compilation */ 11 /** @typedef {import("../Entrypoint").EntryOptions} EntryOptions */ 12 13 /** @typedef {string | SortableSet<string> | undefined} RuntimeSpec */ 14 /** @typedef {RuntimeSpec | boolean} RuntimeCondition */ 15 16 /** 17 * @param {Compilation} compilation the compilation 18 * @param {string} name name of the entry 19 * @param {EntryOptions=} options optionally already received entry options 20 * @returns {RuntimeSpec} runtime 21 */ 22 exports.getEntryRuntime = (compilation, name, options) => { 23 let dependOn; 24 let runtime; 25 if (options) { 26 ({ dependOn, runtime } = options); 27 } else { 28 const entry = compilation.entries.get(name); 29 if (!entry) return name; 30 ({ dependOn, runtime } = entry.options); 31 } 32 if (dependOn) { 33 /** @type {RuntimeSpec} */ 34 let result = undefined; 35 const queue = new Set(dependOn); 36 for (const name of queue) { 37 const dep = compilation.entries.get(name); 38 if (!dep) continue; 39 const { dependOn, runtime } = dep.options; 40 if (dependOn) { 41 for (const name of dependOn) { 42 queue.add(name); 43 } 44 } else { 45 result = mergeRuntimeOwned(result, runtime || name); 46 } 47 } 48 return result || name; 49 } else { 50 return runtime || name; 51 } 52 }; 53 54 /** 55 * @param {RuntimeSpec} runtime runtime 56 * @param {function(string): void} fn functor 57 * @param {boolean} deterministicOrder enforce a deterministic order 58 * @returns {void} 59 */ 60 exports.forEachRuntime = (runtime, fn, deterministicOrder = false) => { 61 if (runtime === undefined) { 62 fn(undefined); 63 } else if (typeof runtime === "string") { 64 fn(runtime); 65 } else { 66 if (deterministicOrder) runtime.sort(); 67 for (const r of runtime) { 68 fn(r); 69 } 70 } 71 }; 72 73 const getRuntimesKey = set => { 74 set.sort(); 75 return Array.from(set).join("\n"); 76 }; 77 78 /** 79 * @param {RuntimeSpec} runtime runtime(s) 80 * @returns {string} key of runtimes 81 */ 82 const getRuntimeKey = runtime => { 83 if (runtime === undefined) return "*"; 84 if (typeof runtime === "string") return runtime; 85 return runtime.getFromUnorderedCache(getRuntimesKey); 86 }; 87 exports.getRuntimeKey = getRuntimeKey; 88 89 /** 90 * @param {string} key key of runtimes 91 * @returns {RuntimeSpec} runtime(s) 92 */ 93 const keyToRuntime = key => { 94 if (key === "*") return undefined; 95 const items = key.split("\n"); 96 if (items.length === 1) return items[0]; 97 return new SortableSet(items); 98 }; 99 exports.keyToRuntime = keyToRuntime; 100 101 const getRuntimesString = set => { 102 set.sort(); 103 return Array.from(set).join("+"); 104 }; 105 106 /** 107 * @param {RuntimeSpec} runtime runtime(s) 108 * @returns {string} readable version 109 */ 110 const runtimeToString = runtime => { 111 if (runtime === undefined) return "*"; 112 if (typeof runtime === "string") return runtime; 113 return runtime.getFromUnorderedCache(getRuntimesString); 114 }; 115 exports.runtimeToString = runtimeToString; 116 117 /** 118 * @param {RuntimeCondition} runtimeCondition runtime condition 119 * @returns {string} readable version 120 */ 121 exports.runtimeConditionToString = runtimeCondition => { 122 if (runtimeCondition === true) return "true"; 123 if (runtimeCondition === false) return "false"; 124 return runtimeToString(runtimeCondition); 125 }; 126 127 /** 128 * @param {RuntimeSpec} a first 129 * @param {RuntimeSpec} b second 130 * @returns {boolean} true, when they are equal 131 */ 132 const runtimeEqual = (a, b) => { 133 if (a === b) { 134 return true; 135 } else if ( 136 a === undefined || 137 b === undefined || 138 typeof a === "string" || 139 typeof b === "string" 140 ) { 141 return false; 142 } else if (a.size !== b.size) { 143 return false; 144 } else { 145 a.sort(); 146 b.sort(); 147 const aIt = a[Symbol.iterator](); 148 const bIt = b[Symbol.iterator](); 149 for (;;) { 150 const aV = aIt.next(); 151 if (aV.done) return true; 152 const bV = bIt.next(); 153 if (aV.value !== bV.value) return false; 154 } 155 } 156 }; 157 exports.runtimeEqual = runtimeEqual; 158 159 /** 160 * @param {RuntimeSpec} a first 161 * @param {RuntimeSpec} b second 162 * @returns {-1|0|1} compare 163 */ 164 exports.compareRuntime = (a, b) => { 165 if (a === b) { 166 return 0; 167 } else if (a === undefined) { 168 return -1; 169 } else if (b === undefined) { 170 return 1; 171 } else { 172 const aKey = getRuntimeKey(a); 173 const bKey = getRuntimeKey(b); 174 if (aKey < bKey) return -1; 175 if (aKey > bKey) return 1; 176 return 0; 177 } 178 }; 179 180 /** 181 * @param {RuntimeSpec} a first 182 * @param {RuntimeSpec} b second 183 * @returns {RuntimeSpec} merged 184 */ 185 const mergeRuntime = (a, b) => { 186 if (a === undefined) { 187 return b; 188 } else if (b === undefined) { 189 return a; 190 } else if (a === b) { 191 return a; 192 } else if (typeof a === "string") { 193 if (typeof b === "string") { 194 const set = new SortableSet(); 195 set.add(a); 196 set.add(b); 197 return set; 198 } else if (b.has(a)) { 199 return b; 200 } else { 201 const set = new SortableSet(b); 202 set.add(a); 203 return set; 204 } 205 } else { 206 if (typeof b === "string") { 207 if (a.has(b)) return a; 208 const set = new SortableSet(a); 209 set.add(b); 210 return set; 211 } else { 212 const set = new SortableSet(a); 213 for (const item of b) set.add(item); 214 if (set.size === a.size) return a; 215 return set; 216 } 217 } 218 }; 219 exports.mergeRuntime = mergeRuntime; 220 221 /** 222 * @param {RuntimeCondition} a first 223 * @param {RuntimeCondition} b second 224 * @param {RuntimeSpec} runtime full runtime 225 * @returns {RuntimeCondition} result 226 */ 227 exports.mergeRuntimeCondition = (a, b, runtime) => { 228 if (a === false) return b; 229 if (b === false) return a; 230 if (a === true || b === true) return true; 231 const merged = mergeRuntime(a, b); 232 if (merged === undefined) return undefined; 233 if (typeof merged === "string") { 234 if (typeof runtime === "string" && merged === runtime) return true; 235 return merged; 236 } 237 if (typeof runtime === "string" || runtime === undefined) return merged; 238 if (merged.size === runtime.size) return true; 239 return merged; 240 }; 241 242 /** 243 * @param {RuntimeSpec | true} a first 244 * @param {RuntimeSpec | true} b second 245 * @param {RuntimeSpec} runtime full runtime 246 * @returns {RuntimeSpec | true} result 247 */ 248 exports.mergeRuntimeConditionNonFalse = (a, b, runtime) => { 249 if (a === true || b === true) return true; 250 const merged = mergeRuntime(a, b); 251 if (merged === undefined) return undefined; 252 if (typeof merged === "string") { 253 if (typeof runtime === "string" && merged === runtime) return true; 254 return merged; 255 } 256 if (typeof runtime === "string" || runtime === undefined) return merged; 257 if (merged.size === runtime.size) return true; 258 return merged; 259 }; 260 261 /** 262 * @param {RuntimeSpec} a first (may be modified) 263 * @param {RuntimeSpec} b second 264 * @returns {RuntimeSpec} merged 265 */ 266 const mergeRuntimeOwned = (a, b) => { 267 if (b === undefined) { 268 return a; 269 } else if (a === b) { 270 return a; 271 } else if (a === undefined) { 272 if (typeof b === "string") { 273 return b; 274 } else { 275 return new SortableSet(b); 276 } 277 } else if (typeof a === "string") { 278 if (typeof b === "string") { 279 const set = new SortableSet(); 280 set.add(a); 281 set.add(b); 282 return set; 283 } else { 284 const set = new SortableSet(b); 285 set.add(a); 286 return set; 287 } 288 } else { 289 if (typeof b === "string") { 290 a.add(b); 291 return a; 292 } else { 293 for (const item of b) a.add(item); 294 return a; 295 } 296 } 297 }; 298 exports.mergeRuntimeOwned = mergeRuntimeOwned; 299 300 /** 301 * @param {RuntimeSpec} a first 302 * @param {RuntimeSpec} b second 303 * @returns {RuntimeSpec} merged 304 */ 305 exports.intersectRuntime = (a, b) => { 306 if (a === undefined) { 307 return b; 308 } else if (b === undefined) { 309 return a; 310 } else if (a === b) { 311 return a; 312 } else if (typeof a === "string") { 313 if (typeof b === "string") { 314 return undefined; 315 } else if (b.has(a)) { 316 return a; 317 } else { 318 return undefined; 319 } 320 } else { 321 if (typeof b === "string") { 322 if (a.has(b)) return b; 323 return undefined; 324 } else { 325 const set = new SortableSet(); 326 for (const item of b) { 327 if (a.has(item)) set.add(item); 328 } 329 if (set.size === 0) return undefined; 330 if (set.size === 1) for (const item of set) return item; 331 return set; 332 } 333 } 334 }; 335 336 /** 337 * @param {RuntimeSpec} a first 338 * @param {RuntimeSpec} b second 339 * @returns {RuntimeSpec} result 340 */ 341 const subtractRuntime = (a, b) => { 342 if (a === undefined) { 343 return undefined; 344 } else if (b === undefined) { 345 return a; 346 } else if (a === b) { 347 return undefined; 348 } else if (typeof a === "string") { 349 if (typeof b === "string") { 350 return a; 351 } else if (b.has(a)) { 352 return undefined; 353 } else { 354 return a; 355 } 356 } else { 357 if (typeof b === "string") { 358 if (!a.has(b)) return a; 359 if (a.size === 2) { 360 for (const item of a) { 361 if (item !== b) return item; 362 } 363 } 364 const set = new SortableSet(a); 365 set.delete(b); 366 } else { 367 const set = new SortableSet(); 368 for (const item of a) { 369 if (!b.has(item)) set.add(item); 370 } 371 if (set.size === 0) return undefined; 372 if (set.size === 1) for (const item of set) return item; 373 return set; 374 } 375 } 376 }; 377 exports.subtractRuntime = subtractRuntime; 378 379 /** 380 * @param {RuntimeCondition} a first 381 * @param {RuntimeCondition} b second 382 * @param {RuntimeSpec} runtime runtime 383 * @returns {RuntimeCondition} result 384 */ 385 exports.subtractRuntimeCondition = (a, b, runtime) => { 386 if (b === true) return false; 387 if (b === false) return a; 388 if (a === false) return false; 389 const result = subtractRuntime(a === true ? runtime : a, b); 390 return result === undefined ? false : result; 391 }; 392 393 /** 394 * @param {RuntimeSpec} runtime runtime 395 * @param {function(RuntimeSpec): boolean} filter filter function 396 * @returns {boolean | RuntimeSpec} true/false if filter is constant for all runtimes, otherwise runtimes that are active 397 */ 398 exports.filterRuntime = (runtime, filter) => { 399 if (runtime === undefined) return filter(undefined); 400 if (typeof runtime === "string") return filter(runtime); 401 let some = false; 402 let every = true; 403 let result = undefined; 404 for (const r of runtime) { 405 const v = filter(r); 406 if (v) { 407 some = true; 408 result = mergeRuntimeOwned(result, r); 409 } else { 410 every = false; 411 } 412 } 413 if (!some) return false; 414 if (every) return true; 415 return result; 416 }; 417 418 /** 419 * @template T 420 */ 421 class RuntimeSpecMap { 422 /** 423 * @param {RuntimeSpecMap<T>=} clone copy form this 424 */ 425 constructor(clone) { 426 this._mode = clone ? clone._mode : 0; // 0 = empty, 1 = single entry, 2 = map 427 /** @type {RuntimeSpec} */ 428 this._singleRuntime = clone ? clone._singleRuntime : undefined; 429 /** @type {T} */ 430 this._singleValue = clone ? clone._singleValue : undefined; 431 /** @type {Map<string, T> | undefined} */ 432 this._map = clone && clone._map ? new Map(clone._map) : undefined; 433 } 434 435 /** 436 * @param {RuntimeSpec} runtime the runtimes 437 * @returns {T} value 438 */ 439 get(runtime) { 440 switch (this._mode) { 441 case 0: 442 return undefined; 443 case 1: 444 return runtimeEqual(this._singleRuntime, runtime) 445 ? this._singleValue 446 : undefined; 447 default: 448 return this._map.get(getRuntimeKey(runtime)); 449 } 450 } 451 452 /** 453 * @param {RuntimeSpec} runtime the runtimes 454 * @returns {boolean} true, when the runtime is stored 455 */ 456 has(runtime) { 457 switch (this._mode) { 458 case 0: 459 return false; 460 case 1: 461 return runtimeEqual(this._singleRuntime, runtime); 462 default: 463 return this._map.has(getRuntimeKey(runtime)); 464 } 465 } 466 467 set(runtime, value) { 468 switch (this._mode) { 469 case 0: 470 this._mode = 1; 471 this._singleRuntime = runtime; 472 this._singleValue = value; 473 break; 474 case 1: 475 if (runtimeEqual(this._singleRuntime, runtime)) { 476 this._singleValue = value; 477 break; 478 } 479 this._mode = 2; 480 this._map = new Map(); 481 this._map.set(getRuntimeKey(this._singleRuntime), this._singleValue); 482 this._singleRuntime = undefined; 483 this._singleValue = undefined; 484 /* falls through */ 485 default: 486 this._map.set(getRuntimeKey(runtime), value); 487 } 488 } 489 490 provide(runtime, computer) { 491 switch (this._mode) { 492 case 0: 493 this._mode = 1; 494 this._singleRuntime = runtime; 495 return (this._singleValue = computer()); 496 case 1: { 497 if (runtimeEqual(this._singleRuntime, runtime)) { 498 return this._singleValue; 499 } 500 this._mode = 2; 501 this._map = new Map(); 502 this._map.set(getRuntimeKey(this._singleRuntime), this._singleValue); 503 this._singleRuntime = undefined; 504 this._singleValue = undefined; 505 const newValue = computer(); 506 this._map.set(getRuntimeKey(runtime), newValue); 507 return newValue; 508 } 509 default: { 510 const key = getRuntimeKey(runtime); 511 const value = this._map.get(key); 512 if (value !== undefined) return value; 513 const newValue = computer(); 514 this._map.set(key, newValue); 515 return newValue; 516 } 517 } 518 } 519 520 delete(runtime) { 521 switch (this._mode) { 522 case 0: 523 return; 524 case 1: 525 if (runtimeEqual(this._singleRuntime, runtime)) { 526 this._mode = 0; 527 this._singleRuntime = undefined; 528 this._singleValue = undefined; 529 } 530 return; 531 default: 532 this._map.delete(getRuntimeKey(runtime)); 533 } 534 } 535 536 update(runtime, fn) { 537 switch (this._mode) { 538 case 0: 539 throw new Error("runtime passed to update must exist"); 540 case 1: { 541 if (runtimeEqual(this._singleRuntime, runtime)) { 542 this._singleValue = fn(this._singleValue); 543 break; 544 } 545 const newValue = fn(undefined); 546 if (newValue !== undefined) { 547 this._mode = 2; 548 this._map = new Map(); 549 this._map.set(getRuntimeKey(this._singleRuntime), this._singleValue); 550 this._singleRuntime = undefined; 551 this._singleValue = undefined; 552 this._map.set(getRuntimeKey(runtime), newValue); 553 } 554 break; 555 } 556 default: { 557 const key = getRuntimeKey(runtime); 558 const oldValue = this._map.get(key); 559 const newValue = fn(oldValue); 560 if (newValue !== oldValue) this._map.set(key, newValue); 561 } 562 } 563 } 564 565 keys() { 566 switch (this._mode) { 567 case 0: 568 return []; 569 case 1: 570 return [this._singleRuntime]; 571 default: 572 return Array.from(this._map.keys(), keyToRuntime); 573 } 574 } 575 576 values() { 577 switch (this._mode) { 578 case 0: 579 return [][Symbol.iterator](); 580 case 1: 581 return [this._singleValue][Symbol.iterator](); 582 default: 583 return this._map.values(); 584 } 585 } 586 587 get size() { 588 if (this._mode <= 1) return this._mode; 589 return this._map.size; 590 } 591 } 592 593 exports.RuntimeSpecMap = RuntimeSpecMap; 594 595 class RuntimeSpecSet { 596 constructor(iterable) { 597 /** @type {Map<string, RuntimeSpec>} */ 598 this._map = new Map(); 599 if (iterable) { 600 for (const item of iterable) { 601 this.add(item); 602 } 603 } 604 } 605 606 add(runtime) { 607 this._map.set(getRuntimeKey(runtime), runtime); 608 } 609 610 has(runtime) { 611 return this._map.has(getRuntimeKey(runtime)); 612 } 613 614 [Symbol.iterator]() { 615 return this._map.values(); 616 } 617 618 get size() { 619 return this._map.size; 620 } 621 } 622 623 exports.RuntimeSpecSet = RuntimeSpecSet;