postcss-merge-rules 4.0.3 → 5.0.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,75 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ # [5.0.0](https://github.com/cssnano/cssnano/compare/postcss-merge-rules@5.0.0-rc.2...postcss-merge-rules@5.0.0) (2021-04-06)
7
+
8
+ **Note:** Version bump only for package postcss-merge-rules
9
+
10
+
11
+
12
+
13
+
14
+ # [5.0.0-rc.2](https://github.com/cssnano/cssnano/compare/postcss-merge-rules@5.0.0-rc.1...postcss-merge-rules@5.0.0-rc.2) (2021-03-15)
15
+
16
+ **Note:** Version bump only for package postcss-merge-rules
17
+
18
+
19
+
20
+
21
+
22
+ # [5.0.0-rc.1](https://github.com/cssnano/cssnano/compare/postcss-merge-rules@5.0.0-rc.0...postcss-merge-rules@5.0.0-rc.1) (2021-03-04)
23
+
24
+ **Note:** Version bump only for package postcss-merge-rules
25
+
26
+
27
+
28
+
29
+
30
+ # 5.0.0-rc.0 (2021-02-19)
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * don't unsafe merge 'all' declaration ([#872](https://github.com/cssnano/cssnano/issues/872)) ([6ea9e5d](https://github.com/cssnano/cssnano/commit/6ea9e5dcad2d8ea22be7209332ee29d352c807de))
36
+ * focus-visible issue ([#882](https://github.com/cssnano/cssnano/issues/882)) ([4cfcaaf](https://github.com/cssnano/cssnano/commit/4cfcaaf25b162ec2b0308907a408d7dba6a354c3))
37
+ * **merge-rules, merge-idents:** add support for nested at-rules ([#719](https://github.com/cssnano/cssnano/issues/719)) ([cdedda7](https://github.com/cssnano/cssnano/commit/cdedda7f9d67873d872add044ad34c91616579f3))
38
+ * **postcss-merge-rules:** don't change specificity of prefixed properties ([#723](https://github.com/cssnano/cssnano/issues/723)) ([863cf2b](https://github.com/cssnano/cssnano/commit/863cf2b3470d3172523a3165dc368abcfa18809c))
39
+
40
+
41
+ ### chore
42
+
43
+ * minimum require version of node is 10.13 ([#871](https://github.com/cssnano/cssnano/issues/871)) ([28bda24](https://github.com/cssnano/cssnano/commit/28bda243e32ce3ba89b3c358a5f78727b3732f11))
44
+
45
+
46
+ ### Features
47
+
48
+ * migarete to PostCSS 8 ([#975](https://github.com/cssnano/cssnano/issues/975)) ([40b82dc](https://github.com/cssnano/cssnano/commit/40b82dca7f53ac02cd4fe62846dec79b898ccb49))
49
+ * **postcss-merge-rules:** merge at-rules ([#722](https://github.com/cssnano/cssnano/issues/722)) ([8d4610a](https://github.com/cssnano/cssnano/commit/8d4610a6391ddab29bcb08ef0522d0b7ce2d6582))
50
+
51
+
52
+ ### BREAKING CHANGES
53
+
54
+ * minimum supported `postcss` version is `8.2.1`
55
+ * minimum require version of node is 10.13
56
+
57
+
58
+
59
+ ## 4.1.9 (2019-02-12)
60
+
61
+
62
+ ### Performance Improvements
63
+
64
+ * **postcss-merge-rules:** increase perf ([#681](https://github.com/cssnano/cssnano/issues/681)) ([35bad2b](https://github.com/cssnano/cssnano/commit/35bad2b70fca5390c88eaabc24c25bb8d28b2f95))
65
+
66
+
67
+
68
+ ## 4.1.1 (2018-09-24)
69
+
70
+
71
+ ### Bug Fixes
72
+
73
+ * handle uppercase `all` property in merge rules ([#611](https://github.com/cssnano/cssnano/issues/611)) ([0dfe335](https://github.com/cssnano/cssnano/commit/0dfe3355951fa4a080a04dca34c6d99420def7ac))
74
+ * merge same atrules with difference case ([#605](https://github.com/cssnano/cssnano/issues/605)) ([ca350fd](https://github.com/cssnano/cssnano/commit/ca350fda779bab5ca2eadf70299d92f8e495a273))
75
+ * **postcss-merge-longhand:** not mangle border output ([#555](https://github.com/cssnano/cssnano/issues/555)) ([9a70605](https://github.com/cssnano/cssnano/commit/9a706050b621e7795a9bf74eb7110b5c81804ffe)), closes [#553](https://github.com/cssnano/cssnano/issues/553) [#554](https://github.com/cssnano/cssnano/issues/554)
package/dist/index.js CHANGED
@@ -1,241 +1,445 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
+ exports.default = void 0;
6
7
 
7
- var _browserslist = require('browserslist');
8
+ var _browserslist = _interopRequireDefault(require("browserslist"));
8
9
 
9
- var _browserslist2 = _interopRequireDefault(_browserslist);
10
+ var _vendors = _interopRequireDefault(require("vendors"));
10
11
 
11
- var _postcss = require('postcss');
12
+ var _cssnanoUtils = require("cssnano-utils");
12
13
 
13
- var _postcss2 = _interopRequireDefault(_postcss);
14
+ var _ensureCompatibility = _interopRequireDefault(require("./lib/ensureCompatibility"));
14
15
 
15
- var _vendors = require('vendors');
16
-
17
- var _vendors2 = _interopRequireDefault(_vendors);
16
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
17
 
19
- var _cssnanoUtilSameParent = require('cssnano-util-same-parent');
18
+ /** @type {string[]} */
19
+ const prefixes = _vendors.default.map(v => `-${v}-`);
20
+ /**
21
+ * @param {postcss.Declaration} a
22
+ * @param {postcss.Declaration} b
23
+ * @return {boolean}
24
+ */
20
25
 
21
- var _cssnanoUtilSameParent2 = _interopRequireDefault(_cssnanoUtilSameParent);
22
26
 
23
- var _ensureCompatibility = require('./lib/ensureCompatibility');
27
+ function declarationIsEqual(a, b) {
28
+ return a.important === b.important && a.prop === b.prop && a.value === b.value;
29
+ }
30
+ /**
31
+ * @param {postcss.Declaration[]} array
32
+ * @param {postcss.Declaration} decl
33
+ * @return {number}
34
+ */
24
35
 
25
- var _ensureCompatibility2 = _interopRequireDefault(_ensureCompatibility);
26
36
 
27
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
37
+ function indexOfDeclaration(array, decl) {
38
+ return array.findIndex(d => declarationIsEqual(d, decl));
39
+ }
40
+ /**
41
+ * Returns filtered array of matched or unmatched declarations
42
+ * @param {postcss.Declaration[]} a
43
+ * @param {postcss.Declaration[]} b
44
+ * @param {boolean} [not=false]
45
+ * @return {postcss.Declaration[]}
46
+ */
28
47
 
29
- const prefixes = _vendors2.default.map(v => `-${v}-`);
30
48
 
31
49
  function intersect(a, b, not) {
32
- return a.filter(c => {
33
- const index = ~b.indexOf(c);
34
- return not ? !index : index;
35
- });
50
+ return a.filter(c => {
51
+ const index = ~indexOfDeclaration(b, c);
52
+ return not ? !index : index;
53
+ });
36
54
  }
55
+ /**
56
+ * @param {postcss.Declaration[]} a
57
+ * @param {postcss.Declaration[]} b
58
+ * @return {boolean}
59
+ */
60
+
37
61
 
38
- // Internet Explorer use :-ms-input-placeholder.
62
+ function sameDeclarationsAndOrder(a, b) {
63
+ if (a.length !== b.length) {
64
+ return false;
65
+ }
66
+
67
+ return a.every((d, index) => declarationIsEqual(d, b[index]));
68
+ } // Internet Explorer use :-ms-input-placeholder.
39
69
  // Microsoft Edge use ::-ms-input-placeholder.
70
+
71
+
40
72
  const findMsInputPlaceholder = selector => ~selector.search(/-ms-input-placeholder/i);
41
- const different = (a, b) => intersect(a, b, true).concat(intersect(b, a, true));
42
- const filterPrefixes = selector => intersect(prefixes, selector);
73
+ /**
74
+ * @param {string} selector
75
+ * @return {string[]}
76
+ */
77
+
78
+
79
+ function filterPrefixes(selector) {
80
+ return prefixes.filter(prefix => selector.indexOf(prefix) !== -1);
81
+ }
43
82
 
44
83
  function sameVendor(selectorsA, selectorsB) {
45
- let same = selectors => selectors.map(filterPrefixes).join();
46
- let findMsVendor = selectors => selectors.find(findMsInputPlaceholder);
47
- return same(selectorsA) === same(selectorsB) && !(findMsVendor(selectorsA) && findMsVendor(selectorsB));
84
+ let same = selectors => selectors.map(filterPrefixes).join();
85
+
86
+ let findMsVendor = selectors => selectors.find(findMsInputPlaceholder);
87
+
88
+ return same(selectorsA) === same(selectorsB) && !(findMsVendor(selectorsA) && findMsVendor(selectorsB));
48
89
  }
90
+ /**
91
+ * @param {string} selector
92
+ * @return {boolean}
93
+ */
94
+
95
+
96
+ function noVendor(selector) {
97
+ return !filterPrefixes(selector).length;
98
+ }
99
+ /**
100
+ * @param {postcss.Rule} ruleA
101
+ * @param {postcss.Rule} ruleB
102
+ * @param {string[]=} browsers
103
+ * @param {Object.<string, boolean>=} compatibilityCache
104
+ * @return {boolean}
105
+ */
49
106
 
50
- const noVendor = selector => !filterPrefixes(selector).length;
51
107
 
52
108
  function canMerge(ruleA, ruleB, browsers, compatibilityCache) {
53
- const a = ruleA.selectors;
54
- const b = ruleB.selectors;
109
+ const a = ruleA.selectors;
110
+ const b = ruleB.selectors;
111
+ const selectors = a.concat(b);
55
112
 
56
- const selectors = a.concat(b);
113
+ if (!(0, _ensureCompatibility.default)(selectors, browsers, compatibilityCache)) {
114
+ return false;
115
+ }
57
116
 
58
- if (!(0, _ensureCompatibility2.default)(selectors, browsers, compatibilityCache)) {
59
- return false;
60
- }
117
+ const parent = (0, _cssnanoUtils.sameParent)(ruleA, ruleB);
118
+ const {
119
+ name
120
+ } = ruleA.parent;
61
121
 
62
- const parent = (0, _cssnanoUtilSameParent2.default)(ruleA, ruleB);
63
- const { name } = ruleA.parent;
64
- if (parent && name && ~name.indexOf('keyframes')) {
65
- return false;
66
- }
67
- return parent && (selectors.every(noVendor) || sameVendor(a, b));
122
+ if (parent && name && ~name.indexOf('keyframes')) {
123
+ return false;
124
+ }
125
+
126
+ return parent && (selectors.every(noVendor) || sameVendor(a, b));
127
+ }
128
+ /**
129
+ * @param {postcss.Rule} rule
130
+ * @return {postcss.Declaration[]}
131
+ */
132
+
133
+
134
+ function getDecls(rule) {
135
+ return rule.nodes.filter(node => node.type === 'decl');
68
136
  }
69
137
 
70
- const getDecls = rule => rule.nodes && rule.nodes.map(String);
71
138
  const joinSelectors = (...rules) => rules.map(s => s.selector).join();
72
139
 
73
140
  function ruleLength(...rules) {
74
- return rules.map(r => r.nodes.length ? String(r) : '').join('').length;
141
+ return rules.map(r => r.nodes.length ? String(r) : '').join('').length;
75
142
  }
143
+ /**
144
+ * @param {string} prop
145
+ * @return {{prefix: string, base:string, rest:string[]}}
146
+ */
147
+
76
148
 
77
149
  function splitProp(prop) {
78
- const parts = prop.split('-');
79
- let base, rest;
80
- // Treat vendor prefixed properties as if they were unprefixed;
81
- // moving them when combined with non-prefixed properties can
82
- // cause issues. e.g. moving -webkit-background-clip when there
83
- // is a background shorthand definition.
84
- if (prop[0] === '-') {
85
- base = parts[2];
86
- rest = parts.slice(3);
87
- } else {
88
- base = parts[0];
89
- rest = parts.slice(1);
90
- }
91
- return [base, rest];
150
+ // Treat vendor prefixed properties as if they were unprefixed;
151
+ // moving them when combined with non-prefixed properties can
152
+ // cause issues. e.g. moving -webkit-background-clip when there
153
+ // is a background shorthand definition.
154
+ const parts = prop.split('-');
155
+
156
+ if (prop[0] !== '-') {
157
+ return {
158
+ prefix: '',
159
+ base: parts[0],
160
+ rest: parts.slice(1)
161
+ };
162
+ } // Don't split css variables
163
+
164
+
165
+ if (prop[1] === '-') {
166
+ return {
167
+ prefix: null,
168
+ base: null,
169
+ rest: [prop]
170
+ };
171
+ } // Found prefix
172
+
173
+
174
+ return {
175
+ prefix: parts[1],
176
+ base: parts[2],
177
+ rest: parts.slice(3)
178
+ };
92
179
  }
180
+ /**
181
+ * @param {string} propA
182
+ * @param {string} propB
183
+ */
184
+
93
185
 
94
186
  function isConflictingProp(propA, propB) {
95
- if (propA === propB) {
96
- return true;
97
- }
98
- const a = splitProp(propA);
99
- const b = splitProp(propB);
100
- return a[0] === b[0] && a[1].length !== b[1].length;
187
+ if (propA === propB) {
188
+ // Same specificity
189
+ return true;
190
+ }
191
+
192
+ const a = splitProp(propA);
193
+ const b = splitProp(propB); // Don't resort css variables
194
+
195
+ if (!a.base && !b.base) {
196
+ return true;
197
+ } // Different base;
198
+
199
+
200
+ if (a.base !== b.base) {
201
+ return false;
202
+ } // Conflict if rest-count mismatches
203
+
204
+
205
+ if (a.rest.length !== b.rest.length) {
206
+ return true;
207
+ } // Conflict if rest parameters are equal (same but unprefixed)
208
+
209
+
210
+ return a.rest.every((s, index) => b.rest[index] === s);
101
211
  }
212
+ /**
213
+ * @param {postcss.Rule} first
214
+ * @param {postcss.Rule} second
215
+ * @return {boolean} merged
216
+ */
217
+
218
+
219
+ function mergeParents(first, second) {
220
+ // Null check for detached rules
221
+ if (!first.parent || !second.parent) {
222
+ return false;
223
+ } // Check if parents share node
224
+
102
225
 
103
- function hasConflicts(declProp, notMoved) {
104
- return notMoved.some(prop => isConflictingProp(prop, declProp));
226
+ if (first.parent === second.parent) {
227
+ return false;
228
+ } // sameParent() already called by canMerge()
229
+
230
+
231
+ second.remove();
232
+ first.parent.append(second);
233
+ return true;
105
234
  }
235
+ /**
236
+ * @param {postcss.Rule} first
237
+ * @param {postcss.Rule} second
238
+ * @return {postcss.Rule} mergedRule
239
+ */
240
+
106
241
 
107
242
  function partialMerge(first, second) {
108
- let intersection = intersect(getDecls(first), getDecls(second));
109
- if (!intersection.length) {
110
- return second;
243
+ let intersection = intersect(getDecls(first), getDecls(second));
244
+
245
+ if (!intersection.length) {
246
+ return second;
247
+ }
248
+
249
+ let nextRule = second.next();
250
+
251
+ if (!nextRule) {
252
+ // Grab next cousin
253
+ const parentSibling = second.parent.next();
254
+ nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
255
+ }
256
+
257
+ if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
258
+ let nextIntersection = intersect(getDecls(second), getDecls(nextRule));
259
+
260
+ if (nextIntersection.length > intersection.length) {
261
+ mergeParents(second, nextRule);
262
+ first = second;
263
+ second = nextRule;
264
+ intersection = nextIntersection;
111
265
  }
112
- let nextRule = second.next();
113
- if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
114
- let nextIntersection = intersect(getDecls(second), getDecls(nextRule));
115
- if (nextIntersection.length > intersection.length) {
116
- first = second;second = nextRule;intersection = nextIntersection;
117
- }
266
+ }
267
+
268
+ const firstDecls = getDecls(first); // Filter out intersections with later conflicts in First
269
+
270
+ intersection = intersection.filter((decl, intersectIndex) => {
271
+ const index = indexOfDeclaration(firstDecls, decl);
272
+ const nextConflictInFirst = firstDecls.slice(index + 1).find(d => isConflictingProp(d.prop, decl.prop));
273
+
274
+ if (!nextConflictInFirst) {
275
+ return true;
118
276
  }
119
- const recievingBlock = second.clone();
120
- recievingBlock.selector = joinSelectors(first, second);
121
- recievingBlock.nodes = [];
122
- const difference = different(getDecls(first), getDecls(second));
123
- const filterConflicts = (decls, intersectn) => {
124
- let willNotMove = [];
125
- return decls.reduce((willMove, decl) => {
126
- let intersects = ~intersectn.indexOf(decl);
127
- let prop = decl.split(':')[0];
128
- let base = prop.split('-')[0];
129
- let canMove = difference.every(d => d.split(':')[0] !== base);
130
- if (intersects && canMove && !hasConflicts(prop, willNotMove)) {
131
- willMove.push(decl);
132
- } else {
133
- willNotMove.push(prop);
134
- }
135
- return willMove;
136
- }, []);
137
- };
138
- const containsAllDeclaration = intersectionList => {
139
- return intersectionList.some(declaration => {
140
- return declaration.split(':')[0].toLowerCase() === 'all';
141
- });
142
- };
143
- intersection = filterConflicts(getDecls(first).reverse(), intersection);
144
- intersection = filterConflicts(getDecls(second), intersection);
145
-
146
- // Rules with "all" declarations must be on top
147
- if (containsAllDeclaration(intersection)) {
148
- second.parent.insertBefore(first, recievingBlock);
149
- } else {
150
- second.parent.insertBefore(second, recievingBlock);
277
+
278
+ const nextConflictInIntersection = intersection.slice(intersectIndex + 1).find(d => isConflictingProp(d.prop, decl.prop));
279
+
280
+ if (!nextConflictInIntersection) {
281
+ return false;
282
+ }
283
+
284
+ if (declarationIsEqual(nextConflictInFirst, nextConflictInIntersection)) {
285
+ return true;
286
+ }
287
+
288
+ return false;
289
+ }); // Filter out intersections with previous conflicts in Second
290
+
291
+ const secondDecls = getDecls(second);
292
+ intersection = intersection.filter(decl => {
293
+ const nextConflictIndex = secondDecls.findIndex(d => isConflictingProp(d.prop, decl.prop));
294
+
295
+ if (nextConflictIndex === -1) {
296
+ return false;
297
+ }
298
+
299
+ if (!declarationIsEqual(secondDecls[nextConflictIndex], decl)) {
300
+ return false;
301
+ }
302
+
303
+ if (decl.prop.toLowerCase() !== 'direction' && decl.prop.toLowerCase() !== 'unicode-bidi' && secondDecls.some(declaration => declaration.prop.toLowerCase() === 'all')) {
304
+ return false;
151
305
  }
152
306
 
153
- const firstClone = first.clone();
154
- const secondClone = second.clone();
155
- const moveDecl = callback => {
156
- return decl => {
157
- if (~intersection.indexOf(String(decl))) {
158
- callback.call(this, decl);
159
- }
160
- };
307
+ secondDecls.splice(nextConflictIndex, 1);
308
+ return true;
309
+ });
310
+
311
+ if (!intersection.length) {
312
+ // Nothing to merge
313
+ return second;
314
+ }
315
+
316
+ const receivingBlock = second.clone();
317
+ receivingBlock.selector = joinSelectors(first, second);
318
+ receivingBlock.nodes = [];
319
+ second.parent.insertBefore(second, receivingBlock);
320
+ const firstClone = first.clone();
321
+ const secondClone = second.clone();
322
+ /**
323
+ * @param {function(postcss.Declaration):void} callback
324
+ * @return {function(postcss.Declaration)}
325
+ */
326
+
327
+ function moveDecl(callback) {
328
+ return decl => {
329
+ if (~indexOfDeclaration(intersection, decl)) {
330
+ callback.call(this, decl);
331
+ }
161
332
  };
162
- firstClone.walkDecls(moveDecl(decl => {
163
- decl.remove();
164
- recievingBlock.append(decl);
165
- }));
166
- secondClone.walkDecls(moveDecl(decl => decl.remove()));
167
- const merged = ruleLength(firstClone, recievingBlock, secondClone);
168
- const original = ruleLength(first, second);
169
- if (merged < original) {
170
- first.replaceWith(firstClone);
171
- second.replaceWith(secondClone);
172
- [firstClone, recievingBlock, secondClone].forEach(r => {
173
- if (!r.nodes.length) {
174
- r.remove();
175
- }
176
- });
177
- if (!secondClone.parent) {
178
- return recievingBlock;
179
- }
180
- return secondClone;
181
- } else {
182
- recievingBlock.remove();
183
- return second;
333
+ }
334
+
335
+ firstClone.walkDecls(moveDecl(decl => {
336
+ decl.remove();
337
+ receivingBlock.append(decl);
338
+ }));
339
+ secondClone.walkDecls(moveDecl(decl => decl.remove()));
340
+ const merged = ruleLength(firstClone, receivingBlock, secondClone);
341
+ const original = ruleLength(first, second);
342
+
343
+ if (merged < original) {
344
+ first.replaceWith(firstClone);
345
+ second.replaceWith(secondClone);
346
+ [firstClone, receivingBlock, secondClone].forEach(r => {
347
+ if (!r.nodes.length) {
348
+ r.remove();
349
+ }
350
+ });
351
+
352
+ if (!secondClone.parent) {
353
+ return receivingBlock;
184
354
  }
355
+
356
+ return secondClone;
357
+ } else {
358
+ receivingBlock.remove();
359
+ return second;
360
+ }
185
361
  }
362
+ /**
363
+ * @param {string[]} browsers
364
+ * @param {Object.<string, boolean>} compatibilityCache
365
+ * @return {function(postcss.Rule)}
366
+ */
367
+
186
368
 
187
369
  function selectorMerger(browsers, compatibilityCache) {
188
- let cache = null;
189
- return function (rule) {
190
- // Prime the cache with the first rule, or alternately ensure that it is
191
- // safe to merge both declarations before continuing
192
- if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) {
193
- cache = rule;
194
- return;
195
- }
196
- // Ensure that we don't deduplicate the same rule; this is sometimes
197
- // caused by a partial merge
198
- if (cache === rule) {
199
- cache = rule;
200
- return;
201
- }
202
- // Merge when declarations are exactly equal
203
- // e.g. h1 { color: red } h2 { color: red }
204
- if (getDecls(rule).join(';') === getDecls(cache).join(';')) {
205
- rule.selector = joinSelectors(cache, rule);
206
- cache.remove();
207
- cache = rule;
208
- return;
370
+ /** @type {postcss.Rule} */
371
+ let cache = null;
372
+ return function (rule) {
373
+ // Prime the cache with the first rule, or alternately ensure that it is
374
+ // safe to merge both declarations before continuing
375
+ if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) {
376
+ cache = rule;
377
+ return;
378
+ } // Ensure that we don't deduplicate the same rule; this is sometimes
379
+ // caused by a partial merge
380
+
381
+
382
+ if (cache === rule) {
383
+ cache = rule;
384
+ return;
385
+ } // Parents merge: check if the rules have same parents, but not same parent nodes
386
+
387
+
388
+ mergeParents(cache, rule); // Merge when declarations are exactly equal
389
+ // e.g. h1 { color: red } h2 { color: red }
390
+
391
+ if (sameDeclarationsAndOrder(getDecls(rule), getDecls(cache))) {
392
+ rule.selector = joinSelectors(cache, rule);
393
+ cache.remove();
394
+ cache = rule;
395
+ return;
396
+ } // Merge when both selectors are exactly equal
397
+ // e.g. a { color: blue } a { font-weight: bold }
398
+
399
+
400
+ if (cache.selector === rule.selector) {
401
+ const cached = getDecls(cache);
402
+ rule.walk(decl => {
403
+ if (~indexOfDeclaration(cached, decl)) {
404
+ return decl.remove();
209
405
  }
210
- // Merge when both selectors are exactly equal
211
- // e.g. a { color: blue } a { font-weight: bold }
212
- if (cache.selector === rule.selector) {
213
- const cached = getDecls(cache);
214
- rule.walk(decl => {
215
- if (~cached.indexOf(String(decl))) {
216
- return decl.remove();
217
- }
218
- cache.append(decl);
219
- });
220
- rule.remove();
221
- return;
406
+
407
+ cache.append(decl);
408
+ });
409
+ rule.remove();
410
+ return;
411
+ } // Partial merge: check if the rule contains a subset of the last; if
412
+ // so create a joined selector with the subset, if smaller.
413
+
414
+
415
+ cache = partialMerge(cache, rule);
416
+ };
417
+ }
418
+
419
+ function pluginCreator() {
420
+ return {
421
+ postcssPlugin: 'postcss-merge-rules',
422
+
423
+ prepare(result) {
424
+ const resultOpts = result.opts || {};
425
+ const browsers = (0, _browserslist.default)(null, {
426
+ stats: resultOpts.stats,
427
+ path: __dirname,
428
+ env: resultOpts.env
429
+ });
430
+ const compatibilityCache = {};
431
+ return {
432
+ OnceExit(css) {
433
+ css.walkRules(selectorMerger(browsers, compatibilityCache));
222
434
  }
223
- // Partial merge: check if the rule contains a subset of the last; if
224
- // so create a joined selector with the subset, if smaller.
225
- cache = partialMerge(cache, rule);
226
- };
435
+
436
+ };
437
+ }
438
+
439
+ };
227
440
  }
228
441
 
229
- exports.default = _postcss2.default.plugin('postcss-merge-rules', () => {
230
- return (css, result) => {
231
- const resultOpts = result.opts || {};
232
- const browsers = (0, _browserslist2.default)(null, {
233
- stats: resultOpts.stats,
234
- path: __dirname,
235
- env: resultOpts.env
236
- });
237
- const compatibilityCache = {};
238
- css.walkRules(selectorMerger(browsers, compatibilityCache));
239
- };
240
- });
241
- module.exports = exports['default'];
442
+ pluginCreator.postcss = true;
443
+ var _default = pluginCreator;
444
+ exports.default = _default;
445
+ module.exports = exports.default;
@@ -1,155 +1,170 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
3
  Object.defineProperty(exports, "__esModule", {
4
- value: true
4
+ value: true
5
5
  });
6
- exports.pseudoElements = undefined;
7
6
  exports.default = ensureCompatibility;
7
+ exports.pseudoElements = void 0;
8
8
 
9
- var _caniuseApi = require('caniuse-api');
9
+ var _caniuseApi = require("caniuse-api");
10
10
 
11
- var _postcssSelectorParser = require('postcss-selector-parser');
12
-
13
- var _postcssSelectorParser2 = _interopRequireDefault(_postcssSelectorParser);
11
+ var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser"));
14
12
 
15
13
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
14
 
17
15
  const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i;
18
-
19
16
  const cssSel2 = 'css-sel2';
20
17
  const cssSel3 = 'css-sel3';
21
18
  const cssGencontent = 'css-gencontent';
22
19
  const cssFirstLetter = 'css-first-letter';
23
20
  const cssFirstLine = 'css-first-line';
24
21
  const cssInOutOfRange = 'css-in-out-of-range';
25
-
26
- const pseudoElements = exports.pseudoElements = {
27
- ':active': cssSel2,
28
- ':after': cssGencontent,
29
- ':before': cssGencontent,
30
- ':checked': cssSel3,
31
- ':default': 'css-default-pseudo',
32
- ':dir': 'css-dir-pseudo',
33
- ':disabled': cssSel3,
34
- ':empty': cssSel3,
35
- ':enabled': cssSel3,
36
- ':first-child': cssSel2,
37
- ':first-letter': cssFirstLetter,
38
- ':first-line': cssFirstLine,
39
- ':first-of-type': cssSel3,
40
- ':focus': cssSel2,
41
- ':focus-within': 'css-focus-within',
42
- ':has': 'css-has',
43
- ':hover': cssSel2,
44
- ':in-range': cssInOutOfRange,
45
- ':indeterminate': 'css-indeterminate-pseudo',
46
- ':lang': cssSel2,
47
- ':last-child': cssSel3,
48
- ':last-of-type': cssSel3,
49
- ':matches': 'css-matches-pseudo',
50
- ':not': cssSel3,
51
- ':nth-child': cssSel3,
52
- ':nth-last-child': cssSel3,
53
- ':nth-last-of-type': cssSel3,
54
- ':nth-of-type': cssSel3,
55
- ':only-child': cssSel3,
56
- ':only-of-type': cssSel3,
57
- ':optional': 'css-optional-pseudo',
58
- ':out-of-range': cssInOutOfRange,
59
- ':placeholder-shown': 'css-placeholder-shown',
60
- ':root': cssSel3,
61
- ':target': cssSel3,
62
- '::after': cssGencontent,
63
- '::backdrop': 'dialog',
64
- '::before': cssGencontent,
65
- '::first-letter': cssFirstLetter,
66
- '::first-line': cssFirstLine,
67
- '::marker': 'css-marker-pseudo',
68
- '::placeholder': 'css-placeholder',
69
- '::selection': 'css-selection'
22
+ const pseudoElements = {
23
+ ':active': cssSel2,
24
+ ':after': cssGencontent,
25
+ ':before': cssGencontent,
26
+ ':checked': cssSel3,
27
+ ':default': 'css-default-pseudo',
28
+ ':dir': 'css-dir-pseudo',
29
+ ':disabled': cssSel3,
30
+ ':empty': cssSel3,
31
+ ':enabled': cssSel3,
32
+ ':first-child': cssSel2,
33
+ ':first-letter': cssFirstLetter,
34
+ ':first-line': cssFirstLine,
35
+ ':first-of-type': cssSel3,
36
+ ':focus': cssSel2,
37
+ ':focus-within': 'css-focus-within',
38
+ ':focus-visible': 'css-focus-visible',
39
+ ':has': 'css-has',
40
+ ':hover': cssSel2,
41
+ ':in-range': cssInOutOfRange,
42
+ ':indeterminate': 'css-indeterminate-pseudo',
43
+ ':lang': cssSel2,
44
+ ':last-child': cssSel3,
45
+ ':last-of-type': cssSel3,
46
+ ':matches': 'css-matches-pseudo',
47
+ ':not': cssSel3,
48
+ ':nth-child': cssSel3,
49
+ ':nth-last-child': cssSel3,
50
+ ':nth-last-of-type': cssSel3,
51
+ ':nth-of-type': cssSel3,
52
+ ':only-child': cssSel3,
53
+ ':only-of-type': cssSel3,
54
+ ':optional': 'css-optional-pseudo',
55
+ ':out-of-range': cssInOutOfRange,
56
+ ':placeholder-shown': 'css-placeholder-shown',
57
+ ':root': cssSel3,
58
+ ':target': cssSel3,
59
+ '::after': cssGencontent,
60
+ '::backdrop': 'dialog',
61
+ '::before': cssGencontent,
62
+ '::first-letter': cssFirstLetter,
63
+ '::first-line': cssFirstLine,
64
+ '::marker': 'css-marker-pseudo',
65
+ '::placeholder': 'css-placeholder',
66
+ '::selection': 'css-selection'
70
67
  };
68
+ exports.pseudoElements = pseudoElements;
71
69
 
72
70
  function isCssMixin(selector) {
73
- return selector[selector.length - 1] === ':';
71
+ return selector[selector.length - 1] === ':';
74
72
  }
75
73
 
76
- const isSupportedCache = {};
74
+ const isSupportedCache = {}; // Move to util in future
77
75
 
78
- // Move to util in future
79
76
  function isSupportedCached(feature, browsers) {
80
- const key = JSON.stringify({ feature, browsers });
81
- let result = isSupportedCache[key];
82
-
83
- if (!result) {
84
- result = (0, _caniuseApi.isSupported)(feature, browsers);
85
- isSupportedCache[key] = result;
86
- }
87
-
88
- return result;
77
+ const key = JSON.stringify({
78
+ feature,
79
+ browsers
80
+ });
81
+ let result = isSupportedCache[key];
82
+
83
+ if (!result) {
84
+ result = (0, _caniuseApi.isSupported)(feature, browsers);
85
+ isSupportedCache[key] = result;
86
+ }
87
+
88
+ return result;
89
89
  }
90
90
 
91
91
  function ensureCompatibility(selectors, browsers, compatibilityCache) {
92
- // Should not merge mixins
93
- if (selectors.some(isCssMixin)) {
94
- return false;
92
+ // Should not merge mixins
93
+ if (selectors.some(isCssMixin)) {
94
+ return false;
95
+ }
96
+
97
+ return selectors.every(selector => {
98
+ if (simpleSelectorRe.test(selector)) {
99
+ return true;
95
100
  }
96
- return selectors.every(selector => {
97
- if (simpleSelectorRe.test(selector)) {
98
- return true;
101
+
102
+ if (compatibilityCache && selector in compatibilityCache) {
103
+ return compatibilityCache[selector];
104
+ }
105
+
106
+ let compatible = true;
107
+ (0, _postcssSelectorParser.default)(ast => {
108
+ ast.walk(node => {
109
+ const {
110
+ type,
111
+ value
112
+ } = node;
113
+
114
+ if (type === 'pseudo') {
115
+ const entry = pseudoElements[value];
116
+
117
+ if (entry && compatible) {
118
+ compatible = isSupportedCached(entry, browsers);
119
+ }
120
+ }
121
+
122
+ if (type === 'combinator') {
123
+ if (~value.indexOf('~')) {
124
+ compatible = isSupportedCached(cssSel3, browsers);
125
+ }
126
+
127
+ if (~value.indexOf('>') || ~value.indexOf('+')) {
128
+ compatible = isSupportedCached(cssSel2, browsers);
129
+ }
99
130
  }
100
- if (compatibilityCache && selector in compatibilityCache) {
101
- return compatibilityCache[selector];
131
+
132
+ if (type === 'attribute' && node.attribute) {
133
+ // [foo]
134
+ if (!node.operator) {
135
+ compatible = isSupportedCached(cssSel2, browsers);
136
+ }
137
+
138
+ if (value) {
139
+ // [foo="bar"], [foo~="bar"], [foo|="bar"]
140
+ if (~['=', '~=', '|='].indexOf(node.operator)) {
141
+ compatible = isSupportedCached(cssSel2, browsers);
142
+ } // [foo^="bar"], [foo$="bar"], [foo*="bar"]
143
+
144
+
145
+ if (~['^=', '$=', '*='].indexOf(node.operator)) {
146
+ compatible = isSupportedCached(cssSel3, browsers);
147
+ }
148
+ } // [foo="bar" i]
149
+
150
+
151
+ if (node.insensitive) {
152
+ compatible = isSupportedCached('css-case-insensitive', browsers);
153
+ }
102
154
  }
103
- let compatible = true;
104
- (0, _postcssSelectorParser2.default)(ast => {
105
- ast.walk(node => {
106
- const { type, value } = node;
107
- if (type === 'pseudo') {
108
- const entry = pseudoElements[value];
109
- if (entry && compatible) {
110
- compatible = isSupportedCached(entry, browsers);
111
- }
112
- }
113
- if (type === 'combinator') {
114
- if (~value.indexOf('~')) {
115
- compatible = isSupportedCached(cssSel3, browsers);
116
- }
117
- if (~value.indexOf('>') || ~value.indexOf('+')) {
118
- compatible = isSupportedCached(cssSel2, browsers);
119
- }
120
- }
121
- if (type === 'attribute' && node.attribute) {
122
- // [foo]
123
- if (!node.operator) {
124
- compatible = isSupportedCached(cssSel2, browsers);
125
- }
126
-
127
- if (value) {
128
- // [foo="bar"], [foo~="bar"], [foo|="bar"]
129
- if (~['=', '~=', '|='].indexOf(node.operator)) {
130
- compatible = isSupportedCached(cssSel2, browsers);
131
- }
132
- // [foo^="bar"], [foo$="bar"], [foo*="bar"]
133
- if (~['^=', '$=', '*='].indexOf(node.operator)) {
134
- compatible = isSupportedCached(cssSel3, browsers);
135
- }
136
- }
137
-
138
- // [foo="bar" i]
139
- if (node.insensitive) {
140
- compatible = isSupportedCached('css-case-insensitive', browsers);
141
- }
142
- }
143
- if (!compatible) {
144
- // If this node was not compatible,
145
- // break out early from walking the rest
146
- return false;
147
- }
148
- });
149
- }).processSync(selector);
150
- if (compatibilityCache) {
151
- compatibilityCache[selector] = compatible;
155
+
156
+ if (!compatible) {
157
+ // If this node was not compatible,
158
+ // break out early from walking the rest
159
+ return false;
152
160
  }
153
- return compatible;
154
- });
161
+ });
162
+ }).processSync(selector);
163
+
164
+ if (compatibilityCache) {
165
+ compatibilityCache[selector] = compatible;
166
+ }
167
+
168
+ return compatible;
169
+ });
155
170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-merge-rules",
3
- "version": "4.0.3",
3
+ "version": "5.0.0",
4
4
  "description": "Merge CSS rules with PostCSS.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -8,7 +8,9 @@
8
8
  "dist"
9
9
  ],
10
10
  "scripts": {
11
- "prepublish": "cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/"
11
+ "prebuild": "del-cli dist",
12
+ "build": "cross-env BABEL_ENV=publish babel src --config-file ../../babel.config.js --out-dir dist --ignore \"**/__tests__/\"",
13
+ "prepublish": "yarn build"
12
14
  },
13
15
  "keywords": [
14
16
  "css",
@@ -17,12 +19,6 @@
17
19
  "postcss-plugin"
18
20
  ],
19
21
  "license": "MIT",
20
- "devDependencies": {
21
- "babel-cli": "^6.0.0",
22
- "cross-env": "^5.0.0",
23
- "postcss-discard-comments": "^4.0.0",
24
- "postcss-simple-vars": "^5.0.1"
25
- },
26
22
  "homepage": "https://github.com/cssnano/cssnano",
27
23
  "author": {
28
24
  "name": "Ben Briggs",
@@ -31,17 +27,23 @@
31
27
  },
32
28
  "repository": "cssnano/cssnano",
33
29
  "dependencies": {
34
- "browserslist": "^4.0.0",
30
+ "browserslist": "^4.16.0",
35
31
  "caniuse-api": "^3.0.0",
36
- "cssnano-util-same-parent": "^4.0.0",
37
- "postcss": "^7.0.0",
38
- "postcss-selector-parser": "^3.0.0",
39
- "vendors": "^1.0.0"
32
+ "cssnano-utils": "^2.0.0",
33
+ "postcss-selector-parser": "^6.0.4",
34
+ "vendors": "^1.0.3"
40
35
  },
41
36
  "bugs": {
42
37
  "url": "https://github.com/cssnano/cssnano/issues"
43
38
  },
44
39
  "engines": {
45
- "node": ">=6.9.0"
46
- }
40
+ "node": "^10 || ^12 || >=14.0"
41
+ },
42
+ "devDependencies": {
43
+ "postcss": "^8.2.1"
44
+ },
45
+ "peerDependencies": {
46
+ "postcss": "^8.2.1"
47
+ },
48
+ "gitHead": "0e2c3bf5835bafcdc8783bef66f730a24194c8f3"
47
49
  }