@thejaredwilcurt/csslop 0.0.55 → 0.0.56
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
CHANGED
|
@@ -23,6 +23,127 @@ function removeIEMediaRules (rules) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Matches the media type keyword that opens a normalized `@media` query, such as `screen` in `screen and (width>=768px)`.
|
|
28
|
+
*
|
|
29
|
+
* @type {RegExp}
|
|
30
|
+
*/
|
|
31
|
+
const LEADING_MEDIA_TYPE_PATTERN = /^[a-z-]+(?=\s|$)/i;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Matches one parenthesized media condition, allowing one level of nested parentheses such as `(not (color))`.
|
|
35
|
+
*
|
|
36
|
+
* @type {RegExp}
|
|
37
|
+
*/
|
|
38
|
+
const PARENTHESIZED_CONDITION_PATTERN = /\((?:[^()]|\([^()]*\))*\)/g;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Splits a `@media` query into the media type it names and the conditions it adds, so that two queries can be joined with `and`. Queries that use a list, `not`, or `only` cannot be joined this way.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} media The raw `@media` query string.
|
|
44
|
+
* @return {object|null} The `type` (null when none applies) and `conditions` text, or null when the query cannot be joined.
|
|
45
|
+
*/
|
|
46
|
+
function parseJoinableMediaQuery (media) {
|
|
47
|
+
const normalized = normalizeMedia(media);
|
|
48
|
+
const typeText = normalized.match(LEADING_MEDIA_TYPE_PATTERN)?.[0] ?? '';
|
|
49
|
+
const typeName = typeText.toLowerCase();
|
|
50
|
+
if (normalized.includes(',') || typeName === 'not' || typeName === 'only') {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
// Drop the `and` that separates the media type from the conditions
|
|
54
|
+
const conditions = normalized.slice(typeText.length).replace(/^\s*and\s*/i, '');
|
|
55
|
+
// Anything left once the parenthesized conditions and `and` joiners are removed is an unsupported operator
|
|
56
|
+
const unparsedText = conditions.replace(PARENTHESIZED_CONDITION_PATTERN, '').replace(/\band\b/gi, '').trim();
|
|
57
|
+
if (unparsedText) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
// `all` matches every document, so it adds no restriction of its own
|
|
62
|
+
type: typeName && typeName !== 'all' ? typeName : null,
|
|
63
|
+
conditions
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reports whether two queries name different media types. A document has only one media type, so a block requiring both can never match.
|
|
69
|
+
*
|
|
70
|
+
* @param {object|null} outer The parsed enclosing query, from `parseJoinableMediaQuery`.
|
|
71
|
+
* @param {object|null} inner The parsed nested query, from `parseJoinableMediaQuery`.
|
|
72
|
+
* @return {boolean} True when the two queries name different media types.
|
|
73
|
+
*/
|
|
74
|
+
function areExclusiveMediaTypes (outer, inner) {
|
|
75
|
+
return Boolean(outer?.type && inner?.type && outer.type !== inner.type);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Joins a `@media` block nested directly inside another into the single query that matches the same documents, such as `screen and (width>=768px)`. A media type both queries share is written once.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} outerMedia The raw query of the enclosing `@media` block.
|
|
82
|
+
* @param {string} innerMedia The raw query of the nested `@media` block.
|
|
83
|
+
* @return {string|null} The joined query, or null when the pair must stay nested.
|
|
84
|
+
*/
|
|
85
|
+
function joinNestedMediaQueries (outerMedia, innerMedia) {
|
|
86
|
+
const outer = parseJoinableMediaQuery(outerMedia);
|
|
87
|
+
const inner = parseJoinableMediaQuery(innerMedia);
|
|
88
|
+
if (!outer || !inner || areExclusiveMediaTypes(outer, inner)) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const parts = [outer.type || inner.type, outer.conditions, inner.conditions].filter(Boolean);
|
|
92
|
+
return parts.join(' and ') || 'all';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reports whether an entry of a rule list is real content rather than the whitespace between entries.
|
|
97
|
+
*
|
|
98
|
+
* @param {object} entry An entry of a rule list.
|
|
99
|
+
* @return {boolean} True when the entry is not whitespace.
|
|
100
|
+
*/
|
|
101
|
+
function isContentEntry (entry) {
|
|
102
|
+
return entry.type !== 'whitespace';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Simplifies one `@media` block. A block whose only content is a single nested `@media` block is joined into one query, and the simplification repeats on the result. Nested `@media` blocks whose media type the block already excludes are removed.
|
|
107
|
+
*
|
|
108
|
+
* @param {object} rule The `@media` rule node to simplify.
|
|
109
|
+
* @return {object} A new `@media` rule node.
|
|
110
|
+
*/
|
|
111
|
+
function simplifyMediaBlock (rule) {
|
|
112
|
+
const children = rule.rules || [];
|
|
113
|
+
const content = children.filter(isContentEntry);
|
|
114
|
+
if (content.length === 1 && content[0].type === 'media') {
|
|
115
|
+
const joinedMedia = joinNestedMediaQueries(rule.media, content[0].media);
|
|
116
|
+
if (joinedMedia !== null) {
|
|
117
|
+
return simplifyMediaBlock({ ...content[0], media: joinedMedia });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const outerQuery = parseJoinableMediaQuery(rule.media);
|
|
121
|
+
return {
|
|
122
|
+
...rule,
|
|
123
|
+
rules: children.filter((child) => {
|
|
124
|
+
return !(child.type === 'media' && areExclusiveMediaTypes(outerQuery, parseJoinableMediaQuery(child.media)));
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Simplifies the `@media` blocks in a rule list, dropping any block whose content was removed entirely as dead.
|
|
131
|
+
*
|
|
132
|
+
* @param {Array} rules The AST rule nodes to process.
|
|
133
|
+
* @return {Array} A new array of rules with nested `@media` blocks simplified.
|
|
134
|
+
*/
|
|
135
|
+
function simplifyNestedMediaRules (rules) {
|
|
136
|
+
return rules.flatMap((rule) => {
|
|
137
|
+
if (rule.type !== 'media') {
|
|
138
|
+
return [rule];
|
|
139
|
+
}
|
|
140
|
+
const simplified = simplifyMediaBlock(rule);
|
|
141
|
+
const hadContent = (rule.rules || []).some(isContentEntry);
|
|
142
|
+
const isEmptiedByDeadContent = hadContent && !simplified.rules.some(isContentEntry);
|
|
143
|
+
return isEmptiedByDeadContent ? [] : [simplified];
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
26
147
|
/**
|
|
27
148
|
* Merges adjacent `@media` rules that share an identical normalized query string and deduplicates their child selector rules.
|
|
28
149
|
*
|
|
@@ -126,5 +247,6 @@ export {
|
|
|
126
247
|
deduplicateKeyframes,
|
|
127
248
|
mergeLayerRules,
|
|
128
249
|
mergeMediaRules,
|
|
129
|
-
removeIEMediaRules
|
|
250
|
+
removeIEMediaRules,
|
|
251
|
+
simplifyNestedMediaRules
|
|
130
252
|
};
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
deduplicateKeyframes,
|
|
7
7
|
mergeLayerRules,
|
|
8
8
|
mergeMediaRules,
|
|
9
|
-
removeIEMediaRules
|
|
9
|
+
removeIEMediaRules,
|
|
10
|
+
simplifyNestedMediaRules
|
|
10
11
|
} from './at-rules.js';
|
|
11
12
|
import { removeEmptyRules } from './empty-rules.js';
|
|
12
13
|
import { expandPureNestedRules } from './expand-nesting.js';
|
|
@@ -45,6 +46,7 @@ function clearSelectorCaches () {
|
|
|
45
46
|
function optimizeRuleList (rules) {
|
|
46
47
|
let result = expandPureNestedRules(rules);
|
|
47
48
|
result = removeIEMediaRules(result);
|
|
49
|
+
result = simplifyNestedMediaRules(result);
|
|
48
50
|
result = mergeLayerRules(result, mergeSelectorRules);
|
|
49
51
|
result = mergeMediaRules(result, mergeSelectorRules);
|
|
50
52
|
result = deduplicateKeyframes(result);
|