postcss-merge-rules 5.0.2 → 5.0.6
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 +9 -14
- package/{dist → src}/index.js +108 -134
- package/{dist → src}/lib/ensureCompatibility.js +48 -79
package/package.json
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "postcss-merge-rules",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.6",
|
|
4
4
|
"description": "Merge CSS rules with PostCSS.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"LICENSE-MIT",
|
|
8
|
-
"
|
|
8
|
+
"src"
|
|
9
9
|
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"prebuild": "del-cli dist",
|
|
12
|
-
"build": "cross-env BABEL_ENV=publish babel src --config-file ../../babel.config.json --out-dir dist --ignore \"**/__tests__/\"",
|
|
13
|
-
"prepublish": "yarn build"
|
|
14
|
-
},
|
|
15
10
|
"keywords": [
|
|
16
11
|
"css",
|
|
17
12
|
"optimise",
|
|
@@ -29,9 +24,8 @@
|
|
|
29
24
|
"dependencies": {
|
|
30
25
|
"browserslist": "^4.16.6",
|
|
31
26
|
"caniuse-api": "^3.0.0",
|
|
32
|
-
"cssnano-utils": "^
|
|
33
|
-
"postcss-selector-parser": "^6.0.5"
|
|
34
|
-
"vendors": "^1.0.3"
|
|
27
|
+
"cssnano-utils": "^3.0.2",
|
|
28
|
+
"postcss-selector-parser": "^6.0.5"
|
|
35
29
|
},
|
|
36
30
|
"bugs": {
|
|
37
31
|
"url": "https://github.com/cssnano/cssnano/issues"
|
|
@@ -40,10 +34,11 @@
|
|
|
40
34
|
"node": "^10 || ^12 || >=14.0"
|
|
41
35
|
},
|
|
42
36
|
"devDependencies": {
|
|
43
|
-
"postcss": "^8.2.15"
|
|
37
|
+
"postcss": "^8.2.15",
|
|
38
|
+
"postcss-discard-comments": "^5.0.3"
|
|
44
39
|
},
|
|
45
40
|
"peerDependencies": {
|
|
46
41
|
"postcss": "^8.2.15"
|
|
47
42
|
},
|
|
48
|
-
"
|
|
49
|
-
}
|
|
43
|
+
"readme": "# [postcss][postcss]-merge-rules\n\n> Merge CSS rules with PostCSS.\n\n## Install\n\nWith [npm](https://npmjs.org/package/postcss-merge-rules) do:\n\n```\nnpm install postcss-merge-rules --save\n```\n\n## Examples\n\nThis module will attempt to merge *adjacent* CSS rules:\n\n### By declarations\n\n#### Input\n\n```css\na {\n color: blue;\n font-weight: bold\n}\n\np {\n color: blue;\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na,p {\n color: blue;\n font-weight: bold\n}\n```\n\n### By selectors\n\n#### Input\n\n```css\na {\n color: blue\n}\n\na {\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na {\n color: blue;\n font-weight: bold\n}\n```\n\n### By partial declarations\n\n#### Input\n\n```css\na {\n font-weight: bold\n}\n\np {\n color: blue;\n font-weight: bold\n}\n```\n\n#### Output\n\n```css\na,p {\n font-weight: bold\n}\n\np {\n color: blue\n}\n```\n\n## Usage\n\nSee the [PostCSS documentation](https://github.com/postcss/postcss#usage) for\nexamples for your environment.\n\n## Contributors\n\nSee [CONTRIBUTORS.md](https://github.com/cssnano/cssnano/blob/master/CONTRIBUTORS.md).\n\n## License\n\nMIT © [Ben Briggs](http://beneb.info)\n\n[postcss]: https://github.com/postcss/postcss\n"
|
|
44
|
+
}
|
package/{dist → src}/index.js
RENAMED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var _cssnanoUtils = require("cssnano-utils");
|
|
11
|
-
|
|
12
|
-
var _ensureCompatibility = require("./lib/ensureCompatibility");
|
|
13
|
-
|
|
14
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
1
|
+
'use strict';
|
|
2
|
+
const browserslist = require('browserslist');
|
|
3
|
+
const { sameParent } = require('cssnano-utils');
|
|
4
|
+
const {
|
|
5
|
+
ensureCompatibility,
|
|
6
|
+
sameVendor,
|
|
7
|
+
noVendor,
|
|
8
|
+
} = require('./lib/ensureCompatibility');
|
|
15
9
|
|
|
16
10
|
/**
|
|
17
11
|
* @param {postcss.Declaration} a
|
|
@@ -19,18 +13,20 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
19
13
|
* @return {boolean}
|
|
20
14
|
*/
|
|
21
15
|
function declarationIsEqual(a, b) {
|
|
22
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
a.important === b.important && a.prop === b.prop && a.value === b.value
|
|
18
|
+
);
|
|
23
19
|
}
|
|
20
|
+
|
|
24
21
|
/**
|
|
25
22
|
* @param {postcss.Declaration[]} array
|
|
26
23
|
* @param {postcss.Declaration} decl
|
|
27
24
|
* @return {number}
|
|
28
25
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
26
|
function indexOfDeclaration(array, decl) {
|
|
32
|
-
return array.findIndex(d => declarationIsEqual(d, decl));
|
|
27
|
+
return array.findIndex((d) => declarationIsEqual(d, decl));
|
|
33
28
|
}
|
|
29
|
+
|
|
34
30
|
/**
|
|
35
31
|
* Returns filtered array of matched or unmatched declarations
|
|
36
32
|
* @param {postcss.Declaration[]} a
|
|
@@ -38,189 +34,167 @@ function indexOfDeclaration(array, decl) {
|
|
|
38
34
|
* @param {boolean} [not=false]
|
|
39
35
|
* @return {postcss.Declaration[]}
|
|
40
36
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
37
|
function intersect(a, b, not) {
|
|
44
|
-
return a.filter(c => {
|
|
38
|
+
return a.filter((c) => {
|
|
45
39
|
const index = ~indexOfDeclaration(b, c);
|
|
46
40
|
return not ? !index : index;
|
|
47
41
|
});
|
|
48
42
|
}
|
|
43
|
+
|
|
49
44
|
/**
|
|
50
45
|
* @param {postcss.Declaration[]} a
|
|
51
46
|
* @param {postcss.Declaration[]} b
|
|
52
47
|
* @return {boolean}
|
|
53
48
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
49
|
function sameDeclarationsAndOrder(a, b) {
|
|
57
50
|
if (a.length !== b.length) {
|
|
58
51
|
return false;
|
|
59
52
|
}
|
|
60
|
-
|
|
61
53
|
return a.every((d, index) => declarationIsEqual(d, b[index]));
|
|
62
54
|
}
|
|
55
|
+
|
|
63
56
|
/**
|
|
64
57
|
* @param {postcss.Rule} ruleA
|
|
65
58
|
* @param {postcss.Rule} ruleB
|
|
66
59
|
* @param {string[]=} browsers
|
|
67
|
-
* @param {
|
|
60
|
+
* @param {Map<string, boolean>=} compatibilityCache
|
|
68
61
|
* @return {boolean}
|
|
69
62
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
63
|
function canMerge(ruleA, ruleB, browsers, compatibilityCache) {
|
|
73
64
|
const a = ruleA.selectors;
|
|
74
65
|
const b = ruleB.selectors;
|
|
66
|
+
|
|
75
67
|
const selectors = a.concat(b);
|
|
76
68
|
|
|
77
|
-
if (!
|
|
69
|
+
if (!ensureCompatibility(selectors, browsers, compatibilityCache)) {
|
|
78
70
|
return false;
|
|
79
71
|
}
|
|
80
72
|
|
|
81
|
-
const parent =
|
|
82
|
-
const {
|
|
83
|
-
|
|
84
|
-
} = ruleA.parent;
|
|
85
|
-
|
|
86
|
-
if (parent && name && ~name.indexOf('keyframes')) {
|
|
73
|
+
const parent = sameParent(ruleA, ruleB);
|
|
74
|
+
const { name } = ruleA.parent;
|
|
75
|
+
if (parent && name && name.includes('keyframes')) {
|
|
87
76
|
return false;
|
|
88
77
|
}
|
|
89
|
-
|
|
90
|
-
return parent && (selectors.every(_ensureCompatibility.noVendor) || (0, _ensureCompatibility.sameVendor)(a, b));
|
|
78
|
+
return parent && (selectors.every(noVendor) || sameVendor(a, b));
|
|
91
79
|
}
|
|
80
|
+
|
|
92
81
|
/**
|
|
93
82
|
* @param {postcss.Rule} rule
|
|
94
83
|
* @return {postcss.Declaration[]}
|
|
95
84
|
*/
|
|
96
|
-
|
|
97
|
-
|
|
98
85
|
function getDecls(rule) {
|
|
99
|
-
return rule.nodes.filter(node => node.type === 'decl');
|
|
86
|
+
return rule.nodes.filter((node) => node.type === 'decl');
|
|
100
87
|
}
|
|
101
88
|
|
|
102
|
-
const joinSelectors = (...rules) => rules.map(s => s.selector).join();
|
|
89
|
+
const joinSelectors = (...rules) => rules.map((s) => s.selector).join();
|
|
103
90
|
|
|
104
91
|
function ruleLength(...rules) {
|
|
105
|
-
return rules.map(r => r.nodes.length ? String(r) : '').join('').length;
|
|
92
|
+
return rules.map((r) => (r.nodes.length ? String(r) : '')).join('').length;
|
|
106
93
|
}
|
|
94
|
+
|
|
107
95
|
/**
|
|
108
96
|
* @param {string} prop
|
|
109
97
|
* @return {{prefix: string, base:string, rest:string[]}}
|
|
110
98
|
*/
|
|
111
|
-
|
|
112
|
-
|
|
113
99
|
function splitProp(prop) {
|
|
114
100
|
// Treat vendor prefixed properties as if they were unprefixed;
|
|
115
101
|
// moving them when combined with non-prefixed properties can
|
|
116
102
|
// cause issues. e.g. moving -webkit-background-clip when there
|
|
117
103
|
// is a background shorthand definition.
|
|
118
|
-
const parts = prop.split('-');
|
|
119
104
|
|
|
105
|
+
const parts = prop.split('-');
|
|
120
106
|
if (prop[0] !== '-') {
|
|
121
107
|
return {
|
|
122
108
|
prefix: '',
|
|
123
109
|
base: parts[0],
|
|
124
|
-
rest: parts.slice(1)
|
|
110
|
+
rest: parts.slice(1),
|
|
125
111
|
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
112
|
+
}
|
|
113
|
+
// Don't split css variables
|
|
129
114
|
if (prop[1] === '-') {
|
|
130
115
|
return {
|
|
131
116
|
prefix: null,
|
|
132
117
|
base: null,
|
|
133
|
-
rest: [prop]
|
|
118
|
+
rest: [prop],
|
|
134
119
|
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
}
|
|
121
|
+
// Found prefix
|
|
138
122
|
return {
|
|
139
123
|
prefix: parts[1],
|
|
140
124
|
base: parts[2],
|
|
141
|
-
rest: parts.slice(3)
|
|
125
|
+
rest: parts.slice(3),
|
|
142
126
|
};
|
|
143
127
|
}
|
|
128
|
+
|
|
144
129
|
/**
|
|
145
130
|
* @param {string} propA
|
|
146
131
|
* @param {string} propB
|
|
147
132
|
*/
|
|
148
|
-
|
|
149
|
-
|
|
150
133
|
function isConflictingProp(propA, propB) {
|
|
151
134
|
if (propA === propB) {
|
|
152
135
|
// Same specificity
|
|
153
136
|
return true;
|
|
154
137
|
}
|
|
155
|
-
|
|
156
138
|
const a = splitProp(propA);
|
|
157
|
-
const b = splitProp(propB);
|
|
158
|
-
|
|
139
|
+
const b = splitProp(propB);
|
|
140
|
+
// Don't resort css variables
|
|
159
141
|
if (!a.base && !b.base) {
|
|
160
142
|
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
143
|
+
}
|
|
144
|
+
// Different base;
|
|
164
145
|
if (a.base !== b.base) {
|
|
165
146
|
return false;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
147
|
+
}
|
|
148
|
+
// Conflict if rest-count mismatches
|
|
169
149
|
if (a.rest.length !== b.rest.length) {
|
|
170
150
|
return true;
|
|
171
|
-
}
|
|
172
|
-
|
|
151
|
+
}
|
|
173
152
|
|
|
153
|
+
// Conflict if rest parameters are equal (same but unprefixed)
|
|
174
154
|
return a.rest.every((s, index) => b.rest[index] === s);
|
|
175
155
|
}
|
|
156
|
+
|
|
176
157
|
/**
|
|
177
158
|
* @param {postcss.Rule} first
|
|
178
159
|
* @param {postcss.Rule} second
|
|
179
160
|
* @return {boolean} merged
|
|
180
161
|
*/
|
|
181
|
-
|
|
182
|
-
|
|
183
162
|
function mergeParents(first, second) {
|
|
184
163
|
// Null check for detached rules
|
|
185
164
|
if (!first.parent || !second.parent) {
|
|
186
165
|
return false;
|
|
187
|
-
}
|
|
188
|
-
|
|
166
|
+
}
|
|
189
167
|
|
|
168
|
+
// Check if parents share node
|
|
190
169
|
if (first.parent === second.parent) {
|
|
191
170
|
return false;
|
|
192
|
-
}
|
|
171
|
+
}
|
|
193
172
|
|
|
173
|
+
// sameParent() already called by canMerge()
|
|
194
174
|
|
|
195
175
|
second.remove();
|
|
196
176
|
first.parent.append(second);
|
|
197
177
|
return true;
|
|
198
178
|
}
|
|
179
|
+
|
|
199
180
|
/**
|
|
200
181
|
* @param {postcss.Rule} first
|
|
201
182
|
* @param {postcss.Rule} second
|
|
202
183
|
* @return {postcss.Rule} mergedRule
|
|
203
184
|
*/
|
|
204
|
-
|
|
205
|
-
|
|
206
185
|
function partialMerge(first, second) {
|
|
207
186
|
let intersection = intersect(getDecls(first), getDecls(second));
|
|
208
|
-
|
|
209
187
|
if (!intersection.length) {
|
|
210
188
|
return second;
|
|
211
189
|
}
|
|
212
|
-
|
|
213
190
|
let nextRule = second.next();
|
|
214
|
-
|
|
215
191
|
if (!nextRule) {
|
|
216
192
|
// Grab next cousin
|
|
217
193
|
const parentSibling = second.parent.next();
|
|
218
194
|
nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
|
|
219
195
|
}
|
|
220
|
-
|
|
221
196
|
if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
|
|
222
197
|
let nextIntersection = intersect(getDecls(second), getDecls(nextRule));
|
|
223
|
-
|
|
224
198
|
if (nextIntersection.length > intersection.length) {
|
|
225
199
|
mergeParents(second, nextRule);
|
|
226
200
|
first = second;
|
|
@@ -229,45 +203,52 @@ function partialMerge(first, second) {
|
|
|
229
203
|
}
|
|
230
204
|
}
|
|
231
205
|
|
|
232
|
-
const firstDecls = getDecls(first);
|
|
206
|
+
const firstDecls = getDecls(first);
|
|
233
207
|
|
|
208
|
+
// Filter out intersections with later conflicts in First
|
|
234
209
|
intersection = intersection.filter((decl, intersectIndex) => {
|
|
235
210
|
const indexOfDecl = indexOfDeclaration(firstDecls, decl);
|
|
236
|
-
const nextConflictInFirst = firstDecls
|
|
237
|
-
|
|
211
|
+
const nextConflictInFirst = firstDecls
|
|
212
|
+
.slice(indexOfDecl + 1)
|
|
213
|
+
.filter((d) => isConflictingProp(d.prop, decl.prop));
|
|
238
214
|
if (!nextConflictInFirst.length) {
|
|
239
215
|
return true;
|
|
240
216
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
217
|
+
const nextConflictInIntersection = intersection
|
|
218
|
+
.slice(intersectIndex + 1)
|
|
219
|
+
.filter((d) => isConflictingProp(d.prop, decl.prop));
|
|
244
220
|
if (!nextConflictInIntersection.length) {
|
|
245
221
|
return false;
|
|
246
222
|
}
|
|
247
|
-
|
|
248
223
|
if (nextConflictInFirst.length !== nextConflictInIntersection.length) {
|
|
249
224
|
return false;
|
|
250
225
|
}
|
|
226
|
+
return nextConflictInFirst.every((d, index) =>
|
|
227
|
+
declarationIsEqual(d, nextConflictInIntersection[index])
|
|
228
|
+
);
|
|
229
|
+
});
|
|
251
230
|
|
|
252
|
-
|
|
253
|
-
}); // Filter out intersections with previous conflicts in Second
|
|
254
|
-
|
|
231
|
+
// Filter out intersections with previous conflicts in Second
|
|
255
232
|
const secondDecls = getDecls(second);
|
|
256
|
-
intersection = intersection.filter(decl => {
|
|
257
|
-
const nextConflictIndex = secondDecls.findIndex(d =>
|
|
258
|
-
|
|
233
|
+
intersection = intersection.filter((decl) => {
|
|
234
|
+
const nextConflictIndex = secondDecls.findIndex((d) =>
|
|
235
|
+
isConflictingProp(d.prop, decl.prop)
|
|
236
|
+
);
|
|
259
237
|
if (nextConflictIndex === -1) {
|
|
260
238
|
return false;
|
|
261
239
|
}
|
|
262
|
-
|
|
263
240
|
if (!declarationIsEqual(secondDecls[nextConflictIndex], decl)) {
|
|
264
241
|
return false;
|
|
265
242
|
}
|
|
266
|
-
|
|
267
|
-
|
|
243
|
+
if (
|
|
244
|
+
decl.prop.toLowerCase() !== 'direction' &&
|
|
245
|
+
decl.prop.toLowerCase() !== 'unicode-bidi' &&
|
|
246
|
+
secondDecls.some(
|
|
247
|
+
(declaration) => declaration.prop.toLowerCase() === 'all'
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
268
250
|
return false;
|
|
269
251
|
}
|
|
270
|
-
|
|
271
252
|
secondDecls.splice(nextConflictIndex, 1);
|
|
272
253
|
return true;
|
|
273
254
|
});
|
|
@@ -280,56 +261,55 @@ function partialMerge(first, second) {
|
|
|
280
261
|
const receivingBlock = second.clone();
|
|
281
262
|
receivingBlock.selector = joinSelectors(first, second);
|
|
282
263
|
receivingBlock.nodes = [];
|
|
264
|
+
|
|
283
265
|
second.parent.insertBefore(second, receivingBlock);
|
|
266
|
+
|
|
284
267
|
const firstClone = first.clone();
|
|
285
268
|
const secondClone = second.clone();
|
|
269
|
+
|
|
286
270
|
/**
|
|
287
271
|
* @param {function(postcss.Declaration):void} callback
|
|
288
272
|
* @return {function(postcss.Declaration)}
|
|
289
273
|
*/
|
|
290
|
-
|
|
291
274
|
function moveDecl(callback) {
|
|
292
|
-
return decl => {
|
|
275
|
+
return (decl) => {
|
|
293
276
|
if (~indexOfDeclaration(intersection, decl)) {
|
|
294
277
|
callback.call(this, decl);
|
|
295
278
|
}
|
|
296
279
|
};
|
|
297
280
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
281
|
+
firstClone.walkDecls(
|
|
282
|
+
moveDecl((decl) => {
|
|
283
|
+
decl.remove();
|
|
284
|
+
receivingBlock.append(decl);
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
secondClone.walkDecls(moveDecl((decl) => decl.remove()));
|
|
304
288
|
const merged = ruleLength(firstClone, receivingBlock, secondClone);
|
|
305
289
|
const original = ruleLength(first, second);
|
|
306
|
-
|
|
307
290
|
if (merged < original) {
|
|
308
291
|
first.replaceWith(firstClone);
|
|
309
292
|
second.replaceWith(secondClone);
|
|
310
|
-
[firstClone, receivingBlock, secondClone].forEach(r => {
|
|
293
|
+
[firstClone, receivingBlock, secondClone].forEach((r) => {
|
|
311
294
|
if (!r.nodes.length) {
|
|
312
295
|
r.remove();
|
|
313
296
|
}
|
|
314
297
|
});
|
|
315
|
-
|
|
316
298
|
if (!secondClone.parent) {
|
|
317
299
|
return receivingBlock;
|
|
318
300
|
}
|
|
319
|
-
|
|
320
301
|
return secondClone;
|
|
321
302
|
} else {
|
|
322
303
|
receivingBlock.remove();
|
|
323
304
|
return second;
|
|
324
305
|
}
|
|
325
306
|
}
|
|
307
|
+
|
|
326
308
|
/**
|
|
327
309
|
* @param {string[]} browsers
|
|
328
|
-
* @param {
|
|
310
|
+
* @param {Map<string, boolean>} compatibilityCache
|
|
329
311
|
* @return {function(postcss.Rule)}
|
|
330
312
|
*/
|
|
331
|
-
|
|
332
|
-
|
|
333
313
|
function selectorMerger(browsers, compatibilityCache) {
|
|
334
314
|
/** @type {postcss.Rule} */
|
|
335
315
|
let cache = null;
|
|
@@ -339,43 +319,40 @@ function selectorMerger(browsers, compatibilityCache) {
|
|
|
339
319
|
if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) {
|
|
340
320
|
cache = rule;
|
|
341
321
|
return;
|
|
342
|
-
}
|
|
322
|
+
}
|
|
323
|
+
// Ensure that we don't deduplicate the same rule; this is sometimes
|
|
343
324
|
// caused by a partial merge
|
|
344
|
-
|
|
345
|
-
|
|
346
325
|
if (cache === rule) {
|
|
347
326
|
cache = rule;
|
|
348
327
|
return;
|
|
349
|
-
}
|
|
328
|
+
}
|
|
350
329
|
|
|
330
|
+
// Parents merge: check if the rules have same parents, but not same parent nodes
|
|
331
|
+
mergeParents(cache, rule);
|
|
351
332
|
|
|
352
|
-
|
|
333
|
+
// Merge when declarations are exactly equal
|
|
353
334
|
// e.g. h1 { color: red } h2 { color: red }
|
|
354
|
-
|
|
355
335
|
if (sameDeclarationsAndOrder(getDecls(rule), getDecls(cache))) {
|
|
356
336
|
rule.selector = joinSelectors(cache, rule);
|
|
357
337
|
cache.remove();
|
|
358
338
|
cache = rule;
|
|
359
339
|
return;
|
|
360
|
-
}
|
|
340
|
+
}
|
|
341
|
+
// Merge when both selectors are exactly equal
|
|
361
342
|
// e.g. a { color: blue } a { font-weight: bold }
|
|
362
|
-
|
|
363
|
-
|
|
364
343
|
if (cache.selector === rule.selector) {
|
|
365
344
|
const cached = getDecls(cache);
|
|
366
|
-
rule.walk(decl => {
|
|
345
|
+
rule.walk((decl) => {
|
|
367
346
|
if (~indexOfDeclaration(cached, decl)) {
|
|
368
347
|
return decl.remove();
|
|
369
348
|
}
|
|
370
|
-
|
|
371
349
|
cache.append(decl);
|
|
372
350
|
});
|
|
373
351
|
rule.remove();
|
|
374
352
|
return;
|
|
375
|
-
}
|
|
353
|
+
}
|
|
354
|
+
// Partial merge: check if the rule contains a subset of the last; if
|
|
376
355
|
// so create a joined selector with the subset, if smaller.
|
|
377
|
-
|
|
378
|
-
|
|
379
356
|
cache = partialMerge(cache, rule);
|
|
380
357
|
};
|
|
381
358
|
}
|
|
@@ -386,24 +363,21 @@ function pluginCreator() {
|
|
|
386
363
|
|
|
387
364
|
prepare(result) {
|
|
388
365
|
const resultOpts = result.opts || {};
|
|
389
|
-
const browsers = (
|
|
366
|
+
const browsers = browserslist(null, {
|
|
390
367
|
stats: resultOpts.stats,
|
|
391
368
|
path: __dirname,
|
|
392
|
-
env: resultOpts.env
|
|
369
|
+
env: resultOpts.env,
|
|
393
370
|
});
|
|
394
|
-
|
|
371
|
+
|
|
372
|
+
const compatibilityCache = new Map();
|
|
395
373
|
return {
|
|
396
374
|
OnceExit(css) {
|
|
397
375
|
css.walkRules(selectorMerger(browsers, compatibilityCache));
|
|
398
|
-
}
|
|
399
|
-
|
|
376
|
+
},
|
|
400
377
|
};
|
|
401
|
-
}
|
|
402
|
-
|
|
378
|
+
},
|
|
403
379
|
};
|
|
404
380
|
}
|
|
405
381
|
|
|
406
382
|
pluginCreator.postcss = true;
|
|
407
|
-
|
|
408
|
-
exports.default = _default;
|
|
409
|
-
module.exports = exports.default;
|
|
383
|
+
module.exports = pluginCreator;
|
|
@@ -1,23 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.filterPrefixes = filterPrefixes;
|
|
7
|
-
exports.sameVendor = sameVendor;
|
|
8
|
-
exports.noVendor = noVendor;
|
|
9
|
-
exports.ensureCompatibility = ensureCompatibility;
|
|
10
|
-
exports.pseudoElements = void 0;
|
|
11
|
-
|
|
12
|
-
var _caniuseApi = require("caniuse-api");
|
|
13
|
-
|
|
14
|
-
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
|
|
15
|
-
|
|
16
|
-
var _vendors = _interopRequireDefault(require("vendors"));
|
|
17
|
-
|
|
18
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
1
|
+
'use strict';
|
|
2
|
+
const { isSupported } = require('caniuse-api');
|
|
3
|
+
const selectorParser = require('postcss-selector-parser');
|
|
19
4
|
|
|
20
5
|
const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;
|
|
6
|
+
|
|
21
7
|
const cssSel2 = 'css-sel2';
|
|
22
8
|
const cssSel3 = 'css-sel3';
|
|
23
9
|
const cssGencontent = 'css-gencontent';
|
|
@@ -25,38 +11,38 @@ const cssFirstLetter = 'css-first-letter';
|
|
|
25
11
|
const cssFirstLine = 'css-first-line';
|
|
26
12
|
const cssInOutOfRange = 'css-in-out-of-range';
|
|
27
13
|
const formValidation = 'form-validation';
|
|
28
|
-
/** @type {string[]} */
|
|
29
14
|
|
|
30
|
-
const
|
|
15
|
+
const vendorPrefix =
|
|
16
|
+
/-(ah|apple|atsc|epub|hp|khtml|moz|ms|o|rim|ro|tc|wap|webkit|xv)-/;
|
|
17
|
+
|
|
31
18
|
/**
|
|
32
19
|
* @param {string} selector
|
|
33
20
|
* @return {string[]}
|
|
34
21
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
22
|
function filterPrefixes(selector) {
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
// Microsoft Edge use ::-ms-input-placeholder.
|
|
41
|
-
|
|
23
|
+
return selector.match(vendorPrefix);
|
|
24
|
+
}
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
// Internet Explorer use :-ms-input-placeholder.
|
|
27
|
+
// Microsoft Edge use ::-ms-input-placeholder.
|
|
28
|
+
const findMsInputPlaceholder = (selector) =>
|
|
29
|
+
~selector.search(/-ms-input-placeholder/i);
|
|
44
30
|
|
|
45
31
|
function sameVendor(selectorsA, selectorsB) {
|
|
46
|
-
let same = selectors => selectors.map(filterPrefixes).join();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
32
|
+
let same = (selectors) => selectors.map(filterPrefixes).join();
|
|
33
|
+
let findMsVendor = (selectors) => selectors.find(findMsInputPlaceholder);
|
|
34
|
+
return (
|
|
35
|
+
same(selectorsA) === same(selectorsB) &&
|
|
36
|
+
!(findMsVendor(selectorsA) && findMsVendor(selectorsB))
|
|
37
|
+
);
|
|
51
38
|
}
|
|
39
|
+
|
|
52
40
|
/**
|
|
53
41
|
* @param {string} selector
|
|
54
42
|
* @return {boolean}
|
|
55
43
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
44
|
function noVendor(selector) {
|
|
59
|
-
return !
|
|
45
|
+
return !vendorPrefix.test(selector);
|
|
60
46
|
}
|
|
61
47
|
|
|
62
48
|
const pseudoElements = {
|
|
@@ -110,9 +96,8 @@ const pseudoElements = {
|
|
|
110
96
|
'::placeholder': 'css-placeholder',
|
|
111
97
|
'::selection': 'css-selection',
|
|
112
98
|
':valid': formValidation,
|
|
113
|
-
':visited': cssSel2
|
|
99
|
+
':visited': cssSel2,
|
|
114
100
|
};
|
|
115
|
-
exports.pseudoElements = pseudoElements;
|
|
116
101
|
|
|
117
102
|
function isCssMixin(selector) {
|
|
118
103
|
return selector[selector.length - 1] === ':';
|
|
@@ -122,18 +107,16 @@ function isHostPseudoClass(selector) {
|
|
|
122
107
|
return selector.includes(':host');
|
|
123
108
|
}
|
|
124
109
|
|
|
125
|
-
const isSupportedCache =
|
|
110
|
+
const isSupportedCache = new Map();
|
|
126
111
|
|
|
112
|
+
// Move to util in future
|
|
127
113
|
function isSupportedCached(feature, browsers) {
|
|
128
|
-
const key = JSON.stringify({
|
|
129
|
-
|
|
130
|
-
browsers
|
|
131
|
-
});
|
|
132
|
-
let result = isSupportedCache[key];
|
|
114
|
+
const key = JSON.stringify({ feature, browsers });
|
|
115
|
+
let result = isSupportedCache.get(key);
|
|
133
116
|
|
|
134
117
|
if (!result) {
|
|
135
|
-
result =
|
|
136
|
-
isSupportedCache
|
|
118
|
+
result = isSupported(feature, browsers);
|
|
119
|
+
isSupportedCache.set(key, result);
|
|
137
120
|
}
|
|
138
121
|
|
|
139
122
|
return result;
|
|
@@ -143,52 +126,40 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
|
|
|
143
126
|
// Should not merge mixins
|
|
144
127
|
if (selectors.some(isCssMixin)) {
|
|
145
128
|
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
129
|
+
}
|
|
148
130
|
|
|
131
|
+
// Should not merge :host selector https://github.com/angular/angular-cli/issues/18672
|
|
149
132
|
if (selectors.some(isHostPseudoClass)) {
|
|
150
133
|
return false;
|
|
151
134
|
}
|
|
152
|
-
|
|
153
|
-
return selectors.every(selector => {
|
|
135
|
+
return selectors.every((selector) => {
|
|
154
136
|
if (simpleSelectorRe.test(selector)) {
|
|
155
137
|
return true;
|
|
156
138
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return compatibilityCache[selector];
|
|
139
|
+
if (compatibilityCache && compatibilityCache.has(selector)) {
|
|
140
|
+
return compatibilityCache.get(selector);
|
|
160
141
|
}
|
|
161
|
-
|
|
162
142
|
let compatible = true;
|
|
163
|
-
(
|
|
164
|
-
ast.walk(node => {
|
|
165
|
-
const {
|
|
166
|
-
type,
|
|
167
|
-
value
|
|
168
|
-
} = node;
|
|
169
|
-
|
|
143
|
+
selectorParser((ast) => {
|
|
144
|
+
ast.walk((node) => {
|
|
145
|
+
const { type, value } = node;
|
|
170
146
|
if (type === 'pseudo') {
|
|
171
147
|
const entry = pseudoElements[value];
|
|
172
|
-
|
|
173
148
|
if (!entry && noVendor(value)) {
|
|
174
149
|
compatible = false;
|
|
175
150
|
}
|
|
176
|
-
|
|
177
151
|
if (entry && compatible) {
|
|
178
152
|
compatible = isSupportedCached(entry, browsers);
|
|
179
153
|
}
|
|
180
154
|
}
|
|
181
|
-
|
|
182
155
|
if (type === 'combinator') {
|
|
183
|
-
if (
|
|
156
|
+
if (value.includes('~')) {
|
|
184
157
|
compatible = isSupportedCached(cssSel3, browsers);
|
|
185
158
|
}
|
|
186
|
-
|
|
187
|
-
if (~value.indexOf('>') || ~value.indexOf('+')) {
|
|
159
|
+
if (value.includes('>') || value.includes('+')) {
|
|
188
160
|
compatible = isSupportedCached(cssSel2, browsers);
|
|
189
161
|
}
|
|
190
162
|
}
|
|
191
|
-
|
|
192
163
|
if (type === 'attribute' && node.attribute) {
|
|
193
164
|
// [foo]
|
|
194
165
|
if (!node.operator) {
|
|
@@ -197,22 +168,20 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
|
|
|
197
168
|
|
|
198
169
|
if (value) {
|
|
199
170
|
// [foo="bar"], [foo~="bar"], [foo|="bar"]
|
|
200
|
-
if (
|
|
171
|
+
if (['=', '~=', '|='].includes(node.operator)) {
|
|
201
172
|
compatible = isSupportedCached(cssSel2, browsers);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (~['^=', '$=', '*='].indexOf(node.operator)) {
|
|
173
|
+
}
|
|
174
|
+
// [foo^="bar"], [foo$="bar"], [foo*="bar"]
|
|
175
|
+
if (['^=', '$=', '*='].includes(node.operator)) {
|
|
206
176
|
compatible = isSupportedCached(cssSel3, browsers);
|
|
207
177
|
}
|
|
208
|
-
}
|
|
209
|
-
|
|
178
|
+
}
|
|
210
179
|
|
|
180
|
+
// [foo="bar" i]
|
|
211
181
|
if (node.insensitive) {
|
|
212
182
|
compatible = isSupportedCached('css-case-insensitive', browsers);
|
|
213
183
|
}
|
|
214
184
|
}
|
|
215
|
-
|
|
216
185
|
if (!compatible) {
|
|
217
186
|
// If this node was not compatible,
|
|
218
187
|
// break out early from walking the rest
|
|
@@ -220,11 +189,11 @@ function ensureCompatibility(selectors, browsers, compatibilityCache) {
|
|
|
220
189
|
}
|
|
221
190
|
});
|
|
222
191
|
}).processSync(selector);
|
|
223
|
-
|
|
224
192
|
if (compatibilityCache) {
|
|
225
|
-
compatibilityCache
|
|
193
|
+
compatibilityCache.set(selector, compatible);
|
|
226
194
|
}
|
|
227
|
-
|
|
228
195
|
return compatible;
|
|
229
196
|
});
|
|
230
|
-
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = { sameVendor, noVendor, pseudoElements, ensureCompatibility };
|