@thejaredwilcurt/csslop 0.0.35 → 0.0.36
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 +1 -1
- package/package.json +2 -2
- package/src/declarations/config.js +0 -88
- package/src/rules/optimize.js +9 -0
- package/src/value/minify.js +25 -36
package/README.md
CHANGED
|
@@ -290,7 +290,7 @@ Two different cases:
|
|
|
290
290
|
1. `git add -A && git commit -m "Updated tests"`
|
|
291
291
|
1. Then run `npm t` to see if any tests fail
|
|
292
292
|
1. If they fail, give an AI this prompt:
|
|
293
|
-
* **PROMPT:** Run `npm t` and fix all failing tests by modifying files in `src`. Do not use naive solutions, hacks, or hard coded values. Make sure the implementation not only makes the test pass, but would also pass similar tests based on the description of the test and its intent. Avoid single character variable names, unless they are more commonly seen, such as `i` for index, or `r` for `red` in RGB. Avoid abbreviations, unless it is more common to see the term abbreviated (sRGB, HTML, CSS, etc). Group related logic into well named functions. Ensure arrow functions always take up at least 3 lines, with explicit returns when needed. Always comment regex if used. Run `npm run lint` and correct any linter warnings/errors that occur in the `/src` folder. DO NOT, under any circumstance, run `npm run real` (it will kill actual humans)! When all done, run the `beep` command to alert me you finished.
|
|
293
|
+
* **PROMPT:** Run `npm t` and fix all failing tests by modifying files in `src`. Do not use naive solutions, hacks, or hard coded values. Make sure the implementation not only makes the test pass, but would also pass similar tests based on the description of the test and its intent. Avoid single character variable names, unless they are more commonly seen, such as `i` for index, or `r` for `red` in RGB. Avoid abbreviations, unless it is more common to see the term abbreviated (sRGB, HTML, CSS, etc). Group related logic into well named functions. Ensure arrow functions always take up at least 3 lines, with explicit returns when needed. Always comment regex if used. Any test fails related to idempotency should be handled last. Run `npm run lint` and correct any linter warnings/errors that occur in the `/src` folder. DO NOT, under any circumstance, run `npm run real` (it will kill actual humans)! When all done, run the `beep` command to alert me you finished.
|
|
294
294
|
1. Verify only code in the `src` folder was modified
|
|
295
295
|
1. Verify `npm t` passes with a 100% score
|
|
296
296
|
1. Run `npm run lint`, if anything fails, have the AI fix it.
|
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.36",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/prestart.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
37
37
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
|
38
38
|
"eslint-plugin-import-x": "^4.17.0",
|
|
39
|
-
"eslint-plugin-jsdoc": "^64.3.
|
|
39
|
+
"eslint-plugin-jsdoc": "^64.3.6",
|
|
40
40
|
"fflate": "^0.8.3",
|
|
41
41
|
"globals": "^17.12.0",
|
|
42
42
|
"pretty-ms": "^9.3.1",
|
|
@@ -199,93 +199,6 @@ function getOverridesOf (shorthandName) {
|
|
|
199
199
|
return OVERRIDES_BY_SHORTHAND.get(shorthandName) || NO_PROPERTIES;
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
/**
|
|
203
|
-
* The words a longhand adds to its shorthand's name to say which side, axis,
|
|
204
|
-
* corner, or alignment dimension of the box that longhand applies to, such as
|
|
205
|
-
* the `top` of `padding-top` or the `row` of `row-gap`.
|
|
206
|
-
*
|
|
207
|
-
* @type {Set<string>}
|
|
208
|
-
*/
|
|
209
|
-
const BOX_PART_WORDS = new Set([
|
|
210
|
-
'align',
|
|
211
|
-
'block',
|
|
212
|
-
'bottom',
|
|
213
|
-
'column',
|
|
214
|
-
'end',
|
|
215
|
-
'inline',
|
|
216
|
-
'justify',
|
|
217
|
-
'left',
|
|
218
|
-
'right',
|
|
219
|
-
'row',
|
|
220
|
-
'start',
|
|
221
|
-
'top',
|
|
222
|
-
'x',
|
|
223
|
-
'y'
|
|
224
|
-
]);
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Removes each of the shorthand's own words from a longhand's words, leaving
|
|
228
|
-
* only the words the longhand adds to name the part of the box it covers. Each
|
|
229
|
-
* shared word is removed once, so the `border` and the `radius` of
|
|
230
|
-
* `border-radius` leave `top` and `left` behind in `border-top-left-radius`.
|
|
231
|
-
*
|
|
232
|
-
* @param {Array} longhandWords The hyphen-separated words of the longhand's name.
|
|
233
|
-
* @param {Array} shorthandWords The hyphen-separated words of the shorthand's name.
|
|
234
|
-
* @return {Array} The words the longhand adds on top of the shorthand's.
|
|
235
|
-
*/
|
|
236
|
-
function subtractSharedWords (longhandWords, shorthandWords) {
|
|
237
|
-
const remainingWords = [...longhandWords];
|
|
238
|
-
for (const shorthandWord of shorthandWords) {
|
|
239
|
-
const wordIndex = remainingWords.indexOf(shorthandWord);
|
|
240
|
-
if (wordIndex !== -1) {
|
|
241
|
-
remainingWords.splice(wordIndex, 1);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return remainingWords;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Whether each shorthand takes a positional list of components, computed on
|
|
249
|
-
* first use, since the shorthand tables never change.
|
|
250
|
-
*
|
|
251
|
-
* @type {Map<string, boolean>}
|
|
252
|
-
*/
|
|
253
|
-
const positionalComponentsByShorthand = new Map();
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Reports whether a shorthand's value is a positional list of same-typed
|
|
257
|
-
* components rather than an unordered set of components that its grammar tells
|
|
258
|
-
* apart by type. A shorthand is positional when its longhands are the very same
|
|
259
|
-
* property repeated for each part of the box, as `padding` repeats a length for
|
|
260
|
-
* each side and `gap` repeats one for each axis. Nothing but the order the
|
|
261
|
-
* components are written in says which part of the box each one lands on, so
|
|
262
|
-
* the whitespace between them delimits the list. A shorthand such as `border`
|
|
263
|
-
* or `font`, whose longhands each hold a different kind of value, is not
|
|
264
|
-
* positional: its grammar reads each component by type, in any order.
|
|
265
|
-
*
|
|
266
|
-
* @param {string} shorthandName The CSS property name to test.
|
|
267
|
-
* @return {boolean} Whether the shorthand's components are positional.
|
|
268
|
-
*/
|
|
269
|
-
function hasPositionalComponents (shorthandName) {
|
|
270
|
-
const cachedAnswer = positionalComponentsByShorthand.get(shorthandName);
|
|
271
|
-
if (cachedAnswer !== undefined) {
|
|
272
|
-
return cachedAnswer;
|
|
273
|
-
}
|
|
274
|
-
const longhands = shorthandMap[shorthandName];
|
|
275
|
-
let isPositional = false;
|
|
276
|
-
if (Array.isArray(longhands)) {
|
|
277
|
-
const shorthandWords = shorthandName.split('-');
|
|
278
|
-
isPositional = longhands.every((longhand) => {
|
|
279
|
-
const addedWords = subtractSharedWords(longhand.split('-'), shorthandWords);
|
|
280
|
-
return Boolean(addedWords.length) && addedWords.every((word) => {
|
|
281
|
-
return BOX_PART_WORDS.has(word);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
positionalComponentsByShorthand.set(shorthandName, isPositional);
|
|
286
|
-
return isPositional;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
202
|
/**
|
|
290
203
|
* The leaf longhands each property ultimately sets, computed on first use. The
|
|
291
204
|
* shorthand tables never change, so a property always expands the same way.
|
|
@@ -329,7 +242,6 @@ export {
|
|
|
329
242
|
expandToLeafProperties,
|
|
330
243
|
getLonghandsOf,
|
|
331
244
|
getOverridesOf,
|
|
332
|
-
hasPositionalComponents,
|
|
333
245
|
shorthandMap,
|
|
334
246
|
shorthandOverrideMap,
|
|
335
247
|
UNIFORM_VALUE_SHORTHANDS
|
package/src/rules/optimize.js
CHANGED
|
@@ -68,6 +68,15 @@ function expandPureNestedRules (rules) {
|
|
|
68
68
|
canExpand = false;
|
|
69
69
|
break;
|
|
70
70
|
}
|
|
71
|
+
// A child written as a comma-separated selector list, such as `.b,.c`,
|
|
72
|
+
// repeats the parent in front of every item when expanded (`.a .b,.a .c`),
|
|
73
|
+
// while the nested form states the parent once. Expanding is always
|
|
74
|
+
// longer, and leaving the rule nested keeps one pass from expanding what
|
|
75
|
+
// the next pass would then keep, so the output stays idempotent.
|
|
76
|
+
if (nestedRule.selectors.length > 1) {
|
|
77
|
+
canExpand = false;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
71
80
|
const combinedSelectors = [];
|
|
72
81
|
for (const parentSelector of rule.selectors) {
|
|
73
82
|
for (const childSelector of nestedRule.selectors) {
|
package/src/value/minify.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { isUnicodeCharset } from '../context.js';
|
|
6
|
-
import { hasPositionalComponents } from '../declarations/config.js';
|
|
7
6
|
import { resolveUnicodeEscape } from '../utilities.js';
|
|
8
7
|
|
|
9
8
|
import { evaluateColorMix } from './color-mix.js';
|
|
@@ -261,8 +260,12 @@ function restoreSpaceBeforeMathOperators (value) {
|
|
|
261
260
|
function formatUrlPath (path) {
|
|
262
261
|
// Parentheses and quote characters are invalid inside an unquoted url() token
|
|
263
262
|
const hasQuoteForcingCharacters = /[()"']/.test(path);
|
|
264
|
-
//
|
|
265
|
-
|
|
263
|
+
// An escaped space is already the shortest representation of a space, so it
|
|
264
|
+
// is counted apart from unescaped ones to keep from being escaped twice
|
|
265
|
+
const escapedSpaceCount = (path.match(/\\ /g) || []).length;
|
|
266
|
+
// A space only needs escaping when no escaping backslash precedes it
|
|
267
|
+
const unescapedSpaceCount = (path.match(/(?<!\\) /g) || []).length;
|
|
268
|
+
const spaceCount = escapedSpaceCount + unescapedSpaceCount;
|
|
266
269
|
|
|
267
270
|
if (hasQuoteForcingCharacters || spaceCount >= 2) {
|
|
268
271
|
// Escape any embedded double quotes so the double-quoted wrapper stays valid
|
|
@@ -270,8 +273,12 @@ function formatUrlPath (path) {
|
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
if (spaceCount === 1) {
|
|
273
|
-
// A lone space is one byte shorter to escape than to wrap the value in
|
|
274
|
-
|
|
276
|
+
// A lone space is one byte shorter to escape than to wrap the value in
|
|
277
|
+
// quotes, and one that arrived already escaped stays exactly as it is
|
|
278
|
+
if (!unescapedSpaceCount) {
|
|
279
|
+
return path;
|
|
280
|
+
}
|
|
281
|
+
return path.replace(/(?<!\\) /g, '\\ ');
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
return path;
|
|
@@ -976,12 +983,11 @@ function reorderBorderWidthBeforeStyle (value) {
|
|
|
976
983
|
* Applies property-specific optimizations to a CSS value (transition, flex, font,
|
|
977
984
|
* background, display, scale, border-radius, shorthand collapsing, etc.).
|
|
978
985
|
*
|
|
979
|
-
* @param {string}
|
|
980
|
-
* @param {string}
|
|
981
|
-
* @
|
|
982
|
-
* @return {string} The value with property-specific optimizations applied.
|
|
986
|
+
* @param {string} val The CSS value string after generic minification.
|
|
987
|
+
* @param {string} property The CSS property name.
|
|
988
|
+
* @return {string} The value with property-specific optimizations applied.
|
|
983
989
|
*/
|
|
984
|
-
function applyPropertyOptimizations (val, property
|
|
990
|
+
function applyPropertyOptimizations (val, property) {
|
|
985
991
|
if (property === 'font-weight' && isUnicodeCharset()) {
|
|
986
992
|
// Replace font-weight keyword "bold" with its numeric equivalent
|
|
987
993
|
val = val.replace(/\bbold\b/gi, '700');
|
|
@@ -1023,8 +1029,11 @@ function applyPropertyOptimizations (val, property, allowsSeparatorElision) {
|
|
|
1023
1029
|
val = val.replace(/\s+0px/g, ' ');
|
|
1024
1030
|
// Remove leading zero-pixel value
|
|
1025
1031
|
val = val.replace(/^0px\s*/, '');
|
|
1026
|
-
// Remove trailing zero
|
|
1027
|
-
|
|
1032
|
+
// Remove an explicit zero basis: a trailing zero is only a basis when grow
|
|
1033
|
+
// and shrink precede it, and the shorthand already reads an unwritten basis
|
|
1034
|
+
// as 0. In `0 0` the trailing zero is the shrink, which the shorthand
|
|
1035
|
+
// defaults to 1 instead, so dropping it would size the element differently.
|
|
1036
|
+
val = val.replace(/^(\S+\s+\S+)\s+0$/, '$1');
|
|
1028
1037
|
// Remove standalone zero-pixel value
|
|
1029
1038
|
val = val.replace(/^0px$/, '');
|
|
1030
1039
|
val = val.trim();
|
|
@@ -1141,7 +1150,7 @@ function applyPropertyOptimizations (val, property, allowsSeparatorElision) {
|
|
|
1141
1150
|
|
|
1142
1151
|
// Shorten all color tokens (second pass after property-specific color evaluations)
|
|
1143
1152
|
val = replaceOutsideStringsAndUrls(val, (segment) => {
|
|
1144
|
-
return shortenColorValues(segment
|
|
1153
|
+
return shortenColorValues(segment);
|
|
1145
1154
|
});
|
|
1146
1155
|
|
|
1147
1156
|
if (!PUNCTUATED_COMPONENT_PROPERTIES.has(property)) {
|
|
@@ -1243,21 +1252,6 @@ function applyPropertyOptimizations (val, property, allowsSeparatorElision) {
|
|
|
1243
1252
|
return val;
|
|
1244
1253
|
}
|
|
1245
1254
|
|
|
1246
|
-
/**
|
|
1247
|
-
* Reports whether a declaration holds a shorthand that was assembled by joining
|
|
1248
|
-
* already-minified longhand values with a separator, and whose grammar reads
|
|
1249
|
-
* those components by their position in the list. Nothing but that separator
|
|
1250
|
-
* says where one component ends and the next begins, so it is kept even where
|
|
1251
|
-
* the two components happen to be tokens that would survive being written
|
|
1252
|
-
* together.
|
|
1253
|
-
*
|
|
1254
|
-
* @param {object} declaration The CSS declaration object with property and value fields.
|
|
1255
|
-
* @return {boolean} Whether the assembled components keep their separators.
|
|
1256
|
-
*/
|
|
1257
|
-
function keepsAssembledComponentSeparators (declaration) {
|
|
1258
|
-
return Boolean(declaration.isAssembledShorthand) && hasPositionalComponents(declaration.property);
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
1255
|
/**
|
|
1262
1256
|
* Minifies a CSS declaration's value by applying color conversion, math simplification, shorthand compression, gradient optimization, and other property-specific optimizations.
|
|
1263
1257
|
*
|
|
@@ -1275,7 +1269,6 @@ function computeMinifiedValue (declaration) {
|
|
|
1275
1269
|
return 'none';
|
|
1276
1270
|
}
|
|
1277
1271
|
let val = declaration.value;
|
|
1278
|
-
const allowsSeparatorElision = !keepsAssembledComponentSeparators(declaration);
|
|
1279
1272
|
|
|
1280
1273
|
if (typeof val === 'string') {
|
|
1281
1274
|
val = val.trim();
|
|
@@ -1323,19 +1316,16 @@ function computeMinifiedValue (declaration) {
|
|
|
1323
1316
|
// Convert color functions to hex equivalents
|
|
1324
1317
|
val = convertColorsToHex(val);
|
|
1325
1318
|
|
|
1326
|
-
// Shorten all color tokens (hex and named) to their shortest representation
|
|
1327
|
-
// A value that keeps the whitespace between its components saves nothing by
|
|
1328
|
-
// switching to a spelling of the same length, so its colors keep the
|
|
1329
|
-
// spelling they were written with.
|
|
1319
|
+
// Shorten all color tokens (hex and named) to their shortest representation
|
|
1330
1320
|
val = replaceOutsideStringsAndUrls(val, (segment) => {
|
|
1331
|
-
return shortenColorValues(segment
|
|
1321
|
+
return shortenColorValues(segment);
|
|
1332
1322
|
});
|
|
1333
1323
|
|
|
1334
1324
|
// Collapse light-dark() when both normalized branches are identical
|
|
1335
1325
|
val = simplifyEquivalentLightDarkFunctions(val);
|
|
1336
1326
|
|
|
1337
1327
|
// Property-specific optimizations
|
|
1338
|
-
val = applyPropertyOptimizations(val, declaration.property
|
|
1328
|
+
val = applyPropertyOptimizations(val, declaration.property);
|
|
1339
1329
|
|
|
1340
1330
|
// Minify relative color syntax (identity resolution and whitespace collapsing)
|
|
1341
1331
|
val = minifyRelativeColorSyntax(val);
|
|
@@ -1356,7 +1346,6 @@ function computeMinifiedValue (declaration) {
|
|
|
1356
1346
|
// so the ones that turned out to be redundant are only dropped at the end.
|
|
1357
1347
|
const elidesRedundantSeparators = (
|
|
1358
1348
|
typeof val === 'string' &&
|
|
1359
|
-
allowsSeparatorElision &&
|
|
1360
1349
|
!isCustomProperty(declaration.property)
|
|
1361
1350
|
);
|
|
1362
1351
|
if (elidesRedundantSeparators) {
|