@thejaredwilcurt/csslop 0.0.26 → 0.0.27
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/package.json +2 -2
- package/src/context.js +4 -3
- package/src/declarations/border.js +12 -3
- package/src/declarations/config.js +1 -1
- package/src/declarations/css-wide-keywords.js +11 -5
- package/src/declarations/order.js +25 -19
- package/src/declarations/process.js +9 -2
- package/src/declarations/reset-hazards.js +108 -0
- package/src/index.js +5 -0
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.27",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/prestart.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@eslint/js": "^10.0.1",
|
|
32
32
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
33
33
|
"codemirror": "^6.0.2",
|
|
34
|
-
"eslint": "^10.9.
|
|
34
|
+
"eslint": "^10.9.1",
|
|
35
35
|
"eslint-config-tjw-base": "^5.0.0",
|
|
36
36
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
37
37
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
package/src/context.js
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
import { isUnicodeCompatibleCharset } from './charset.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Creates a fresh minification context used to track `@property`-registered custom properties
|
|
8
|
+
* Creates a fresh minification context used to track `@property`-registered custom properties, their declared syntax types, and the properties that a newly assembled shorthand must not silently reset, across the entire stylesheet.
|
|
9
9
|
*
|
|
10
|
-
* @return {object} A context object with a registeredCustomProperties Set and a
|
|
10
|
+
* @return {object} A context object with a registeredCustomProperties Set, a registeredCustomPropertySyntax Map, and a stylesheetResetProperties Set.
|
|
11
11
|
*/
|
|
12
12
|
function createMinifyContext () {
|
|
13
13
|
return {
|
|
14
14
|
registeredCustomProperties: new Set(),
|
|
15
|
-
registeredCustomPropertySyntax: new Map()
|
|
15
|
+
registeredCustomPropertySyntax: new Map(),
|
|
16
|
+
stylesheetResetProperties: new Set()
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -6,6 +6,8 @@ import { minifyValue } from '../value/minify.js';
|
|
|
6
6
|
import { splitTopLevelComponents } from '../value/syntax.js';
|
|
7
7
|
|
|
8
8
|
import { CSS_WIDE_KEYWORDS } from './config.js';
|
|
9
|
+
import { collectDeclaredProperties } from './lookup.js';
|
|
10
|
+
import { resetsPropertyDeclaredElsewhere } from './reset-hazards.js';
|
|
9
11
|
|
|
10
12
|
const BORDER_TRIO_PROPERTIES = ['border-width', 'border-style', 'border-color'];
|
|
11
13
|
|
|
@@ -32,10 +34,17 @@ function findLastDeclarationIndex (declarations, property) {
|
|
|
32
34
|
* by a `border-color` override that restores the per-edge colors. The rewrite is
|
|
33
35
|
* only applied when the resulting pair is shorter than the three longhands.
|
|
34
36
|
*
|
|
35
|
-
* @param {Array}
|
|
36
|
-
* @
|
|
37
|
+
* @param {Array} declarations The declarations of a single rule.
|
|
38
|
+
* @param {object} context The minification context with the stylesheet's reset properties.
|
|
39
|
+
* @return {Array} The declarations, with the trio rewritten when it is shorter.
|
|
37
40
|
*/
|
|
38
|
-
function collapseBorderTrioWithPerEdgeColor (declarations) {
|
|
41
|
+
function collapseBorderTrioWithPerEdgeColor (declarations, context) {
|
|
42
|
+
// The `border` shorthand also resets `border-image`, so the trio stays as it
|
|
43
|
+
// is when another rule of the stylesheet relies on that value.
|
|
44
|
+
if (resetsPropertyDeclaredElsewhere('border', collectDeclaredProperties(declarations), context)) {
|
|
45
|
+
return declarations;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
const trioIndexes = BORDER_TRIO_PROPERTIES.map((property) => {
|
|
40
49
|
return findLastDeclarationIndex(declarations, property);
|
|
41
50
|
});
|
|
@@ -90,7 +90,7 @@ const shorthandMap = {
|
|
|
90
90
|
const shorthandOverrideMap = {
|
|
91
91
|
animation: ['animation-timeline', 'animation-range', 'animation-range-start', 'animation-range-end'],
|
|
92
92
|
border: ['border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat'],
|
|
93
|
-
font: ['font-variant', 'font-variant-alternates', 'font-variant-caps', 'font-variant-east-asian', 'font-variant-ligatures', 'font-variant-numeric', 'font-variant-position'],
|
|
93
|
+
font: ['font-variant', 'font-variant-alternates', 'font-variant-caps', 'font-variant-east-asian', 'font-variant-ligatures', 'font-variant-numeric', 'font-variant-position', 'font-feature-settings', 'font-kerning', 'font-language-override', 'font-optical-sizing', 'font-size-adjust', 'font-variation-settings'],
|
|
94
94
|
mask: ['mask-border', 'mask-border-source', 'mask-border-slice', 'mask-border-width', 'mask-border-outset', 'mask-border-repeat', 'mask-border-mode']
|
|
95
95
|
};
|
|
96
96
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
collectDeclaredProperties,
|
|
14
14
|
describeDeclaration
|
|
15
15
|
} from './lookup.js';
|
|
16
|
+
import { resetsPropertyDeclaredElsewhere } from './reset-hazards.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Checks whether a group of longhands sets every leaf longhand that the
|
|
@@ -153,12 +154,16 @@ function resetsEarlierDeclaration (declarations, shorthandName, insertionIndex)
|
|
|
153
154
|
* @param {Array} declarations The declarations of a single rule.
|
|
154
155
|
* @param {string} shorthandName The target shorthand property name.
|
|
155
156
|
* @param {Set} declaredProperties The property names the rule currently declares.
|
|
157
|
+
* @param {object} context The minification context with the stylesheet's reset properties.
|
|
156
158
|
* @return {Array|null} The rewritten declarations, or null when the rewrite does not apply.
|
|
157
159
|
*/
|
|
158
|
-
function rewriteGroupAsKeywordShorthand (declarations, shorthandName, declaredProperties) {
|
|
160
|
+
function rewriteGroupAsKeywordShorthand (declarations, shorthandName, declaredProperties, context) {
|
|
159
161
|
if (declaredProperties.has(shorthandName)) {
|
|
160
162
|
return null;
|
|
161
163
|
}
|
|
164
|
+
if (resetsPropertyDeclaredElsewhere(shorthandName, declaredProperties, context)) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
162
167
|
|
|
163
168
|
const entries = collectLonghandEntries(declarations, shorthandName);
|
|
164
169
|
if (entries.length < 2 || hasRepeatedProperty(entries)) {
|
|
@@ -230,10 +235,11 @@ function rewriteGroupAsKeywordShorthand (declarations, shorthandName, declaredPr
|
|
|
230
235
|
* followed by `border-width:2px`, which inherits every border property and then
|
|
231
236
|
* overrides the one that differs.
|
|
232
237
|
*
|
|
233
|
-
* @param {Array}
|
|
234
|
-
* @
|
|
238
|
+
* @param {Array} declarations The declarations of a single rule.
|
|
239
|
+
* @param {object} context The minification context with the stylesheet's reset properties.
|
|
240
|
+
* @return {Array} The declarations, with eligible groups rewritten.
|
|
235
241
|
*/
|
|
236
|
-
function hoistCssWideKeywordsIntoShorthands (declarations) {
|
|
242
|
+
function hoistCssWideKeywordsIntoShorthands (declarations, context) {
|
|
237
243
|
let result = declarations;
|
|
238
244
|
// Every shorthand needs to know which properties the rule declares, so that
|
|
239
245
|
// set is kept alongside the declarations and only rebuilt after a rewrite
|
|
@@ -242,7 +248,7 @@ function hoistCssWideKeywordsIntoShorthands (declarations) {
|
|
|
242
248
|
// Shorthands are visited in declaration order, so the widest shorthand of a
|
|
243
249
|
// family is rewritten before the narrower shorthands it contains.
|
|
244
250
|
for (const shorthandName of Object.keys(shorthandMap)) {
|
|
245
|
-
const rewritten = rewriteGroupAsKeywordShorthand(result, shorthandName, declaredProperties);
|
|
251
|
+
const rewritten = rewriteGroupAsKeywordShorthand(result, shorthandName, declaredProperties, context);
|
|
246
252
|
if (rewritten) {
|
|
247
253
|
result = rewritten;
|
|
248
254
|
declaredProperties = collectDeclaredProperties(result);
|
|
@@ -9,28 +9,34 @@ import {
|
|
|
9
9
|
import { collectDeclaredProperties } from './lookup.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* the first resets the second
|
|
14
|
-
* longhands it was built from.
|
|
12
|
+
* Builds the pairs of properties where the first has to be emitted before the
|
|
13
|
+
* second, because the first resets the second while a merged shorthand is
|
|
14
|
+
* appended after the longhands it was built from. Every property a shorthand
|
|
15
|
+
* resets but cannot express has to be restated after that shorthand, and a
|
|
16
|
+
* `margin` built from a mixed `!important` group leaves its important longhands
|
|
17
|
+
* behind for the same reason.
|
|
18
|
+
*
|
|
19
|
+
* @return {Array} The property pairs, each as a shorthand followed by the property it resets.
|
|
20
|
+
*/
|
|
21
|
+
function buildRequiredPropertyOrder () {
|
|
22
|
+
const orderedPairs = [];
|
|
23
|
+
for (const [shorthandProperty, resetProperties] of Object.entries(shorthandOverrideMap)) {
|
|
24
|
+
for (const resetProperty of resetProperties) {
|
|
25
|
+
orderedPairs.push([shorthandProperty, resetProperty]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
for (const longhandProperty of shorthandMap.margin) {
|
|
29
|
+
orderedPairs.push(['margin', longhandProperty]);
|
|
30
|
+
}
|
|
31
|
+
return orderedPairs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The property pairs whose relative order the output has to correct.
|
|
15
36
|
*
|
|
16
37
|
* @type {Array}
|
|
17
38
|
*/
|
|
18
|
-
const REQUIRED_PROPERTY_ORDER =
|
|
19
|
-
['animation', 'animation-timeline'],
|
|
20
|
-
['animation', 'animation-range'],
|
|
21
|
-
['animation', 'animation-range-start'],
|
|
22
|
-
['animation', 'animation-range-end'],
|
|
23
|
-
['border', 'border-image'],
|
|
24
|
-
['font', 'font-feature-settings'],
|
|
25
|
-
['font', 'font-variant-ligatures'],
|
|
26
|
-
['font', 'font-kerning'],
|
|
27
|
-
['font', 'font-variation-settings'],
|
|
28
|
-
['mask', 'mask-border'],
|
|
29
|
-
['margin', 'margin-top'],
|
|
30
|
-
['margin', 'margin-right'],
|
|
31
|
-
['margin', 'margin-bottom'],
|
|
32
|
-
['margin', 'margin-left']
|
|
33
|
-
];
|
|
39
|
+
const REQUIRED_PROPERTY_ORDER = buildRequiredPropertyOrder();
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* Reorders declarations so that shorthands appear before any related longhands they would override, preventing cascade issues in the minified output.
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
getOverriddenLonghands,
|
|
26
26
|
orderDeclarations
|
|
27
27
|
} from './order.js';
|
|
28
|
+
import { resetsPropertyDeclaredElsewhere } from './reset-hazards.js';
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Shorthands that keep their non-important longhands in the output, so a mixed
|
|
@@ -348,6 +349,12 @@ function mergeLonghandsIntoShorthands (declarations, context) {
|
|
|
348
349
|
if (declaredProperties.has(shorthand)) {
|
|
349
350
|
continue;
|
|
350
351
|
}
|
|
352
|
+
// Assembling a shorthand also resets the properties it cannot express, so
|
|
353
|
+
// a rule only collapses into one when nothing else in the stylesheet
|
|
354
|
+
// relies on a value that reset would discard.
|
|
355
|
+
if (resetsPropertyDeclaredElsewhere(shorthand, declaredProperties, context)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
351
358
|
|
|
352
359
|
const mergeableProperties = getMergeProps(shorthand, longhands, declaredProperties);
|
|
353
360
|
if (!mergeableProperties) {
|
|
@@ -398,8 +405,8 @@ function processDeclarations (declarations, context) {
|
|
|
398
405
|
result = absorbBackgroundLonghandsIntoShorthand(result);
|
|
399
406
|
result = mergeLonghandsIntoShorthands(result, context);
|
|
400
407
|
result = foldLonghandOverridesIntoShorthands(result, context);
|
|
401
|
-
result = hoistCssWideKeywordsIntoShorthands(result);
|
|
402
|
-
result = collapseBorderTrioWithPerEdgeColor(result);
|
|
408
|
+
result = hoistCssWideKeywordsIntoShorthands(result, context);
|
|
409
|
+
result = collapseBorderTrioWithPerEdgeColor(result, context);
|
|
403
410
|
|
|
404
411
|
return orderDeclarations(result);
|
|
405
412
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tracks the properties a shorthand resets without being able to express them, so that assembling a shorthand out of longhands never cancels a value another rule of the stylesheet set.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getOverridesOf,
|
|
7
|
+
shorthandOverrideMap
|
|
8
|
+
} from './config.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Collects every property that some shorthand resets without being able to
|
|
12
|
+
* express it, such as `border-image` for `border`. Only these properties can
|
|
13
|
+
* lose their value to a newly assembled shorthand, so only these are worth
|
|
14
|
+
* tracking across the stylesheet.
|
|
15
|
+
*
|
|
16
|
+
* @return {Set} The property names a shorthand resets but cannot express.
|
|
17
|
+
*/
|
|
18
|
+
function collectResettableProperties () {
|
|
19
|
+
const resettableProperties = new Set();
|
|
20
|
+
for (const resetProperties of Object.values(shorthandOverrideMap)) {
|
|
21
|
+
for (const property of resetProperties) {
|
|
22
|
+
resettableProperties.add(property);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return resettableProperties;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The properties a shorthand resets but cannot express. The shorthand tables
|
|
30
|
+
* never change, so the set is built once.
|
|
31
|
+
*
|
|
32
|
+
* @type {Set<string>}
|
|
33
|
+
*/
|
|
34
|
+
const RESETTABLE_PROPERTIES = collectResettableProperties();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Collects the rules nested inside a rule. The parser stores the children of an
|
|
38
|
+
* at-rule as a list of rules, and the children of a style rule that uses CSS
|
|
39
|
+
* nesting as declarations that carry rules of their own.
|
|
40
|
+
*
|
|
41
|
+
* @param {object} rule The AST rule node to look inside.
|
|
42
|
+
* @return {Array} The rule nodes nested within it.
|
|
43
|
+
*/
|
|
44
|
+
function collectNestedRules (rule) {
|
|
45
|
+
const nestedRules = [...(rule.rules || [])];
|
|
46
|
+
for (const declaration of rule.declarations || []) {
|
|
47
|
+
if (declaration.rules || declaration.declarations) {
|
|
48
|
+
nestedRules.push(declaration);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return nestedRules;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Records which resettable properties the stylesheet declares, in any rule at
|
|
56
|
+
* any nesting depth, so that each rule can later ask whether assembling a
|
|
57
|
+
* shorthand would cancel a value another rule set.
|
|
58
|
+
*
|
|
59
|
+
* @param {Array} rules The AST rule nodes of the whole stylesheet.
|
|
60
|
+
* @param {object} context The minification context to populate.
|
|
61
|
+
*/
|
|
62
|
+
function recordStylesheetResetProperties (rules, context) {
|
|
63
|
+
const pendingRules = [...rules];
|
|
64
|
+
while (pendingRules.length) {
|
|
65
|
+
const rule = pendingRules.pop();
|
|
66
|
+
for (const declaration of rule.declarations || []) {
|
|
67
|
+
if (RESETTABLE_PROPERTIES.has(declaration.property)) {
|
|
68
|
+
context.stylesheetResetProperties.add(declaration.property);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
pendingRules.push(...collectNestedRules(rule));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Checks whether assembling a shorthand out of longhands would reset a property
|
|
77
|
+
* that the stylesheet sets somewhere else. The `border` shorthand resets
|
|
78
|
+
* `border-image`, so turning the border longhands of one rule into `border`
|
|
79
|
+
* cancels the `border-image` that another rule sets on the same element. A rule
|
|
80
|
+
* that states the reset property itself is safe, because that declaration is
|
|
81
|
+
* emitted after the shorthand and restates the value the shorthand discarded.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} shorthandName The shorthand that would be assembled.
|
|
84
|
+
* @param {Set} declaredProperties The property names the rule declares.
|
|
85
|
+
* @param {object} context The minification context with the stylesheet's reset properties.
|
|
86
|
+
* @return {boolean} Whether assembling the shorthand would discard another rule's value.
|
|
87
|
+
*/
|
|
88
|
+
function resetsPropertyDeclaredElsewhere (shorthandName, declaredProperties, context) {
|
|
89
|
+
const stylesheetResetProperties = context?.stylesheetResetProperties;
|
|
90
|
+
if (!stylesheetResetProperties?.size) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
for (const resetProperty of getOverridesOf(shorthandName)) {
|
|
94
|
+
const isSetElsewhere = (
|
|
95
|
+
stylesheetResetProperties.has(resetProperty) &&
|
|
96
|
+
!declaredProperties.has(resetProperty)
|
|
97
|
+
);
|
|
98
|
+
if (isSetElsewhere) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
recordStylesheetResetProperties,
|
|
107
|
+
resetsPropertyDeclaredElsewhere
|
|
108
|
+
};
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
createMinifyContext,
|
|
14
14
|
setActiveCharset
|
|
15
15
|
} from './context.js';
|
|
16
|
+
import { recordStylesheetResetProperties } from './declarations/reset-hazards.js';
|
|
16
17
|
import {
|
|
17
18
|
analyzePositionTryRules,
|
|
18
19
|
cleanPositionTryRules,
|
|
@@ -231,6 +232,10 @@ export const minifyCSS = function (input) {
|
|
|
231
232
|
const context = createMinifyContext();
|
|
232
233
|
|
|
233
234
|
if (ast?.stylesheet?.rules) {
|
|
235
|
+
// Which properties a shorthand may not silently reset is a question about
|
|
236
|
+
// the whole stylesheet, so it is answered before any rule is rewritten.
|
|
237
|
+
recordStylesheetResetProperties(ast.stylesheet.rules, context);
|
|
238
|
+
|
|
234
239
|
const {
|
|
235
240
|
positionTryRules,
|
|
236
241
|
positionTryUsage
|