@thejaredwilcurt/csslop 0.0.20 → 0.0.22
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 +12 -5
- package/package.json +5 -7
- package/src/declarations/background.js +322 -0
- package/src/declarations/border.js +120 -0
- package/src/declarations/config.js +36 -0
- package/src/declarations/css-wide-keywords.js +285 -0
- package/src/declarations/merge.js +323 -0
- package/src/declarations/order.js +77 -0
- package/src/declarations/process.js +188 -869
- package/src/declarations/shorthand-values.js +374 -0
- package/src/index.js +5 -2
- package/src/rules/normalize.js +29 -5
- package/src/rules/optimize.js +5 -2
- package/src/rules/stringify.js +49 -18
- package/src/value/minify.js +102 -36
- package/src/value/syntax.js +82 -1
package/src/value/minify.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './relative-colors.js';
|
|
31
31
|
import {
|
|
32
32
|
collapseShorthandParts,
|
|
33
|
+
convertAbsoluteLengthToPx,
|
|
33
34
|
normalizeScaleComponent,
|
|
34
35
|
parseAlphaString,
|
|
35
36
|
parseAngleToDegrees,
|
|
@@ -59,7 +60,9 @@ const POSITION_AREA_SHORTHANDS = {
|
|
|
59
60
|
* Regex matching hex color tokens (#rgb, #rgba, #rrggbb, #rrggbbaa) and CSS named
|
|
60
61
|
* color keywords. Hex patterns are ordered longest-first to avoid partial matches.
|
|
61
62
|
* Named colors are sorted longest-first so longer names like `darkslategray` are
|
|
62
|
-
* matched before shorter substrings.
|
|
63
|
+
* matched before shorter substrings. A named color only counts as a color keyword
|
|
64
|
+
* when it is a complete identifier, so hyphens on either side disqualify it (this
|
|
65
|
+
* keeps identifiers such as the custom property name in `var(--grey)` intact).
|
|
63
66
|
*
|
|
64
67
|
* @type {RegExp}
|
|
65
68
|
*/
|
|
@@ -68,11 +71,11 @@ const COLOR_TOKEN_PATTERN = new RegExp(
|
|
|
68
71
|
'#[0-9a-fA-F]{6}(?![0-9a-fA-F])|' +
|
|
69
72
|
'#[0-9a-fA-F]{4}(?![0-9a-fA-F])|' +
|
|
70
73
|
'#[0-9a-fA-F]{3}(?![0-9a-fA-F])|' +
|
|
71
|
-
'\\
|
|
74
|
+
'(?<![\\w-])(?:' +
|
|
72
75
|
Object.keys(namedColors).sort((a, b) => {
|
|
73
76
|
return b.length - a.length;
|
|
74
77
|
}).join('|') +
|
|
75
|
-
')\\
|
|
78
|
+
')(?![\\w-])',
|
|
76
79
|
'gi'
|
|
77
80
|
);
|
|
78
81
|
|
|
@@ -180,6 +183,37 @@ function replaceOutsideStringsAndUrls (value, replacer) {
|
|
|
180
183
|
return result;
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Lowercases every hex color token in a CSS value, since uppercase hex digits
|
|
188
|
+
* compress worse and are equivalent to their lowercase form.
|
|
189
|
+
*
|
|
190
|
+
* @param {string} value The CSS value string.
|
|
191
|
+
* @return {string} The value with all hex color tokens lowercased.
|
|
192
|
+
*/
|
|
193
|
+
function lowercaseHexColors (value) {
|
|
194
|
+
return replaceOutsideStringsAndUrls(value, (segment) => {
|
|
195
|
+
// Match hex color tokens of 3 to 8 hex digits
|
|
196
|
+
return segment.replace(/#([0-9a-fA-F]{3,8})\b/gi, (hexColor) => {
|
|
197
|
+
return hexColor.toLowerCase();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Removes whitespace that precedes a hex color token. A `#` unambiguously starts
|
|
204
|
+
* a hash token in CSS, so no separator is required between it and a preceding
|
|
205
|
+
* ident, keyword, or number (e.g. `1px solid #f00` becomes `1px solid#f00`).
|
|
206
|
+
*
|
|
207
|
+
* @param {string} value The CSS value string.
|
|
208
|
+
* @return {string} The value with spaces before hex colors removed.
|
|
209
|
+
*/
|
|
210
|
+
function elideSpaceBeforeHexColors (value) {
|
|
211
|
+
return replaceOutsideStringsAndUrls(value, (segment) => {
|
|
212
|
+
// Match whitespace followed by a hex color token of 3 to 8 hex digits
|
|
213
|
+
return segment.replace(/\s+#([0-9a-fA-F]{3,8})\b/gi, '#$1');
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
183
217
|
/**
|
|
184
218
|
* Chooses the shortest valid representation for the path inside a `url(...)`
|
|
185
219
|
* token, weighing an unquoted form, an escaped single space, and a quoted form.
|
|
@@ -678,15 +712,59 @@ function convertMillisecondsToSeconds (value) {
|
|
|
678
712
|
});
|
|
679
713
|
}
|
|
680
714
|
|
|
715
|
+
/**
|
|
716
|
+
* Matches an absolute CSS length token: an optionally signed number followed by
|
|
717
|
+
* an absolute length unit. The lookbehind rejects digits that belong to a larger
|
|
718
|
+
* identifier, such as the custom property name in `var(--size-2in)`, where the
|
|
719
|
+
* digits and unit do not form a value of their own.
|
|
720
|
+
*
|
|
721
|
+
* @type {RegExp}
|
|
722
|
+
*/
|
|
723
|
+
const ABSOLUTE_LENGTH_PATTERN = /(?<![\w#.%-])(-?(?:\d+|\d*\.\d+))(pt|pc|in|cm|mm|q)\b/gi;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* The largest difference in pixels that a rounded conversion may introduce and
|
|
727
|
+
* still count as exact, which allows for binary floating point error without
|
|
728
|
+
* allowing a visible change to the rendered length.
|
|
729
|
+
*
|
|
730
|
+
* @type {number}
|
|
731
|
+
*/
|
|
732
|
+
const PIXEL_ROUNDING_TOLERANCE = 1e-6;
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Converts absolute length values (pt, pc, in, cm, mm, Q) to their pixel
|
|
736
|
+
* equivalent when the conversion is exact and the pixel form is no longer than
|
|
737
|
+
* the original. Normalizing to px also improves compression by reducing the
|
|
738
|
+
* number of distinct unit strings in the output.
|
|
739
|
+
*
|
|
740
|
+
* @param {string} value A CSS value segment, outside strings and urls.
|
|
741
|
+
* @return {string} The segment with eligible absolute lengths converted to px.
|
|
742
|
+
*/
|
|
743
|
+
function convertAbsoluteLengthsToPx (value) {
|
|
744
|
+
return value.replace(ABSOLUTE_LENGTH_PATTERN, (token, amount, unit) => {
|
|
745
|
+
const pixels = convertAbsoluteLengthToPx(amount, unit);
|
|
746
|
+
if (pixels === null) {
|
|
747
|
+
return token;
|
|
748
|
+
}
|
|
749
|
+
const converted = roundCompactNumber(pixels) + 'px';
|
|
750
|
+
const isExact = Math.abs(parseFloat(converted) - pixels) < PIXEL_ROUNDING_TOLERANCE;
|
|
751
|
+
if (!isExact || converted.length > token.length) {
|
|
752
|
+
return token;
|
|
753
|
+
}
|
|
754
|
+
return converted;
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
681
758
|
/**
|
|
682
759
|
* Applies property-specific optimizations to a CSS value (transition, flex, font,
|
|
683
760
|
* background, display, scale, border-radius, shorthand collapsing, etc.).
|
|
684
761
|
*
|
|
685
|
-
* @param {string}
|
|
686
|
-
* @param {string}
|
|
687
|
-
* @
|
|
762
|
+
* @param {string} val The CSS value string after generic minification.
|
|
763
|
+
* @param {string} property The CSS property name.
|
|
764
|
+
* @param {boolean} allowsHexSpaceElision Whether the space preceding a hex color may be removed.
|
|
765
|
+
* @return {string} The value with property-specific optimizations applied.
|
|
688
766
|
*/
|
|
689
|
-
function applyPropertyOptimizations (val, property) {
|
|
767
|
+
function applyPropertyOptimizations (val, property, allowsHexSpaceElision) {
|
|
690
768
|
if (property === 'font-weight' && isUnicodeCharset()) {
|
|
691
769
|
// Replace font-weight keyword "bold" with its numeric equivalent
|
|
692
770
|
val = val.replace(/\bbold\b/gi, '700');
|
|
@@ -833,11 +911,11 @@ function applyPropertyOptimizations (val, property) {
|
|
|
833
911
|
});
|
|
834
912
|
}
|
|
835
913
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
914
|
+
// Custom properties hold an arbitrary token stream rather than a typed value,
|
|
915
|
+
// so a unit-like token in one is not necessarily a length.
|
|
916
|
+
const isCustomProperty = Boolean(property) && property.startsWith('--');
|
|
917
|
+
if (!isCustomProperty) {
|
|
918
|
+
val = replaceOutsideStringsAndUrls(val, convertAbsoluteLengthsToPx);
|
|
841
919
|
}
|
|
842
920
|
|
|
843
921
|
if (property === 'syntax') {
|
|
@@ -859,12 +937,9 @@ function applyPropertyOptimizations (val, property) {
|
|
|
859
937
|
val = replaceOutsideStringsAndUrls(val, shortenColorValues);
|
|
860
938
|
|
|
861
939
|
// Remove space before hex colors (second pass after color evaluations)
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
// Then remove other spaces before hex colors
|
|
866
|
-
return segment.replace(/\s+#([0-9a-fA-F]{3,8})\b/gi, '#$1');
|
|
867
|
-
});
|
|
940
|
+
if (allowsHexSpaceElision) {
|
|
941
|
+
val = elideSpaceBeforeHexColors(val);
|
|
942
|
+
}
|
|
868
943
|
if (property !== 'transform' && property !== 'background' && property !== 'src') {
|
|
869
944
|
// Restore space after close-paren when followed by an alphanumeric, hash, or hyphen
|
|
870
945
|
val = val.replace(/\)(?=[0-9a-zA-Z#-])/g, ') ');
|
|
@@ -911,9 +986,6 @@ function applyPropertyOptimizations (val, property) {
|
|
|
911
986
|
if (property === 'border') {
|
|
912
987
|
// Remove default "medium" border-width keyword
|
|
913
988
|
val = val.replace(/\bmedium\s+/g, '');
|
|
914
|
-
// Restore missing space between border-style and a 4-digit hex color (with alpha) when they are adjacent
|
|
915
|
-
// This is needed because solid#0000 could be parsed as solid followed by #000 followed by position 0
|
|
916
|
-
val = val.replace(/\b(solid|dashed|dotted|double|groove|ridge|inset|outset|hidden|none)#([0-9a-fA-F]{4})\b/gi, '$1 #$2');
|
|
917
989
|
}
|
|
918
990
|
|
|
919
991
|
if (property === 'outline') {
|
|
@@ -982,6 +1054,10 @@ function minifyValue (declaration) {
|
|
|
982
1054
|
return 'none';
|
|
983
1055
|
}
|
|
984
1056
|
let val = declaration.value;
|
|
1057
|
+
// Values assembled from already-minified longhands keep the separator spaces
|
|
1058
|
+
// between their components, because those spaces delimit the shorthand's
|
|
1059
|
+
// parts rather than the authored whitespace of a single written value.
|
|
1060
|
+
const allowsHexSpaceElision = !declaration.isAssembledShorthand;
|
|
985
1061
|
|
|
986
1062
|
if (typeof val === 'string') {
|
|
987
1063
|
val = val.trim();
|
|
@@ -1024,20 +1100,10 @@ function minifyValue (declaration) {
|
|
|
1024
1100
|
val = roundCompactNumber(rawNumber, 4) + rawUnit;
|
|
1025
1101
|
}
|
|
1026
1102
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
// Remove other spaces before hex colors
|
|
1032
|
-
segment = segment.replace(/\s+#([0-9a-fA-F]{3,8})\b/gi, '#$1');
|
|
1033
|
-
// Restore the preserved space
|
|
1034
|
-
segment = segment.replace(/__BORDER_SPACE__#/g, ' #');
|
|
1035
|
-
// Lowercase hex color tokens for consistency and shorter output
|
|
1036
|
-
segment = segment.replace(/#([0-9a-fA-F]{3,8})\b/gi, (m) => {
|
|
1037
|
-
return m.toLowerCase();
|
|
1038
|
-
});
|
|
1039
|
-
return segment;
|
|
1040
|
-
});
|
|
1103
|
+
val = lowercaseHexColors(val);
|
|
1104
|
+
if (allowsHexSpaceElision) {
|
|
1105
|
+
val = elideSpaceBeforeHexColors(val);
|
|
1106
|
+
}
|
|
1041
1107
|
|
|
1042
1108
|
// Convert color functions to hex equivalents
|
|
1043
1109
|
val = convertColorsToHex(val);
|
|
@@ -1049,7 +1115,7 @@ function minifyValue (declaration) {
|
|
|
1049
1115
|
val = simplifyEquivalentLightDarkFunctions(val);
|
|
1050
1116
|
|
|
1051
1117
|
// Property-specific optimizations
|
|
1052
|
-
val = applyPropertyOptimizations(val, declaration.property);
|
|
1118
|
+
val = applyPropertyOptimizations(val, declaration.property, allowsHexSpaceElision);
|
|
1053
1119
|
|
|
1054
1120
|
// Minify relative color syntax (identity resolution and whitespace collapsing)
|
|
1055
1121
|
val = minifyRelativeColorSyntax(val);
|
package/src/value/syntax.js
CHANGED
|
@@ -53,4 +53,85 @@ function findMatchingParenthesis (value, openParenIndex) {
|
|
|
53
53
|
return -1;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Splits a CSS value into its top-level components, keeping parenthesized
|
|
58
|
+
* function arguments and quoted strings intact. Components are separated by
|
|
59
|
+
* whitespace, or by a `#`, which always starts a hash token and therefore ends
|
|
60
|
+
* any component already in progress.
|
|
61
|
+
*
|
|
62
|
+
* For example, `rgb(0 0 0) red` yields `["rgb(0 0 0)", "red"]`, and the
|
|
63
|
+
* minified `red#00f` yields `["red", "#00f"]`.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} value The CSS value to split.
|
|
66
|
+
* @return {Array} The top-level components of the value.
|
|
67
|
+
*/
|
|
68
|
+
function splitTopLevelComponents (value) {
|
|
69
|
+
const components = [];
|
|
70
|
+
let current = '';
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let activeQuote = '';
|
|
73
|
+
let index = 0;
|
|
74
|
+
|
|
75
|
+
while (index < value.length) {
|
|
76
|
+
const character = value[index];
|
|
77
|
+
|
|
78
|
+
if (activeQuote) {
|
|
79
|
+
current += character;
|
|
80
|
+
if (character === '\\') {
|
|
81
|
+
current += value[index + 1] ?? '';
|
|
82
|
+
index += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (character === activeQuote) {
|
|
86
|
+
activeQuote = '';
|
|
87
|
+
}
|
|
88
|
+
index++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (character === '"' || character === '\'') {
|
|
93
|
+
activeQuote = character;
|
|
94
|
+
current += character;
|
|
95
|
+
index++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (character === '(') {
|
|
100
|
+
depth++;
|
|
101
|
+
}
|
|
102
|
+
if (character === ')' && depth > 0) {
|
|
103
|
+
depth--;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Match any whitespace character, which separates components at depth zero
|
|
107
|
+
const isSeparator = depth === 0 && /\s/.test(character);
|
|
108
|
+
if (isSeparator) {
|
|
109
|
+
if (current) {
|
|
110
|
+
components.push(current);
|
|
111
|
+
current = '';
|
|
112
|
+
}
|
|
113
|
+
index++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const startsHashToken = character === '#' && depth === 0 && current !== '';
|
|
118
|
+
if (startsHashToken) {
|
|
119
|
+
components.push(current);
|
|
120
|
+
current = '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
current += character;
|
|
124
|
+
index++;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (current) {
|
|
128
|
+
components.push(current);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return components;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export {
|
|
135
|
+
findMatchingParenthesis,
|
|
136
|
+
splitTopLevelComponents
|
|
137
|
+
};
|