comparators.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const { getFullModuleName } = require("../ids/IdHelpers");
  7. const { compareRuntime } = require("./runtime");
  8. /** @typedef {import("../Chunk")} Chunk */
  9. /** @typedef {import("../Chunk").ChunkName} ChunkName */
  10. /** @typedef {import("../Chunk").ChunkId} ChunkId */
  11. /** @typedef {import("../ChunkGraph")} ChunkGraph */
  12. /** @typedef {import("../ChunkGraph").ModuleId} ModuleId */
  13. /** @typedef {import("../ChunkGroup")} ChunkGroup */
  14. /** @typedef {import("../Compiler")} Compiler */
  15. /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
  16. /** @typedef {import("../Dependency")} Dependency */
  17. /** @typedef {import("../dependencies/HarmonyImportSideEffectDependency")} HarmonyImportSideEffectDependency */
  18. /** @typedef {import("../dependencies/HarmonyImportSpecifierDependency")} HarmonyImportSpecifierDependency */
  19. /** @typedef {import("../Module")} Module */
  20. /** @typedef {import("../ModuleGraph")} ModuleGraph */
  21. /** @typedef {import("../dependencies/ModuleDependency")} ModuleDependency */
  22. /**
  23. * @typedef {object} DependencySourceOrder
  24. * @property {number} main the main source order
  25. * @property {number} sub the sub source order
  26. */
  27. /**
  28. * @template T
  29. * @typedef {(a: T, b: T) => -1 | 0 | 1} Comparator
  30. */
  31. /**
  32. * @template {object} TArg
  33. * @template T
  34. * @typedef {(tArg: TArg, a: T, b: T) => -1 | 0 | 1} RawParameterizedComparator
  35. */
  36. /**
  37. * @template {object} TArg
  38. * @template T
  39. * @typedef {(tArg: TArg) => Comparator<T>} ParameterizedComparator
  40. */
  41. /**
  42. * @template {object} TArg
  43. * @template {object} T
  44. * @param {RawParameterizedComparator<TArg, T>} fn comparator with argument
  45. * @returns {ParameterizedComparator<TArg, T>} comparator
  46. */
  47. const createCachedParameterizedComparator = (fn) => {
  48. /** @type {WeakMap<EXPECTED_OBJECT, Comparator<T>>} */
  49. const map = new WeakMap();
  50. return (arg) => {
  51. const cachedResult = map.get(/** @type {EXPECTED_OBJECT} */ (arg));
  52. if (cachedResult !== undefined) return cachedResult;
  53. /**
  54. * @param {T} a first item
  55. * @param {T} b second item
  56. * @returns {-1 | 0 | 1} compare result
  57. */
  58. const result = fn.bind(null, arg);
  59. map.set(/** @type {EXPECTED_OBJECT} */ (arg), result);
  60. return result;
  61. };
  62. };
  63. /**
  64. * @param {string | number} a first id
  65. * @param {string | number} b second id
  66. * @returns {-1 | 0 | 1} compare result
  67. */
  68. const compareIds = (a, b) => {
  69. if (typeof a !== typeof b) {
  70. return typeof a < typeof b ? -1 : 1;
  71. }
  72. if (a < b) return -1;
  73. if (a > b) return 1;
  74. return 0;
  75. };
  76. /**
  77. * @template T
  78. * @param {Comparator<T>} elementComparator comparator for elements
  79. * @returns {Comparator<Iterable<T>>} comparator for iterables of elements
  80. */
  81. const compareIterables = (elementComparator) => {
  82. const cacheEntry = compareIteratorsCache.get(elementComparator);
  83. if (cacheEntry !== undefined) return cacheEntry;
  84. /**
  85. * @param {Iterable<T>} a first value
  86. * @param {Iterable<T>} b second value
  87. * @returns {-1 | 0 | 1} compare result
  88. */
  89. const result = (a, b) => {
  90. const aI = a[Symbol.iterator]();
  91. const bI = b[Symbol.iterator]();
  92. while (true) {
  93. const aItem = aI.next();
  94. const bItem = bI.next();
  95. if (aItem.done) {
  96. return bItem.done ? 0 : -1;
  97. } else if (bItem.done) {
  98. return 1;
  99. }
  100. const res = elementComparator(aItem.value, bItem.value);
  101. if (res !== 0) return res;
  102. }
  103. };
  104. compareIteratorsCache.set(elementComparator, result);
  105. return result;
  106. };
  107. /**
  108. * Compare two locations
  109. * @param {DependencyLocation} a A location node
  110. * @param {DependencyLocation} b A location node
  111. * @returns {-1 | 0 | 1} sorting comparator value
  112. */
  113. const compareLocations = (a, b) => {
  114. const isObjectA = typeof a === "object" && a !== null;
  115. const isObjectB = typeof b === "object" && b !== null;
  116. if (!isObjectA || !isObjectB) {
  117. if (isObjectA) return 1;
  118. if (isObjectB) return -1;
  119. return 0;
  120. }
  121. if ("start" in a) {
  122. if ("start" in b) {
  123. const ap = a.start;
  124. const bp = b.start;
  125. if (ap.line < bp.line) return -1;
  126. if (ap.line > bp.line) return 1;
  127. if (
  128. /** @type {number} */ (ap.column) < /** @type {number} */ (bp.column)
  129. ) {
  130. return -1;
  131. }
  132. if (
  133. /** @type {number} */ (ap.column) > /** @type {number} */ (bp.column)
  134. ) {
  135. return 1;
  136. }
  137. } else {
  138. return -1;
  139. }
  140. } else if ("start" in b) {
  141. return 1;
  142. }
  143. if ("name" in a) {
  144. if ("name" in b) {
  145. if (a.name < b.name) return -1;
  146. if (a.name > b.name) return 1;
  147. } else {
  148. return -1;
  149. }
  150. } else if ("name" in b) {
  151. return 1;
  152. }
  153. if ("index" in a) {
  154. if ("index" in b) {
  155. if (/** @type {number} */ (a.index) < /** @type {number} */ (b.index)) {
  156. return -1;
  157. }
  158. if (/** @type {number} */ (a.index) > /** @type {number} */ (b.index)) {
  159. return 1;
  160. }
  161. } else {
  162. return -1;
  163. }
  164. } else if ("index" in b) {
  165. return 1;
  166. }
  167. return 0;
  168. };
  169. /**
  170. * @param {ChunkGraph} chunkGraph the chunk graph
  171. * @param {Module} a module
  172. * @param {Module} b module
  173. * @returns {-1 | 0 | 1} compare result
  174. */
  175. const compareModulesById = (chunkGraph, a, b) =>
  176. compareIds(
  177. /** @type {ModuleId} */ (chunkGraph.getModuleId(a)),
  178. /** @type {ModuleId} */ (chunkGraph.getModuleId(b))
  179. );
  180. /**
  181. * @param {number} a number
  182. * @param {number} b number
  183. * @returns {-1 | 0 | 1} compare result
  184. */
  185. const compareNumbers = (a, b) => {
  186. if (typeof a !== typeof b) {
  187. return typeof a < typeof b ? -1 : 1;
  188. }
  189. if (a < b) return -1;
  190. if (a > b) return 1;
  191. return 0;
  192. };
  193. /**
  194. * @param {string} a string
  195. * @param {string} b string
  196. * @returns {-1 | 0 | 1} compare result
  197. */
  198. const compareStringsNumeric = (a, b) => {
  199. const aLength = a.length;
  200. const bLength = b.length;
  201. let aChar = 0;
  202. let bChar = 0;
  203. let aIsDigit = false;
  204. let bIsDigit = false;
  205. let i = 0;
  206. let j = 0;
  207. while (i < aLength && j < bLength) {
  208. aChar = a.charCodeAt(i);
  209. bChar = b.charCodeAt(j);
  210. aIsDigit = aChar >= 48 && aChar <= 57;
  211. bIsDigit = bChar >= 48 && bChar <= 57;
  212. if (!aIsDigit && !bIsDigit) {
  213. if (aChar < bChar) return -1;
  214. if (aChar > bChar) return 1;
  215. i++;
  216. j++;
  217. } else if (aIsDigit && !bIsDigit) {
  218. // This segment of a is shorter than in b
  219. return 1;
  220. } else if (!aIsDigit && bIsDigit) {
  221. // This segment of b is shorter than in a
  222. return -1;
  223. } else {
  224. let aNumber = aChar - 48;
  225. let bNumber = bChar - 48;
  226. while (++i < aLength) {
  227. aChar = a.charCodeAt(i);
  228. if (aChar < 48 || aChar > 57) break;
  229. aNumber = aNumber * 10 + aChar - 48;
  230. }
  231. while (++j < bLength) {
  232. bChar = b.charCodeAt(j);
  233. if (bChar < 48 || bChar > 57) break;
  234. bNumber = bNumber * 10 + bChar - 48;
  235. }
  236. if (aNumber < bNumber) return -1;
  237. if (aNumber > bNumber) return 1;
  238. }
  239. }
  240. if (j < bLength) {
  241. // a is shorter than b
  242. bChar = b.charCodeAt(j);
  243. bIsDigit = bChar >= 48 && bChar <= 57;
  244. return bIsDigit ? -1 : 1;
  245. }
  246. if (i < aLength) {
  247. // b is shorter than a
  248. aChar = a.charCodeAt(i);
  249. aIsDigit = aChar >= 48 && aChar <= 57;
  250. return aIsDigit ? 1 : -1;
  251. }
  252. return 0;
  253. };
  254. /**
  255. * @param {ModuleGraph} moduleGraph the module graph
  256. * @param {Module} a module
  257. * @param {Module} b module
  258. * @returns {-1 | 0 | 1} compare result
  259. */
  260. const compareModulesByPostOrderIndexOrIdentifier = (moduleGraph, a, b) => {
  261. const cmp = compareNumbers(
  262. /** @type {number} */ (moduleGraph.getPostOrderIndex(a)),
  263. /** @type {number} */ (moduleGraph.getPostOrderIndex(b))
  264. );
  265. if (cmp !== 0) return cmp;
  266. return compareIds(a.identifier(), b.identifier());
  267. };
  268. /**
  269. * @param {ModuleGraph} moduleGraph the module graph
  270. * @param {Module} a module
  271. * @param {Module} b module
  272. * @returns {-1 | 0 | 1} compare result
  273. */
  274. const compareModulesByPreOrderIndexOrIdentifier = (moduleGraph, a, b) => {
  275. const cmp = compareNumbers(
  276. /** @type {number} */ (moduleGraph.getPreOrderIndex(a)),
  277. /** @type {number} */ (moduleGraph.getPreOrderIndex(b))
  278. );
  279. if (cmp !== 0) return cmp;
  280. return compareIds(a.identifier(), b.identifier());
  281. };
  282. /**
  283. * @param {ChunkGraph} chunkGraph the chunk graph
  284. * @param {Module} a module
  285. * @param {Module} b module
  286. * @returns {-1 | 0 | 1} compare result
  287. */
  288. const compareModulesByIdOrIdentifier = (chunkGraph, a, b) => {
  289. const cmp = compareIds(
  290. /** @type {ModuleId} */ (chunkGraph.getModuleId(a)),
  291. /** @type {ModuleId} */ (chunkGraph.getModuleId(b))
  292. );
  293. if (cmp !== 0) return cmp;
  294. return compareIds(a.identifier(), b.identifier());
  295. };
  296. /**
  297. * Compare modules by their full name. This differs from comparing by identifier in that the values have been normalized to be relative to the compiler context.
  298. * @param {{ context: string, root: object }} compiler the compiler, used for context and cache
  299. * @param {Module} a module
  300. * @param {Module} b module
  301. * @returns {-1 | 0 | 1} compare result
  302. */
  303. const compareModulesByFullName = (compiler, a, b) => {
  304. const aName = getFullModuleName(a, compiler.context, compiler.root);
  305. const bName = getFullModuleName(b, compiler.context, compiler.root);
  306. return compareIds(aName, bName);
  307. };
  308. /**
  309. * @param {ChunkGraph} chunkGraph the chunk graph
  310. * @param {Chunk} a chunk
  311. * @param {Chunk} b chunk
  312. * @returns {-1 | 0 | 1} compare result
  313. */
  314. const compareChunks = (chunkGraph, a, b) => chunkGraph.compareChunks(a, b);
  315. /**
  316. * @param {string} a first string
  317. * @param {string} b second string
  318. * @returns {-1 | 0 | 1} compare result
  319. */
  320. const compareStrings = (a, b) => {
  321. if (a < b) return -1;
  322. if (a > b) return 1;
  323. return 0;
  324. };
  325. /**
  326. * @param {ChunkGroup} a first chunk group
  327. * @param {ChunkGroup} b second chunk group
  328. * @returns {-1 | 0 | 1} compare result
  329. */
  330. const compareChunkGroupsByIndex = (a, b) =>
  331. /** @type {number} */ (a.index) < /** @type {number} */ (b.index) ? -1 : 1;
  332. /**
  333. * @template {EXPECTED_OBJECT} K1
  334. * @template {EXPECTED_OBJECT} K2
  335. * @template T
  336. */
  337. class TwoKeyWeakMap {
  338. constructor() {
  339. /**
  340. * @private
  341. * @type {WeakMap<K1, WeakMap<K2, T | undefined>>}
  342. */
  343. this._map = new WeakMap();
  344. }
  345. /**
  346. * @param {K1} key1 first key
  347. * @param {K2} key2 second key
  348. * @returns {T | undefined} value
  349. */
  350. get(key1, key2) {
  351. const childMap = this._map.get(key1);
  352. if (childMap === undefined) {
  353. return;
  354. }
  355. return childMap.get(key2);
  356. }
  357. /**
  358. * @param {K1} key1 first key
  359. * @param {K2} key2 second key
  360. * @param {T | undefined} value new value
  361. * @returns {void}
  362. */
  363. set(key1, key2, value) {
  364. let childMap = this._map.get(key1);
  365. if (childMap === undefined) {
  366. childMap = new WeakMap();
  367. this._map.set(key1, childMap);
  368. }
  369. childMap.set(key2, value);
  370. }
  371. }
  372. /** @type {TwoKeyWeakMap<Comparator<EXPECTED_ANY>, Comparator<EXPECTED_ANY>, Comparator<EXPECTED_ANY>>}} */
  373. const concatComparatorsCache = new TwoKeyWeakMap();
  374. /**
  375. * @template T
  376. * @param {Comparator<T>} c1 comparator
  377. * @param {Comparator<T>} c2 comparator
  378. * @param {Comparator<T>[]} cRest comparators
  379. * @returns {Comparator<T>} comparator
  380. */
  381. const concatComparators = (c1, c2, ...cRest) => {
  382. if (cRest.length > 0) {
  383. const [c3, ...cRest2] = cRest;
  384. return concatComparators(c1, concatComparators(c2, c3, ...cRest2));
  385. }
  386. const cacheEntry = /** @type {Comparator<T>} */ (
  387. concatComparatorsCache.get(c1, c2)
  388. );
  389. if (cacheEntry !== undefined) return cacheEntry;
  390. /**
  391. * @param {T} a first value
  392. * @param {T} b second value
  393. * @returns {-1 | 0 | 1} compare result
  394. */
  395. const result = (a, b) => {
  396. const res = c1(a, b);
  397. if (res !== 0) return res;
  398. return c2(a, b);
  399. };
  400. concatComparatorsCache.set(c1, c2, result);
  401. return result;
  402. };
  403. /**
  404. * @template A, B
  405. * @typedef {(input: A) => B | undefined | null} Selector
  406. */
  407. /** @type {TwoKeyWeakMap<Selector<EXPECTED_ANY, EXPECTED_ANY>, Comparator<EXPECTED_ANY>, Comparator<EXPECTED_ANY>>}} */
  408. const compareSelectCache = new TwoKeyWeakMap();
  409. /**
  410. * @template T
  411. * @template R
  412. * @param {Selector<T, R>} getter getter for value
  413. * @param {Comparator<R>} comparator comparator
  414. * @returns {Comparator<T>} comparator
  415. */
  416. const compareSelect = (getter, comparator) => {
  417. const cacheEntry = compareSelectCache.get(getter, comparator);
  418. if (cacheEntry !== undefined) return cacheEntry;
  419. /**
  420. * @param {T} a first value
  421. * @param {T} b second value
  422. * @returns {-1 | 0 | 1} compare result
  423. */
  424. const result = (a, b) => {
  425. const aValue = getter(a);
  426. const bValue = getter(b);
  427. if (aValue !== undefined && aValue !== null) {
  428. if (bValue !== undefined && bValue !== null) {
  429. return comparator(aValue, bValue);
  430. }
  431. return -1;
  432. }
  433. if (bValue !== undefined && bValue !== null) {
  434. return 1;
  435. }
  436. return 0;
  437. };
  438. compareSelectCache.set(getter, comparator, result);
  439. return result;
  440. };
  441. /** @type {WeakMap<Comparator<EXPECTED_ANY>, Comparator<Iterable<EXPECTED_ANY>>>} */
  442. const compareIteratorsCache = new WeakMap();
  443. // TODO this is no longer needed when minimum node.js version is >= 12
  444. // since these versions ship with a stable sort function
  445. /**
  446. * @template T
  447. * @param {Iterable<T>} iterable original ordered list
  448. * @returns {Comparator<T>} comparator
  449. */
  450. const keepOriginalOrder = (iterable) => {
  451. /** @type {Map<T, number>} */
  452. const map = new Map();
  453. let i = 0;
  454. for (const item of iterable) {
  455. map.set(item, i++);
  456. }
  457. return (a, b) =>
  458. compareNumbers(
  459. /** @type {number} */ (map.get(a)),
  460. /** @type {number} */ (map.get(b))
  461. );
  462. };
  463. /**
  464. * @param {ChunkGraph} chunkGraph the chunk graph
  465. * @returns {Comparator<Chunk>} comparator
  466. */
  467. const compareChunksNatural = (chunkGraph) => {
  468. const cmpFn = module.exports.compareModulesById(chunkGraph);
  469. const cmpIterableFn = compareIterables(cmpFn);
  470. return concatComparators(
  471. compareSelect((chunk) => /** @type {ChunkName} */ (chunk.name), compareIds),
  472. compareSelect((chunk) => chunk.runtime, compareRuntime),
  473. compareSelect(
  474. /**
  475. * @param {Chunk} chunk a chunk
  476. * @returns {Iterable<Module>} modules
  477. */
  478. (chunk) => chunkGraph.getOrderedChunkModulesIterable(chunk, cmpFn),
  479. cmpIterableFn
  480. )
  481. );
  482. };
  483. /**
  484. * For HarmonyImportSideEffectDependency and HarmonyImportSpecifierDependency, we should prioritize import order to match the behavior of running modules directly in a JS engine without a bundler.
  485. * For other types like ConstDependency, we can instead prioritize usage order.
  486. * https://github.com/webpack/webpack/pull/19686
  487. * @param {Dependency[]} dependencies dependencies
  488. * @param {WeakMap<Dependency, DependencySourceOrder>} dependencySourceOrderMap dependency source order map
  489. * @param {((dep: Dependency, index: number) => void)=} onDependencyReSort optional callback to set index for each dependency
  490. * @returns {void}
  491. */
  492. const sortWithSourceOrder = (
  493. dependencies,
  494. dependencySourceOrderMap,
  495. onDependencyReSort
  496. ) => {
  497. /** @type {{ dep: Dependency, main: number, sub: number }[]} */
  498. const withSourceOrder = [];
  499. /** @type {number[]} */
  500. const positions = [];
  501. for (let i = 0; i < dependencies.length; i++) {
  502. const dep = dependencies[i];
  503. const cached = dependencySourceOrderMap.get(dep);
  504. if (cached) {
  505. positions.push(i);
  506. withSourceOrder.push({
  507. dep,
  508. main: cached.main,
  509. sub: cached.sub
  510. });
  511. } else {
  512. const sourceOrder = /** @type {number | undefined} */ (
  513. /** @type {ModuleDependency} */ (dep).sourceOrder
  514. );
  515. if (typeof sourceOrder === "number") {
  516. positions.push(i);
  517. withSourceOrder.push({
  518. dep,
  519. main: sourceOrder,
  520. sub: 0
  521. });
  522. }
  523. }
  524. }
  525. if (withSourceOrder.length <= 1) {
  526. return;
  527. }
  528. withSourceOrder.sort((a, b) => {
  529. if (a.main !== b.main) {
  530. return compareNumbers(a.main, b.main);
  531. }
  532. return compareNumbers(a.sub, b.sub);
  533. });
  534. // Second pass: place sorted deps back to original positions
  535. for (let i = 0; i < positions.length; i++) {
  536. const depIndex = positions[i];
  537. dependencies[depIndex] = withSourceOrder[i].dep;
  538. if (onDependencyReSort) {
  539. onDependencyReSort(dependencies[depIndex], depIndex);
  540. }
  541. }
  542. };
  543. module.exports.compareChunkGroupsByIndex = compareChunkGroupsByIndex;
  544. /** @type {ParameterizedComparator<ChunkGraph, Chunk>} */
  545. module.exports.compareChunks =
  546. createCachedParameterizedComparator(compareChunks);
  547. /**
  548. * @param {Chunk} a chunk
  549. * @param {Chunk} b chunk
  550. * @returns {-1 | 0 | 1} compare result
  551. */
  552. module.exports.compareChunksById = (a, b) =>
  553. compareIds(/** @type {ChunkId} */ (a.id), /** @type {ChunkId} */ (b.id));
  554. module.exports.compareChunksNatural = compareChunksNatural;
  555. module.exports.compareIds = compareIds;
  556. module.exports.compareIterables = compareIterables;
  557. module.exports.compareLocations = compareLocations;
  558. /** @type {ParameterizedComparator<Compiler, Module>} */
  559. module.exports.compareModulesByFullName = createCachedParameterizedComparator(
  560. compareModulesByFullName
  561. );
  562. /** @type {ParameterizedComparator<ChunkGraph, Module>} */
  563. module.exports.compareModulesById =
  564. createCachedParameterizedComparator(compareModulesById);
  565. /** @type {ParameterizedComparator<ChunkGraph, Module>} */
  566. module.exports.compareModulesByIdOrIdentifier =
  567. createCachedParameterizedComparator(compareModulesByIdOrIdentifier);
  568. /**
  569. * @param {Module} a module
  570. * @param {Module} b module
  571. * @returns {-1 | 0 | 1} compare result
  572. */
  573. module.exports.compareModulesByIdentifier = (a, b) =>
  574. compareIds(a.identifier(), b.identifier());
  575. /** @type {ParameterizedComparator<ModuleGraph, Module>} */
  576. module.exports.compareModulesByPostOrderIndexOrIdentifier =
  577. createCachedParameterizedComparator(
  578. compareModulesByPostOrderIndexOrIdentifier
  579. );
  580. /** @type {ParameterizedComparator<ModuleGraph, Module>} */
  581. module.exports.compareModulesByPreOrderIndexOrIdentifier =
  582. createCachedParameterizedComparator(
  583. compareModulesByPreOrderIndexOrIdentifier
  584. );
  585. module.exports.compareNumbers = compareNumbers;
  586. module.exports.compareSelect = compareSelect;
  587. module.exports.compareStrings = compareStrings;
  588. module.exports.compareStringsNumeric = compareStringsNumeric;
  589. module.exports.concatComparators = concatComparators;
  590. module.exports.keepOriginalOrder = keepOriginalOrder;
  591. module.exports.sortWithSourceOrder = sortWithSourceOrder;