@thejaredwilcurt/csslop 0.0.49 → 0.0.51
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 +0 -1
- package/package.json +2 -2
- package/src/declarations/border.js +1 -1
- package/src/declarations/process.js +155 -1
- package/src/index.js +7 -7
- package/src/rules/optimize/at-rules.js +113 -0
- package/src/rules/optimize/declaration-writes.js +70 -0
- package/src/rules/optimize/empty-rules.js +51 -0
- package/src/rules/optimize/expand-nesting.js +236 -0
- package/src/rules/optimize/factor-parents.js +343 -0
- package/src/rules/optimize/index.js +71 -0
- package/src/rules/optimize/merge-declarations.js +199 -0
- package/src/rules/optimize/merge-nested-rules.js +117 -0
- package/src/rules/optimize/merge-selectors.js +252 -0
- package/src/rules/optimize/nest-rules.js +202 -0
- package/src/rules/optimize/override-index.js +264 -0
- package/src/rules/optimize/prune-overridden.js +267 -0
- package/src/rules/optimize/selector-normalization.js +60 -0
- package/src/rules/optimize/specificity.js +280 -0
- package/src/{position-try.js → rules/position-try.js} +1 -1
- package/src/rules/stringify.js +13 -0
- package/src/utilities.js +14 -0
- package/src/value/border.js +85 -0
- package/src/value/{colors.js → color/conversion.js} +3 -2
- package/src/value/color/functions.js +463 -0
- package/src/value/{light-dark.js → color/light-dark.js} +1 -1
- package/src/value/{color-mix.js → color/mix.js} +3 -3
- package/src/value/{relative-colors.js → color/relative.js} +3 -3
- package/src/value/color/shorten.js +93 -0
- package/src/value/gradients.js +1 -1
- package/src/value/hyphenate-limit-chars.js +72 -0
- package/src/value/initial-keywords.js +128 -0
- package/src/value/lengths.js +55 -0
- package/src/value/math/evaluate.js +36 -0
- package/src/value/math/fold.js +315 -0
- package/src/value/math/format.js +230 -0
- package/src/value/math/functions.js +302 -0
- package/src/value/math/index.js +219 -0
- package/src/value/math/parser.js +301 -0
- package/src/value/math/property-ranges.js +174 -0
- package/src/value/math/rewrite.js +222 -0
- package/src/value/math/tokenizer.js +160 -0
- package/src/value/math/unit-totals.js +194 -0
- package/src/value/minify.js +127 -1669
- package/src/value/numbers.js +28 -0
- package/src/value/positions.js +152 -0
- package/src/value/property-optimizations.js +485 -0
- package/src/value/segments.js +74 -0
- package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
- package/src/value/{svg-markup.js → svg/markup.js} +1 -1
- package/src/value/time.js +75 -0
- package/src/value/urls.js +242 -0
- package/src/value/whitespace.js +88 -0
- package/src/rules/optimize.js +0 -2314
- package/src/value/math-expression.js +0 -1156
- package/src/value/math.js +0 -886
- /package/src/value/{named-colors.js → color/named.js} +0 -0
package/README.md
CHANGED
|
@@ -321,6 +321,5 @@ Two different cases:
|
|
|
321
321
|
* Ensure arrow functions always take up at least 3 lines, with explicit returns when needed.
|
|
322
322
|
* Always comment regex if used.
|
|
323
323
|
1. After all other code edits, run `npm run lint` and ensure the linter passes.
|
|
324
|
-
1. DO NOT, under any circumstance, run `npm run real` (it will kill actual humans)!
|
|
325
324
|
1. When all done, run the `beep` command to alert me you finished.
|
|
326
325
|
```
|
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.
|
|
5
|
+
"version": "0.0.51",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/pre/start.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"eslint-plugin-import-x": "^4.17.0",
|
|
38
38
|
"eslint-plugin-jsdoc": "^65.4.2",
|
|
39
39
|
"fflate": "^0.8.3",
|
|
40
|
-
"globals": "^17.
|
|
40
|
+
"globals": "^17.14.0",
|
|
41
41
|
"pretty-ms": "^10.0.0",
|
|
42
42
|
"real-world-css-libraries": "^1.0.9",
|
|
43
43
|
"vite": "^8.3.5"
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { splitTopLevelComponents } from '../parser/source-search.js';
|
|
6
|
+
import { namedColors } from '../value/color/named.js';
|
|
6
7
|
import { minifyValue } from '../value/minify.js';
|
|
7
|
-
import { namedColors } from '../value/named-colors.js';
|
|
8
8
|
import {
|
|
9
9
|
collapseShorthandParts,
|
|
10
10
|
hasSubstitutedParts
|
|
@@ -106,6 +106,33 @@ function collectCandidateShorthands (declaredProperties) {
|
|
|
106
106
|
*/
|
|
107
107
|
const PARSE_TIME_FALLBACK_MARKERS = ['calc(', 'env(', '-webkit-'];
|
|
108
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Vendor-prefixed gradient functions that should be removed when the standard
|
|
111
|
+
* version is present. These are obsolete as all modern browsers support the
|
|
112
|
+
* unprefixed versions.
|
|
113
|
+
*
|
|
114
|
+
* @type {Array}
|
|
115
|
+
*/
|
|
116
|
+
const VENDOR_PREFIXED_GRADIENTS = [
|
|
117
|
+
'-moz-linear-gradient',
|
|
118
|
+
'-webkit-linear-gradient',
|
|
119
|
+
'-o-linear-gradient',
|
|
120
|
+
'-ms-linear-gradient'
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Vendor-prefixed display values that should be removed when the standard
|
|
125
|
+
* version is present. These are obsolete as all modern browsers support the
|
|
126
|
+
* unprefixed versions.
|
|
127
|
+
*
|
|
128
|
+
* @type {Array}
|
|
129
|
+
*/
|
|
130
|
+
const VENDOR_PREFIXED_DISPLAY_VALUES = [
|
|
131
|
+
'-webkit-box',
|
|
132
|
+
'-ms-flexbox',
|
|
133
|
+
'-webkit-flex'
|
|
134
|
+
];
|
|
135
|
+
|
|
109
136
|
/**
|
|
110
137
|
* Determines whether a value relies on syntax that older browsers cannot parse,
|
|
111
138
|
* which means a preceding declaration for the same property is an intentional
|
|
@@ -120,6 +147,61 @@ function needsParseTimeFallback (value) {
|
|
|
120
147
|
});
|
|
121
148
|
}
|
|
122
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Determines whether a value uses a vendor-prefixed gradient function.
|
|
152
|
+
*
|
|
153
|
+
* @param {string} value The minified CSS value string.
|
|
154
|
+
* @return {boolean} Whether the value contains a vendor-prefixed gradient.
|
|
155
|
+
*/
|
|
156
|
+
function usesVendorPrefixedGradient (value) {
|
|
157
|
+
return VENDOR_PREFIXED_GRADIENTS.some((gradient) => {
|
|
158
|
+
return value.includes(gradient);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Extracts the standard gradient from a vendor-prefixed gradient value by
|
|
164
|
+
* replacing the vendor prefix with the standard name.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} value The minified CSS value string.
|
|
167
|
+
* @return {string} The value with vendor prefixes replaced.
|
|
168
|
+
*/
|
|
169
|
+
function normalizeVendorPrefixedGradient (value) {
|
|
170
|
+
let normalized = value;
|
|
171
|
+
for (const prefixedGradient of VENDOR_PREFIXED_GRADIENTS) {
|
|
172
|
+
normalized = normalized.replace(prefixedGradient, 'linear-gradient');
|
|
173
|
+
}
|
|
174
|
+
return normalized;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Determines whether a value uses a vendor-prefixed display value.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} value The minified CSS value string.
|
|
181
|
+
* @return {boolean} Whether the value contains a vendor-prefixed display value.
|
|
182
|
+
*/
|
|
183
|
+
function usesVendorPrefixedDisplayValue (value) {
|
|
184
|
+
return VENDOR_PREFIXED_DISPLAY_VALUES.some((displayValue) => {
|
|
185
|
+
return value === displayValue;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Normalizes a vendor-prefixed display value to its standard equivalent.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} value The minified CSS value string.
|
|
193
|
+
* @return {string} The normalized display value.
|
|
194
|
+
*/
|
|
195
|
+
function normalizeVendorPrefixedDisplayValue (value) {
|
|
196
|
+
// Map vendor-prefixed display values to their standard equivalents
|
|
197
|
+
const displayValueMap = {
|
|
198
|
+
'-webkit-box': 'flex',
|
|
199
|
+
'-ms-flexbox': 'flex',
|
|
200
|
+
'-webkit-flex': 'flex'
|
|
201
|
+
};
|
|
202
|
+
return displayValueMap[value] || value;
|
|
203
|
+
}
|
|
204
|
+
|
|
123
205
|
/**
|
|
124
206
|
* Creates the bookkeeping for the declarations that survive deduplication.
|
|
125
207
|
*
|
|
@@ -197,6 +279,21 @@ function findLastPositionOfProperty (survivors, property) {
|
|
|
197
279
|
return positions[positions.length - 1];
|
|
198
280
|
}
|
|
199
281
|
|
|
282
|
+
/**
|
|
283
|
+
* Finds all surviving declarations that set a given property, in source order.
|
|
284
|
+
*
|
|
285
|
+
* @param {object} survivors The surviving-declaration bookkeeping.
|
|
286
|
+
* @param {string} property The property name to look for.
|
|
287
|
+
* @return {Array} The positions of all matching declarations, in source order.
|
|
288
|
+
*/
|
|
289
|
+
function findAllPositionsOfProperty (survivors, property) {
|
|
290
|
+
const positions = survivors.positionsByProperty.get(property);
|
|
291
|
+
if (!positions?.length) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
return [...positions];
|
|
295
|
+
}
|
|
296
|
+
|
|
200
297
|
/**
|
|
201
298
|
* Finds the surviving vendor-prefixed declarations that set a prefixed form of
|
|
202
299
|
* a property, such as `-webkit-transform` for `transform`, including legacy
|
|
@@ -264,6 +361,11 @@ function deduplicateDeclarations (declarations) {
|
|
|
264
361
|
|
|
265
362
|
const minifiedValue = minifyValue(declaration);
|
|
266
363
|
|
|
364
|
+
// IE6 hack properties starting with * are obsolete and should be removed
|
|
365
|
+
if (propertyName.startsWith('*')) {
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
|
|
267
369
|
// A vendor-prefixed property that repeats the value of its unprefixed
|
|
268
370
|
// form is dead code for browsers that read the unprefixed one
|
|
269
371
|
if (propertyName.startsWith('-') && !propertyName.startsWith('--')) {
|
|
@@ -273,7 +375,7 @@ function deduplicateDeclarations (declarations) {
|
|
|
273
375
|
}
|
|
274
376
|
}
|
|
275
377
|
|
|
276
|
-
|
|
378
|
+
let previousPosition = findLastPositionOfProperty(survivors, propertyName);
|
|
277
379
|
|
|
278
380
|
// An unprefixed property with the same value also replaces every prefixed form of it
|
|
279
381
|
let prefixedPositions = [];
|
|
@@ -288,6 +390,22 @@ function deduplicateDeclarations (declarations) {
|
|
|
288
390
|
}
|
|
289
391
|
}
|
|
290
392
|
|
|
393
|
+
// For display property, remove vendor-prefixed values when standard version is present
|
|
394
|
+
if (propertyName === 'display' && !usesVendorPrefixedDisplayValue(minifiedValue)) {
|
|
395
|
+
const allPositions = findAllPositionsOfProperty(survivors, propertyName);
|
|
396
|
+
for (const position of allPositions) {
|
|
397
|
+
const existingValue = minifyValue(survivors.slots[position]);
|
|
398
|
+
if (
|
|
399
|
+
usesVendorPrefixedDisplayValue(existingValue) &&
|
|
400
|
+
normalizeVendorPrefixedDisplayValue(existingValue) === minifiedValue
|
|
401
|
+
) {
|
|
402
|
+
dropDeclaration(survivors, position);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Recalculate previous position after dropping declarations
|
|
406
|
+
previousPosition = findLastPositionOfProperty(survivors, propertyName);
|
|
407
|
+
}
|
|
408
|
+
|
|
291
409
|
if (previousPosition !== -1) {
|
|
292
410
|
const previousValue = minifyValue(survivors.slots[previousPosition]);
|
|
293
411
|
|
|
@@ -295,6 +413,42 @@ function deduplicateDeclarations (declarations) {
|
|
|
295
413
|
continue;
|
|
296
414
|
}
|
|
297
415
|
|
|
416
|
+
// When the current value is a simple value that all browsers understand,
|
|
417
|
+
// remove ALL previous declarations of the same property since the current
|
|
418
|
+
// one works everywhere and makes all earlier ones redundant
|
|
419
|
+
if (!needsParseTimeFallback(minifiedValue)) {
|
|
420
|
+
const allPositions = findAllPositionsOfProperty(survivors, propertyName);
|
|
421
|
+
for (const position of allPositions) {
|
|
422
|
+
dropDeclaration(survivors, position);
|
|
423
|
+
}
|
|
424
|
+
keepDeclaration(survivors, declaration);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// When the current value is a standard gradient and the previous value
|
|
429
|
+
// is a vendor-prefixed version of the same gradient, remove the prefixed one
|
|
430
|
+
if (
|
|
431
|
+
!usesVendorPrefixedGradient(minifiedValue) &&
|
|
432
|
+
usesVendorPrefixedGradient(previousValue) &&
|
|
433
|
+
normalizeVendorPrefixedGradient(previousValue) === minifiedValue
|
|
434
|
+
) {
|
|
435
|
+
dropDeclaration(survivors, previousPosition);
|
|
436
|
+
keepDeclaration(survivors, declaration);
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// When the current value is a standard display value and the previous value
|
|
441
|
+
// is a vendor-prefixed version that maps to the same standard value, remove the prefixed one
|
|
442
|
+
if (
|
|
443
|
+
!usesVendorPrefixedDisplayValue(minifiedValue) &&
|
|
444
|
+
usesVendorPrefixedDisplayValue(previousValue) &&
|
|
445
|
+
normalizeVendorPrefixedDisplayValue(previousValue) === minifiedValue
|
|
446
|
+
) {
|
|
447
|
+
dropDeclaration(survivors, previousPosition);
|
|
448
|
+
keepDeclaration(survivors, declaration);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
|
|
298
452
|
// Fallbacks for functions older browsers cannot parse should be kept
|
|
299
453
|
if (needsParseTimeFallback(minifiedValue) && !needsParseTimeFallback(previousValue)) {
|
|
300
454
|
keepDeclaration(survivors, declaration);
|
package/src/index.js
CHANGED
|
@@ -14,19 +14,19 @@ import {
|
|
|
14
14
|
import { recordStylesheetResetProperties } from './declarations/reset-hazards.js';
|
|
15
15
|
import { parse } from './parser/index.js';
|
|
16
16
|
import { splitTopLevelCommaList } from './parser/source-search.js';
|
|
17
|
-
import {
|
|
18
|
-
analyzePositionTryRules,
|
|
19
|
-
cleanPositionTryRules,
|
|
20
|
-
collectRuleMetadata,
|
|
21
|
-
filterUnusedPositionTry
|
|
22
|
-
} from './position-try.js';
|
|
23
17
|
import { preprocessDeclarationBlocks } from './preprocess.js';
|
|
24
18
|
import { combineLanguageSelectors } from './rules/lang.js';
|
|
25
19
|
import { normalizeRuleSelectors } from './rules/normalize.js';
|
|
26
20
|
import {
|
|
27
21
|
clearSelectorCaches,
|
|
28
22
|
optimizeRuleList
|
|
29
|
-
} from './rules/optimize.js';
|
|
23
|
+
} from './rules/optimize/index.js';
|
|
24
|
+
import {
|
|
25
|
+
analyzePositionTryRules,
|
|
26
|
+
cleanPositionTryRules,
|
|
27
|
+
collectRuleMetadata,
|
|
28
|
+
filterUnusedPositionTry
|
|
29
|
+
} from './rules/position-try.js';
|
|
30
30
|
import { combineLegacyAnySelectors } from './rules/selectors.js';
|
|
31
31
|
import {
|
|
32
32
|
removeRedundantLayerStatementSemicolon,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Consolidates grouping at-rules by merging matching `@media` and `@layer` blocks, and drops the `@keyframes` that a later definition replaces.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
normalizeLayerNames,
|
|
7
|
+
normalizeMedia
|
|
8
|
+
} from '../normalize.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Merges adjacent `@media` rules that share an identical normalized query string and deduplicates their child selector rules.
|
|
12
|
+
*
|
|
13
|
+
* @param {Array} rules The AST rule nodes to process.
|
|
14
|
+
* @param {function(Array): Array} mergeSelectorRules Callback to merge selector rules within each media block.
|
|
15
|
+
* @return {Array} A new array of rules with consecutive identical `@media` blocks combined.
|
|
16
|
+
*/
|
|
17
|
+
function mergeMediaRules (rules, mergeSelectorRules) {
|
|
18
|
+
const mediaMap = new Map();
|
|
19
|
+
const result = [];
|
|
20
|
+
for (const rule of rules) {
|
|
21
|
+
if (rule.type === 'media') {
|
|
22
|
+
const normalizedQuery = normalizeMedia(rule.media);
|
|
23
|
+
if (mediaMap.has(normalizedQuery)) {
|
|
24
|
+
mediaMap.get(normalizedQuery).rules.push(...(rule.rules || []));
|
|
25
|
+
} else {
|
|
26
|
+
mediaMap.set(normalizedQuery, rule);
|
|
27
|
+
result.push(rule);
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
// Clearing a map replaces its storage, so an already empty one is left alone
|
|
31
|
+
if (rule.type !== 'whitespace' && mediaMap.size) {
|
|
32
|
+
mediaMap.clear();
|
|
33
|
+
}
|
|
34
|
+
result.push(rule);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const rule of result) {
|
|
38
|
+
if (rule.type === 'media' && rule.rules && rule.rules.length) {
|
|
39
|
+
rule.rules = mergeSelectorRules(rule.rules);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Removes duplicate `@keyframes` definitions, keeping only the last occurrence of each named animation.
|
|
47
|
+
*
|
|
48
|
+
* @param {Array} rules The AST rule nodes to deduplicate.
|
|
49
|
+
* @return {Array} A new array of rules with earlier duplicate `@keyframes` removed.
|
|
50
|
+
*/
|
|
51
|
+
function deduplicateKeyframes (rules) {
|
|
52
|
+
const lastIndexByName = new Map();
|
|
53
|
+
for (let i = 0; i < rules.length; i++) {
|
|
54
|
+
if (rules[i].type === 'keyframes' && rules[i].name) {
|
|
55
|
+
lastIndexByName.set(rules[i].name, i);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return rules.filter((rule, index) => {
|
|
59
|
+
if (rule.type === 'keyframes' && rule.name) {
|
|
60
|
+
return lastIndexByName.get(rule.name) === index;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Merges `@layer` blocks with the same name by combining their child rules, deduplicates layer statements, and merges selector rules within each block.
|
|
68
|
+
*
|
|
69
|
+
* @param {Array} rules The AST rule nodes to process.
|
|
70
|
+
* @param {function(Array): Array} mergeSelectorRules Callback to merge selector rules within each layer block.
|
|
71
|
+
* @return {Array} A new array of rules with same-name `@layer` blocks combined.
|
|
72
|
+
*/
|
|
73
|
+
function mergeLayerRules (rules, mergeSelectorRules) {
|
|
74
|
+
const layerBlockMap = new Map();
|
|
75
|
+
const layerStatementSeen = new Set();
|
|
76
|
+
const result = [];
|
|
77
|
+
for (const rule of rules) {
|
|
78
|
+
if (rule.type === 'layer') {
|
|
79
|
+
const layerName = normalizeLayerNames(rule.layer);
|
|
80
|
+
if (rule.rules && rule.rules.length > 0) {
|
|
81
|
+
if (layerName && layerBlockMap.has(layerName)) {
|
|
82
|
+
layerBlockMap.get(layerName).rules.push(...rule.rules);
|
|
83
|
+
} else {
|
|
84
|
+
if (layerName) {
|
|
85
|
+
layerBlockMap.set(layerName, rule);
|
|
86
|
+
}
|
|
87
|
+
result.push(rule);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
if (!layerName || !layerStatementSeen.has(layerName)) {
|
|
91
|
+
if (layerName) {
|
|
92
|
+
layerStatementSeen.add(layerName);
|
|
93
|
+
}
|
|
94
|
+
result.push(rule);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
result.push(rule);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const rule of result) {
|
|
102
|
+
if (rule.type === 'layer' && rule.rules && rule.rules.length) {
|
|
103
|
+
rule.rules = mergeSelectorRules(rule.rules);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
deduplicateKeyframes,
|
|
111
|
+
mergeLayerRules,
|
|
112
|
+
mergeMediaRules
|
|
113
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Describes what a declaration writes to: whether it declares a property at all, which leaf properties it can override, and whether it is flagged important.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
expandToLeafProperties,
|
|
7
|
+
getOverridesOf
|
|
8
|
+
} from '../../declarations/config.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The leaf properties each declared property can write to, computed on first
|
|
12
|
+
* use. The shorthand tables never change, so a property always expands the same
|
|
13
|
+
* way.
|
|
14
|
+
*
|
|
15
|
+
* @type {Map<string, Set<string>>}
|
|
16
|
+
*/
|
|
17
|
+
const overridablePropertiesByProperty = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Expands a declared property into every leaf longhand it can write to,
|
|
21
|
+
* including the extras a shorthand resets beyond its own longhands. Two
|
|
22
|
+
* declarations can only override one another when these sets intersect, which
|
|
23
|
+
* is what makes `margin` conflict with `margin-top` but not with `color`.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} property The declared property name.
|
|
26
|
+
* @return {Set} The leaf property names the declaration writes to.
|
|
27
|
+
*/
|
|
28
|
+
function expandToOverridableProperties (property) {
|
|
29
|
+
const cachedProperties = overridablePropertiesByProperty.get(property);
|
|
30
|
+
if (cachedProperties) {
|
|
31
|
+
return cachedProperties;
|
|
32
|
+
}
|
|
33
|
+
const overridableProperties = new Set(expandToLeafProperties(property));
|
|
34
|
+
for (const resetProperty of getOverridesOf(property)) {
|
|
35
|
+
for (const leafProperty of expandToLeafProperties(resetProperty)) {
|
|
36
|
+
overridableProperties.add(leafProperty);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
overridablePropertiesByProperty.set(property, overridableProperties);
|
|
40
|
+
return overridableProperties;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Reports whether an entry of a rule's body is a declaration whose standing in
|
|
45
|
+
* the cascade can be read off the property it writes. Anything else a body may
|
|
46
|
+
* hold, such as a nested rule, is never shadowed by a declaration beside it.
|
|
47
|
+
*
|
|
48
|
+
* @param {object} entry An entry of a rule's declaration list.
|
|
49
|
+
* @return {boolean} True when the entry is a property declaration.
|
|
50
|
+
*/
|
|
51
|
+
function isPropertyDeclaration (entry) {
|
|
52
|
+
return entry.type === 'declaration' && Boolean(entry.property);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Determines whether a declaration carries a trailing `!important` flag.
|
|
57
|
+
*
|
|
58
|
+
* @param {object} declaration The AST declaration node.
|
|
59
|
+
* @return {boolean} True when the declaration is important.
|
|
60
|
+
*/
|
|
61
|
+
function isImportantDeclaration (declaration) {
|
|
62
|
+
// A trailing !important suffix on the raw declaration value
|
|
63
|
+
return /!\s*important\s*$/i.test(declaration.rawValue || declaration.value || '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
expandToOverridableProperties,
|
|
68
|
+
isImportantDeclaration,
|
|
69
|
+
isPropertyDeclaration
|
|
70
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Detects and removes rules that produce no CSS output once minified.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether a rule is effectively empty, containing no meaningful
|
|
7
|
+
* CSS output after minification. A rule is effectively empty when it has
|
|
8
|
+
* no declarations, or all of its entries are whitespace, non-important
|
|
9
|
+
* comments, or recursively empty nested rules.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} rule The AST rule node to evaluate.
|
|
12
|
+
* @return {boolean} True if the rule produces no CSS output.
|
|
13
|
+
*/
|
|
14
|
+
function isRuleEffectivelyEmpty (rule) {
|
|
15
|
+
if (rule.type !== 'rule') {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const nonWhitespaceEntries = (rule.declarations || []).filter((declaration) => {
|
|
19
|
+
return declaration.type !== 'whitespace';
|
|
20
|
+
});
|
|
21
|
+
if (nonWhitespaceEntries.length === 0) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return nonWhitespaceEntries.every((entry) => {
|
|
25
|
+
if (entry.type === 'comment') {
|
|
26
|
+
return !entry.comment?.startsWith('!');
|
|
27
|
+
}
|
|
28
|
+
if (entry.type === 'rule') {
|
|
29
|
+
return isRuleEffectivelyEmpty(entry);
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Filters out effectively empty rules from the rules array, preventing
|
|
37
|
+
* empty rules from being nested into parent rules during later
|
|
38
|
+
* optimization passes and producing incorrect non-empty output.
|
|
39
|
+
*
|
|
40
|
+
* @param {Array} rules The AST rule nodes to filter.
|
|
41
|
+
* @return {Array} A new array with effectively empty rules removed.
|
|
42
|
+
*/
|
|
43
|
+
function removeEmptyRules (rules) {
|
|
44
|
+
return rules.filter((rule) => {
|
|
45
|
+
return !isRuleEffectivelyEmpty(rule);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
removeEmptyRules
|
|
51
|
+
};
|