diff --git a/package-lock.json b/package-lock.json index c19ebedbf..4c0030e41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16746,11 +16746,6 @@ "figgy-pudding": "^3.5.1" } }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", diff --git a/package.json b/package.json index b41859207..c0d9c9398 100644 --- a/package.json +++ b/package.json @@ -50,8 +50,7 @@ }, "dependencies": { "css-select": "^2.0.2", - "css-tree": "^1.0.0-alpha.36", - "stable": "^0.1.8" + "css-tree": "^1.0.0-alpha.36" }, "devDependencies": { "@react-native-community/bob": "^0.7.0", diff --git a/src/css.tsx b/src/css.tsx index 29f901d5a..5385d88a4 100644 --- a/src/css.tsx +++ b/src/css.tsx @@ -24,7 +24,6 @@ import csstree, { SelectorList, } from 'css-tree'; import cssSelect, { Adapter, Options, Predicate, Query } from 'css-select'; -import stable from 'stable'; /* * Style element inlining experiment based on SVGO @@ -396,7 +395,7 @@ function specificity(selector: Selector): Specificity { function compareSpecificity( aSpecificity: Specificity, bSpecificity: Specificity, -) { +): number { for (let i = 0; i < 4; i += 1) { if (aSpecificity[i] < bSpecificity[i]) { return -1; @@ -417,12 +416,84 @@ function compareSpecificity( function bySelectorSpecificity( selectorA: FlatSelector, selectorB: FlatSelector, -) { +): number { const aSpecificity = specificity(selectorA.item.data as Selector), bSpecificity = specificity(selectorB.item.data as Selector); return compareSpecificity(aSpecificity, bSpecificity); } +// Run a single pass with the given chunk size. +function pass( + arr: T[], + comp: (a: T, b: T) => number, + chk: number, + result: T[], +) { + // Step size / double chunk size. + const dbl = chk * 2; + // Bounds of the left and right chunks. + let l, r, e; + // Iterators over the left and right chunk. + let li, ri; + + // Iterate over pairs of chunks. + let i = 0; + const len = arr.length; + for (l = 0; l < len; l += dbl) { + r = l + chk; + e = r + chk; + if (r > len) { + r = len; + } + if (e > len) { + e = len; + } + + // Iterate both chunks in parallel. + li = l; + ri = r; + while (true) { + // Compare the chunks. + if (li < r && ri < e) { + // This works for a regular `sort()` compatible comparator, + // but also for a simple comparator like: `a > b` + if (comp(arr[li], arr[ri]) <= 0) { + result[i++] = arr[li++]; + } else { + result[i++] = arr[ri++]; + } + } + // Nothing to compare, just flush what's left. + else if (li < r) { + result[i++] = arr[li++]; + } else if (ri < e) { + result[i++] = arr[ri++]; + } + // Both iterators are at the chunk ends. + else { + break; + } + } + } +} + +// Execute the sort using the input array and a second buffer as work space. +// Returns one of those two, containing the final result. +function exec(arr: T[], len: number, comp: (a: T, b: T) => number) { + // Rather than dividing input, simply iterate chunks of 1, 2, 4, 8, etc. + // Chunks are the size of the left or right hand in merge sort. + // Stop when the left-hand covers all of the array. + let buffer = new Array(len); + for (let chk = 1; chk < len; chk *= 2) { + pass(arr, comp, chk, buffer); + const tmp = arr; + arr = buffer; + buffer = tmp; + } + + return arr; +} + /** * Sort selectors stably by their specificity. * @@ -430,7 +501,12 @@ function bySelectorSpecificity( * @return {Array} Stable sorted selectors */ function sortSelectors(selectors: FlatSelectorList) { - return stable(selectors, bySelectorSpecificity); + // Short-circuit when there's nothing to sort. + const len = selectors.length; + if (len <= 1) { + return selectors; + } + return exec(selectors.slice(), len, bySelectorSpecificity); } const declarationParseProps = {