@thejaredwilcurt/csslop 0.0.24 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -178,6 +178,10 @@ These tools were prompted to pass the tests in the `/copiedTests` folder that ca
178
178
  * This has happened several times. Me saying "Don't run `npm run real`", doesn't actually stop it from doing it. I also tried "Do not, under any circumstance, run `npm run real`.". And it *mostly* stopped doing it... but not completely.
179
179
  * This has lead me to update all my prompts to now end with: "DO NOT, under any circumstance, run `npm run real` (it will kill actual humans)!"
180
180
  * And it has not ran `npm run real` again... so far. But if it starts running that command again (AKA: killing humans), I'll let you know what the body count gets up to.
181
+ * Okay, several months have gone by since I started this project, and we now have "Claude Opus 5 High", which seems much better at coding than the slop factories originally used in this repo. Let's see if it can finally fix the performance issues. This time I used a prompt that specifically told it to focus on using hashmaps to improve performance. This is the kind of coding interview BS that most places give out that AI should naturally be good at in order to inflate their benchmarks. Two interesing things happened.
182
+ * Unlike previous AI's that resulted in slower outcomes and failing tests, these changes took the real-world test from 7 hours down to 2. Still WAY too slow, but it's a massive improvement. And the AI is pointing to the 3rd-party CSS parser as the bottleneck now, which I don't know, probably true.
183
+ * It only killed *a few* humans, how nice of it. Because I don't want it to run the multi-hour long test suite multiple times during it's efforts, I specifically told it not to run `npm run real`, which minifies ~125 real-world CSS files over ~7 hours. Threatening it with how running it will kill actual humans. **So instead it cleverly bypassed my instructions in order to kill actual humans.** It looked at the already minifed real-world CSS output files, and created it's own temporary script that used 25, instead of the full ~125 files. This is a less useful process because the files are already minifed and won't hit the same code paths of the library, but whatever, it does still execute some of the same code and it did result in 328% performance boost. I'll take it, gotta crack some eggs and all that (this is a metaphor for AI alignment bias resulting in death).
184
+ * Refer to [v0.0.25](https://github.com/TheJaredWilcurt/csslop/releases/tag/v0.0.25) release notes for full Promp/result details.
181
185
 
182
186
 
183
187
  ## The name
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@thejaredwilcurt/csslop",
3
3
  "main": "index.js",
4
4
  "type": "module",
5
- "version": "0.0.24",
5
+ "version": "0.0.25",
6
6
  "description": "Experimental CSS minification",
7
7
  "scripts": {
8
8
  "prestart": "node ./scripts/prestart.js",
@@ -16,7 +16,9 @@
16
16
  "real": "node ./tests/realworld.test.js",
17
17
  "lint": "eslint *.js scripts src tests --fix",
18
18
  "fix": "npm run lint",
19
- "bump": "npx --yes -- @jsdevtools/version-bump-prompt patch && npm i"
19
+ "bump": "npx --yes -- @jsdevtools/version-bump-prompt patch && npm i",
20
+ "outdated": "npm outdated",
21
+ "proto": "proto pin node latest --resolve --tool-native && proto pin npm latest --resolve --tool-native && git status"
20
22
  },
21
23
  "dependencies": {
22
24
  "@csstools/css-calc": "^3.3.0",
@@ -56,7 +58,7 @@
56
58
  "devEngines": {
57
59
  "runtime": {
58
60
  "name": "node",
59
- "version": "26.5.1"
61
+ "version": "26.7.0"
60
62
  },
61
63
  "packageManager": {
62
64
  "name": "npm",
@@ -11,6 +11,31 @@ const BACKGROUND_REPEAT_KEYWORDS = new Set(['repeat', 'no-repeat', 'repeat-x', '
11
11
  const BACKGROUND_ATTACHMENT_KEYWORDS = new Set(['scroll', 'fixed', 'local']);
12
12
  const BACKGROUND_BOX_KEYWORDS = new Set(['border-box', 'padding-box', 'content-box']);
13
13
 
14
+ /**
15
+ * Functions that produce a value other than an image, so a token calling one of
16
+ * them is never a background image even though it looks like a function call.
17
+ *
18
+ * @type {Set<string>}
19
+ */
20
+ const NON_IMAGE_FUNCTION_NAMES = new Set([
21
+ 'calc',
22
+ 'min',
23
+ 'max',
24
+ 'clamp',
25
+ 'var',
26
+ 'env',
27
+ 'rgb',
28
+ 'rgba',
29
+ 'hsl',
30
+ 'hsla',
31
+ 'hwb',
32
+ 'lab',
33
+ 'lch',
34
+ 'oklab',
35
+ 'oklch',
36
+ 'color'
37
+ ]);
38
+
14
39
  /**
15
40
  * Resolves the background position from a value map. Prefers the combined
16
41
  * `background-position` property if present, otherwise combines
@@ -51,7 +76,7 @@ function isBackgroundImageToken (token) {
51
76
  return false;
52
77
  }
53
78
  const functionName = functionNameMatch[1].toLowerCase();
54
- return !['calc', 'min', 'max', 'clamp', 'var', 'env', 'rgb', 'rgba', 'hsl', 'hsla', 'hwb', 'lab', 'lch', 'oklab', 'oklch', 'color'].includes(functionName);
79
+ return !NON_IMAGE_FUNCTION_NAMES.has(functionName);
55
80
  }
56
81
 
57
82
  /**
@@ -84,10 +84,70 @@ const EDGE_SHORTHANDS = new Set([
84
84
  */
85
85
  const BORDER_EDGE_PROPERTIES = ['border-top', 'border-right', 'border-bottom', 'border-left'];
86
86
 
87
+ /**
88
+ * Converts a lookup table of property name to property list into the same
89
+ * lookup keyed by name with each list as a set, so that "does this shorthand
90
+ * cover that property" is a single key lookup rather than a list scan.
91
+ *
92
+ * @param {object} propertyListsByName A lookup of property name to an array of property names.
93
+ * @return {Map} The same lookup with each array stored as a set.
94
+ */
95
+ function createPropertySetLookup (propertyListsByName) {
96
+ const setsByName = new Map();
97
+ for (const [name, propertyList] of Object.entries(propertyListsByName)) {
98
+ setsByName.set(name, new Set(propertyList));
99
+ }
100
+ return setsByName;
101
+ }
102
+
103
+ /**
104
+ * The longhands of each shorthand, as sets for membership testing.
105
+ *
106
+ * @type {Map<string, Set<string>>}
107
+ */
108
+ const LONGHANDS_BY_SHORTHAND = createPropertySetLookup(shorthandMap);
109
+
110
+ /**
111
+ * The extra properties each shorthand resets, as sets for membership testing.
112
+ *
113
+ * @type {Map<string, Set<string>>}
114
+ */
115
+ const OVERRIDES_BY_SHORTHAND = createPropertySetLookup(shorthandOverrideMap);
116
+
117
+ /**
118
+ * An immutable empty set, returned for properties that are not shorthands so
119
+ * callers can test membership without first checking for a missing entry.
120
+ *
121
+ * @type {Set<string>}
122
+ */
123
+ const NO_PROPERTIES = new Set();
124
+
125
+ /**
126
+ * Returns the set of longhands a shorthand expands into.
127
+ *
128
+ * @param {string} shorthandName The CSS shorthand property name.
129
+ * @return {Set} The longhand property names, empty when the name is not a shorthand.
130
+ */
131
+ function getLonghandsOf (shorthandName) {
132
+ return LONGHANDS_BY_SHORTHAND.get(shorthandName) || NO_PROPERTIES;
133
+ }
134
+
135
+ /**
136
+ * Returns the set of extra properties a shorthand resets beyond its longhands.
137
+ *
138
+ * @param {string} shorthandName The CSS shorthand property name.
139
+ * @return {Set} The reset property names, empty when the shorthand resets nothing else.
140
+ */
141
+ function getOverridesOf (shorthandName) {
142
+ return OVERRIDES_BY_SHORTHAND.get(shorthandName) || NO_PROPERTIES;
143
+ }
144
+
87
145
  export {
88
146
  BORDER_EDGE_PROPERTIES,
89
147
  CSS_WIDE_KEYWORDS,
90
148
  EDGE_SHORTHANDS,
149
+ getLonghandsOf,
150
+ getOverridesOf,
91
151
  shorthandMap,
92
152
  shorthandOverrideMap
93
153
  };
@@ -6,28 +6,45 @@ import { minifyValue } from '../value/minify.js';
6
6
 
7
7
  import {
8
8
  CSS_WIDE_KEYWORDS,
9
- shorthandMap,
10
- shorthandOverrideMap
9
+ getLonghandsOf,
10
+ getOverridesOf,
11
+ shorthandMap
11
12
  } from './config.js';
13
+ import { collectDeclaredProperties } from './lookup.js';
14
+
15
+ /**
16
+ * The leaf longhands each property ultimately sets, computed on first use. The
17
+ * shorthand tables never change, so a property always expands the same way.
18
+ *
19
+ * @type {Map<string, Set<string>>}
20
+ */
21
+ const leafPropertiesByProperty = new Map();
12
22
 
13
23
  /**
14
24
  * Expands a property into the set of leaf longhands it ultimately sets, so that
15
25
  * different groupings of the same box, such as `border-width` and
16
26
  * `border-top-width`, can be compared for equivalent coverage.
17
27
  *
18
- * @param {string} property The property name to expand.
19
- * @param {Set} leafProperties The set collecting the leaf longhand names.
20
- * @return {Set} The set of leaf longhand property names.
28
+ * @param {string} property The property name to expand.
29
+ * @return {Set} The set of leaf longhand property names.
21
30
  */
22
- function expandToLeafProperties (property, leafProperties = new Set()) {
31
+ function expandToLeafProperties (property) {
32
+ const cachedLeaves = leafPropertiesByProperty.get(property);
33
+ if (cachedLeaves) {
34
+ return cachedLeaves;
35
+ }
36
+ const leafProperties = new Set();
23
37
  const longhands = shorthandMap[property];
24
38
  if (!longhands) {
25
39
  leafProperties.add(property);
26
- return leafProperties;
27
- }
28
- for (const longhand of longhands) {
29
- expandToLeafProperties(longhand, leafProperties);
40
+ } else {
41
+ for (const longhand of longhands) {
42
+ for (const leafProperty of expandToLeafProperties(longhand)) {
43
+ leafProperties.add(leafProperty);
44
+ }
45
+ }
30
46
  }
47
+ leafPropertiesByProperty.set(property, leafProperties);
31
48
  return leafProperties;
32
49
  }
33
50
 
@@ -44,7 +61,9 @@ function expandToLeafProperties (property, leafProperties = new Set()) {
44
61
  function coversEveryLonghandOfShorthand (shorthandName, properties) {
45
62
  const coveredLeaves = new Set();
46
63
  for (const property of properties) {
47
- expandToLeafProperties(property, coveredLeaves);
64
+ for (const leafProperty of expandToLeafProperties(property)) {
65
+ coveredLeaves.add(leafProperty);
66
+ }
48
67
  }
49
68
  return [...expandToLeafProperties(shorthandName)].every((leafProperty) => {
50
69
  return coveredLeaves.has(leafProperty);
@@ -70,10 +89,10 @@ function coversEveryLonghandOfShorthand (shorthandName, properties) {
70
89
  * @return {Array} The matching longhand entries, in source order.
71
90
  */
72
91
  function collectLonghandEntries (declarations, shorthandName) {
73
- const longhands = shorthandMap[shorthandName];
92
+ const longhands = getLonghandsOf(shorthandName);
74
93
  const entries = [];
75
94
  declarations.forEach((declaration, index) => {
76
- if (!declaration.property || !longhands.includes(declaration.property)) {
95
+ if (!declaration.property || !longhands.has(declaration.property)) {
77
96
  return;
78
97
  }
79
98
  const minifiedValue = minifyValue(declaration);
@@ -170,12 +189,12 @@ function resolveSharedKeyword (entries) {
170
189
  * @return {boolean} Whether an earlier declaration would be discarded.
171
190
  */
172
191
  function resetsEarlierDeclaration (declarations, shorthandName, insertionIndex) {
173
- const resetProperties = shorthandOverrideMap[shorthandName] || [];
174
- if (!resetProperties.length) {
192
+ const resetProperties = getOverridesOf(shorthandName);
193
+ if (!resetProperties.size) {
175
194
  return false;
176
195
  }
177
196
  return declarations.slice(0, insertionIndex).some((declaration) => {
178
- return resetProperties.includes(declaration.property);
197
+ return resetProperties.has(declaration.property);
179
198
  });
180
199
  }
181
200
 
@@ -184,15 +203,13 @@ function resetsEarlierDeclaration (declarations, shorthandName, insertionIndex)
184
203
  * keyword, followed by the longhands that override it, when that is shorter
185
204
  * than the group of longhands it replaces.
186
205
  *
187
- * @param {Array} declarations The declarations of a single rule.
188
- * @param {string} shorthandName The target shorthand property name.
189
- * @return {Array|null} The rewritten declarations, or null when the rewrite does not apply.
206
+ * @param {Array} declarations The declarations of a single rule.
207
+ * @param {string} shorthandName The target shorthand property name.
208
+ * @param {Set} declaredProperties The property names the rule currently declares.
209
+ * @return {Array|null} The rewritten declarations, or null when the rewrite does not apply.
190
210
  */
191
- function rewriteGroupAsKeywordShorthand (declarations, shorthandName) {
192
- const shorthandAlreadyExists = declarations.some((declaration) => {
193
- return declaration.property === shorthandName;
194
- });
195
- if (shorthandAlreadyExists) {
211
+ function rewriteGroupAsKeywordShorthand (declarations, shorthandName, declaredProperties) {
212
+ if (declaredProperties.has(shorthandName)) {
196
213
  return null;
197
214
  }
198
215
 
@@ -271,12 +288,17 @@ function rewriteGroupAsKeywordShorthand (declarations, shorthandName) {
271
288
  */
272
289
  function hoistCssWideKeywordsIntoShorthands (declarations) {
273
290
  let result = declarations;
291
+ // Every shorthand needs to know which properties the rule declares, so that
292
+ // set is kept alongside the declarations and only rebuilt after a rewrite
293
+ // actually changes them.
294
+ let declaredProperties = collectDeclaredProperties(result);
274
295
  // Shorthands are visited in declaration order, so the widest shorthand of a
275
296
  // family is rewritten before the narrower shorthands it contains.
276
297
  for (const shorthandName of Object.keys(shorthandMap)) {
277
- const rewritten = rewriteGroupAsKeywordShorthand(result, shorthandName);
298
+ const rewritten = rewriteGroupAsKeywordShorthand(result, shorthandName, declaredProperties);
278
299
  if (rewritten) {
279
300
  result = rewritten;
301
+ declaredProperties = collectDeclaredProperties(result);
280
302
  }
281
303
  }
282
304
  return result;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file Builds keyed lookups over a rule's declarations, so the shorthand
3
+ * passes can ask which properties a rule sets without rescanning its
4
+ * declarations once per property they are interested in.
5
+ */
6
+
7
+ /**
8
+ * Indexes the first declaration of each property. The first occurrence is the
9
+ * one a linear search would return, so this stands in for repeated scans that
10
+ * look a property's declaration up by name.
11
+ *
12
+ * @param {Array} declarations The declarations of a single rule, in source order.
13
+ * @return {Map} Map of property name to its first declaration.
14
+ */
15
+ function indexFirstDeclarationByProperty (declarations) {
16
+ const declarationByProperty = new Map();
17
+ for (const declaration of declarations) {
18
+ if (declaration.property && !declarationByProperty.has(declaration.property)) {
19
+ declarationByProperty.set(declaration.property, declaration);
20
+ }
21
+ }
22
+ return declarationByProperty;
23
+ }
24
+
25
+ /**
26
+ * Collects the names of every property a rule declares.
27
+ *
28
+ * @param {Array} declarations The declarations of a single rule.
29
+ * @return {Set} The declared property names.
30
+ */
31
+ function collectDeclaredProperties (declarations) {
32
+ const declaredProperties = new Set();
33
+ for (const declaration of declarations) {
34
+ if (declaration.property) {
35
+ declaredProperties.add(declaration.property);
36
+ }
37
+ }
38
+ return declaredProperties;
39
+ }
40
+
41
+ export {
42
+ collectDeclaredProperties,
43
+ indexFirstDeclarationByProperty
44
+ };
@@ -8,9 +8,10 @@ import {
8
8
  BORDER_EDGE_PROPERTIES,
9
9
  CSS_WIDE_KEYWORDS,
10
10
  EDGE_SHORTHANDS,
11
- shorthandMap,
12
- shorthandOverrideMap
11
+ getLonghandsOf,
12
+ getOverridesOf
13
13
  } from './config.js';
14
+ import { indexFirstDeclarationByProperty } from './lookup.js';
14
15
  import { buildShorthandValue } from './shorthand-values.js';
15
16
 
16
17
  /**
@@ -24,64 +25,62 @@ const MIXED_IMPORTANT_SHORTHANDS = new Set(['margin', 'padding', 'inset', 'posit
24
25
  /**
25
26
  * Determines which longhand properties are present and eligible for merging into a given shorthand. Returns null when the required longhands for the shorthand are not all available.
26
27
  *
27
- * @param {string} shorthand The CSS shorthand property name.
28
- * @param {Array} longhands The expected longhand property names for this shorthand.
29
- * @param {Array} declarations The current array of CSS declaration objects.
30
- * @return {Array|null} The list of longhand names to merge, or null if merging is not possible.
28
+ * @param {string} shorthand The CSS shorthand property name.
29
+ * @param {Array} longhands The expected longhand property names for this shorthand.
30
+ * @param {Set} declaredProperties The property names the rule currently declares.
31
+ * @return {Array|null} The list of longhand names to merge, or null if merging is not possible.
31
32
  */
32
- function getMergeProps (shorthand, longhands, declarations) {
33
+ function getMergeProps (shorthand, longhands, declaredProperties) {
33
34
  const presentLonghands = longhands.filter((longhand) => {
34
- return declarations.some((declaration) => {
35
- return declaration.property === longhand;
36
- });
35
+ return declaredProperties.has(longhand);
37
36
  });
38
37
  if (presentLonghands.length === 0) {
39
38
  return null;
40
39
  }
41
40
  if (shorthand === 'font') {
42
- const hasRequiredFontProps = presentLonghands.includes('font-size') && presentLonghands.includes('font-family');
41
+ const hasRequiredFontProps = declaredProperties.has('font-size') && declaredProperties.has('font-family');
43
42
  if (hasRequiredFontProps) {
44
43
  return presentLonghands;
45
44
  }
46
45
  return null;
47
46
  }
48
47
  if (shorthand === 'background-position') {
49
- const hasBothAxes = presentLonghands.includes('background-position-x') && presentLonghands.includes('background-position-y');
48
+ const hasBothAxes = declaredProperties.has('background-position-x') && declaredProperties.has('background-position-y');
50
49
  if (hasBothAxes) {
51
50
  return presentLonghands;
52
51
  }
53
52
  return null;
54
53
  }
55
54
  if (shorthand === 'background') {
56
- const hasBackgroundProp = presentLonghands.includes('background-color') || presentLonghands.includes('background-image');
55
+ const hasBackgroundProp = declaredProperties.has('background-color') || declaredProperties.has('background-image');
57
56
  if (hasBackgroundProp) {
58
57
  return presentLonghands;
59
58
  }
60
59
  return null;
61
60
  }
62
61
  if (shorthand === 'mask') {
63
- if (presentLonghands.includes('mask-image')) {
62
+ if (declaredProperties.has('mask-image')) {
64
63
  return presentLonghands;
65
64
  }
66
65
  return null;
67
66
  }
68
67
  if (shorthand === 'border-image') {
69
- if (presentLonghands.includes('border-image-source')) {
68
+ if (declaredProperties.has('border-image-source')) {
70
69
  return presentLonghands;
71
70
  }
72
71
  return null;
73
72
  }
74
73
  if (shorthand === 'border') {
75
74
  const hasAllBorderParts = (
76
- presentLonghands.includes('border-width') &&
77
- presentLonghands.includes('border-style') &&
78
- presentLonghands.includes('border-color')
75
+ declaredProperties.has('border-width') &&
76
+ declaredProperties.has('border-style') &&
77
+ declaredProperties.has('border-color')
79
78
  );
80
79
  if (hasAllBorderParts) {
81
80
  return ['border-width', 'border-style', 'border-color'];
82
81
  }
83
82
  const hasAllBorderEdges = BORDER_EDGE_PROPERTIES.every((edgeProperty) => {
84
- return presentLonghands.includes(edgeProperty);
83
+ return declaredProperties.has(edgeProperty);
85
84
  });
86
85
  if (hasAllBorderEdges) {
87
86
  return [...BORDER_EDGE_PROPERTIES];
@@ -90,9 +89,9 @@ function getMergeProps (shorthand, longhands, declarations) {
90
89
  }
91
90
  if (shorthand === 'flex') {
92
91
  const hasAllFlexParts = (
93
- presentLonghands.includes('flex-grow') &&
94
- presentLonghands.includes('flex-shrink') &&
95
- presentLonghands.includes('flex-basis')
92
+ declaredProperties.has('flex-grow') &&
93
+ declaredProperties.has('flex-shrink') &&
94
+ declaredProperties.has('flex-basis')
96
95
  );
97
96
  if (hasAllFlexParts) {
98
97
  return ['flex-grow', 'flex-shrink', 'flex-basis'];
@@ -150,13 +149,12 @@ function canMergeVarValue (value, context) {
150
149
  * @return {boolean} True when the shorthand only affects the merged longhands.
151
150
  */
152
151
  function shorthandAffectsOnlyMergedLonghands (shorthandName, properties) {
153
- const overrides = shorthandOverrideMap[shorthandName] || [];
154
- if (overrides.length) {
152
+ if (getOverridesOf(shorthandName).size) {
155
153
  return false;
156
154
  }
157
- const longhands = shorthandMap[shorthandName] || [];
158
- return longhands.every((longhand) => {
159
- return properties.includes(longhand);
155
+ const mergedProperties = new Set(properties);
156
+ return [...getLonghandsOf(shorthandName)].every((longhand) => {
157
+ return mergedProperties.has(longhand);
160
158
  });
161
159
  }
162
160
 
@@ -196,10 +194,9 @@ function resolveCssWideKeywordMerge (values, shorthandName, properties) {
196
194
  * @return {Array|null} The minified longhand values, or null when one is missing.
197
195
  */
198
196
  function collectLonghandValues (properties, declarations) {
197
+ const declarationByProperty = indexFirstDeclarationByProperty(declarations);
199
198
  const values = properties.map((property) => {
200
- const declaration = declarations.find((candidate) => {
201
- return candidate.property === property;
202
- });
199
+ const declaration = declarationByProperty.get(property);
203
200
  if (declaration) {
204
201
  return minifyValue(declaration);
205
202
  }
@@ -55,20 +55,46 @@ function orderDeclarations (declarations) {
55
55
  }
56
56
 
57
57
  /**
58
- * Get all longhands that a shorthand would override.
58
+ * The overridden longhands of each shorthand, computed on first use. The
59
+ * shorthand tables never change, so the answer for a property name is the same
60
+ * every time it is asked for.
61
+ *
62
+ * @type {Map<string, Set<string>>}
63
+ */
64
+ const overriddenLonghandsByShorthand = new Map();
65
+
66
+ /**
67
+ * Collects all longhands that a shorthand would override.
59
68
  *
60
69
  * @param {string} shorthandProperty The CSS shorthand property name.
61
- * @return {Array} A deduplicated array of all longhand property names that the shorthand overrides, including nested longhands.
70
+ * @return {Set} The longhand property names the shorthand overrides, including nested longhands.
62
71
  */
63
- function getOverriddenLonghands (shorthandProperty) {
72
+ function collectOverriddenLonghands (shorthandProperty) {
64
73
  const direct = shorthandMap[shorthandProperty] || [];
65
74
  const overrides = shorthandOverrideMap[shorthandProperty] || [];
66
- const all = [...direct, ...overrides];
75
+ const all = new Set([...direct, ...overrides]);
67
76
  for (const property of direct) {
68
77
  const nested = shorthandMap[property] || [];
69
- all.push(...nested);
78
+ for (const nestedProperty of nested) {
79
+ all.add(nestedProperty);
80
+ }
81
+ }
82
+ return all;
83
+ }
84
+
85
+ /**
86
+ * Get all longhands that a shorthand would override.
87
+ *
88
+ * @param {string} shorthandProperty The CSS shorthand property name.
89
+ * @return {Set} The longhand property names that the shorthand overrides, including nested longhands.
90
+ */
91
+ function getOverriddenLonghands (shorthandProperty) {
92
+ let overridden = overriddenLonghandsByShorthand.get(shorthandProperty);
93
+ if (!overridden) {
94
+ overridden = collectOverriddenLonghands(shorthandProperty);
95
+ overriddenLonghandsByShorthand.set(shorthandProperty, overridden);
70
96
  }
71
- return [...new Set(all)];
97
+ return overridden;
72
98
  }
73
99
 
74
100
  export {