postcss-merge-rules 8.0.2 → 8.0.3

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-merge-rules",
3
- "version": "8.0.2",
3
+ "version": "8.0.3",
4
4
  "description": "Merge CSS rules with PostCSS.",
5
5
  "exports": {
6
6
  ".": {
@@ -43,10 +43,10 @@
43
43
  "url": "cssnano/cssnano"
44
44
  },
45
45
  "dependencies": {
46
- "browserslist": "^4.28.7",
46
+ "browserslist": "^4.28.8",
47
47
  "caniuse-api": "^4.0.0",
48
- "postcss-selector-parser": "^7.1.4",
49
- "cssnano-utils": "^6.0.2"
48
+ "postcss-selector-parser": "^7.1.5",
49
+ "cssnano-utils": "^6.0.3"
50
50
  },
51
51
  "bugs": {
52
52
  "url": "https://github.com/cssnano/cssnano/issues"
@@ -55,11 +55,14 @@
55
55
  "node": "^22.11.0 || ^24.11.0 || >=26.0"
56
56
  },
57
57
  "devDependencies": {
58
- "postcss": "^8.5.25",
58
+ "postcss": "^8.5.26",
59
59
  "postcss-simple-vars": "^7.0.1",
60
- "postcss-discard-comments": "^8.0.2"
60
+ "postcss-discard-comments": "^8.0.3"
61
61
  },
62
62
  "peerDependencies": {
63
- "postcss": "^8.5.25"
63
+ "postcss": "^8.5.26"
64
+ },
65
+ "scripts": {
66
+ "test": "node --test"
64
67
  }
65
68
  }
package/src/index.js CHANGED
@@ -300,45 +300,47 @@ function mergeParents(first, second) {
300
300
  return true;
301
301
  }
302
302
 
303
+ /**
304
+ * @param {Rule} second
305
+ * @return {Rule | null}
306
+ */
307
+ function getNextRule(second) {
308
+ let nextRule = second.next();
309
+ if (!nextRule) {
310
+ // Grab next cousin
311
+ /** @type {any} */
312
+ const parentSibling =
313
+ /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
314
+ second.parent
315
+ ).next();
316
+ nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
317
+ }
318
+ return nextRule?.type === 'rule' ? nextRule : null;
319
+ }
320
+
303
321
  /**
304
322
  * @param {Rule} first
305
323
  * @param {Rule} second
324
+ * @param {Declaration[]} intersection
306
325
  * @param {string[]} browsers
307
326
  * @param {Map<string, boolean>} compatibilityCache
308
327
  * @param {WeakSet<Rule>} ruleCache
309
328
  * @param {WeakMap<Rule, RuleMeta>} ruleMeta
310
- * @return {Rule} mergedRule
329
+ * @return {{first: Rule, second: Rule, intersection: Declaration[]}}
311
330
  */
312
- function partialMerge(
331
+ function mergeWithNextRule(
313
332
  first,
314
333
  second,
334
+ intersection,
315
335
  browsers,
316
336
  compatibilityCache,
317
337
  ruleCache,
318
338
  ruleMeta
319
339
  ) {
320
- if (ruleMeta) {
321
- flush(first, ruleMeta);
322
- }
323
- const metaFirst = getMeta(first, ruleMeta);
324
- const metaSecond = getMeta(second, ruleMeta);
325
- let intersection = intersect(metaFirst.declarations, metaSecond.declarations);
326
- if (intersection.length === 0) {
327
- return second;
328
- }
329
- let nextRule = second.next();
330
- if (!nextRule) {
331
- // Grab next cousin
332
- /** @type {any} */
333
- const parentSibling =
334
- /** @type {import('postcss').Container<import('postcss').ChildNode>} */ (
335
- second.parent
336
- ).next();
337
- nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
338
- }
340
+ const nextRule = getNextRule(second);
339
341
  if (
340
- nextRule?.type === 'rule' &&
341
- canMerge(
342
+ !nextRule ||
343
+ !canMerge(
342
344
  second,
343
345
  nextRule,
344
346
  browsers,
@@ -347,24 +349,28 @@ function partialMerge(
347
349
  ruleMeta
348
350
  )
349
351
  ) {
350
- const metaNext = getMeta(nextRule, ruleMeta);
351
- const nextIntersection = intersect(
352
- metaSecond.declarations,
353
- metaNext.declarations
354
- );
355
- if (nextIntersection.length > intersection.length) {
356
- mergeParents(second, nextRule);
357
- first = second;
358
- second = nextRule;
359
- intersection = nextIntersection;
360
- }
352
+ return { first, second, intersection };
361
353
  }
362
354
 
363
- const metaFirstActual = getMeta(first, ruleMeta);
364
- const metaSecondActual = getMeta(second, ruleMeta);
365
- const firstDecls = [...metaFirstActual.declarations];
366
- const secondDecls = [...metaSecondActual.declarations];
355
+ const nextIntersection = intersect(
356
+ getMeta(second, ruleMeta).declarations,
357
+ getMeta(nextRule, ruleMeta).declarations
358
+ );
359
+ if (nextIntersection.length <= intersection.length) {
360
+ return { first, second, intersection };
361
+ }
362
+
363
+ mergeParents(second, nextRule);
364
+ return { first: second, second: nextRule, intersection: nextIntersection };
365
+ }
367
366
 
367
+ /**
368
+ * @param {Declaration[]} intersection
369
+ * @param {Declaration[]} firstDecls
370
+ * @param {Declaration[]} secondDecls
371
+ * @return {Declaration[]}
372
+ */
373
+ function filterIntersections(intersection, firstDecls, secondDecls) {
368
374
  // Filter out intersections with later conflicts in First
369
375
  intersection = intersection.filter((decl, intersectIndex) => {
370
376
  const indexOfDecl = indexOfDeclaration(firstDecls, decl);
@@ -386,7 +392,7 @@ function partialMerge(
386
392
  });
387
393
 
388
394
  // Filter out intersections with previous conflicts in Second
389
- intersection = intersection.filter((decl) => {
395
+ return intersection.filter((decl) => {
390
396
  const nextConflictIndex = secondDecls.findIndex((d) =>
391
397
  isConflictingProp(d.prop, decl.prop)
392
398
  );
@@ -408,15 +414,20 @@ function partialMerge(
408
414
  secondDecls.splice(nextConflictIndex, 1);
409
415
  return true;
410
416
  });
417
+ }
411
418
 
412
- if (intersection.length === 0) {
413
- // Nothing to merge
414
- return second;
415
- }
416
-
419
+ /**
420
+ * @param {Rule} first
421
+ * @param {Rule} second
422
+ * @param {Declaration[]} intersection
423
+ * @param {WeakSet<Rule>} ruleCache
424
+ * @param {WeakMap<Rule, RuleMeta>} ruleMeta
425
+ * @return {Rule}
426
+ */
427
+ function buildMergedRule(first, second, intersection, ruleCache, ruleMeta) {
417
428
  const receivingBlock = second.clone();
418
- const firstSelectors = metaFirstActual.selectors;
419
- const secondSelectors = metaSecondActual.selectors;
429
+ const firstSelectors = getMeta(first, ruleMeta).selectors;
430
+ const secondSelectors = getMeta(second, ruleMeta).selectors;
420
431
 
421
432
  receivingBlock.selector = [...firstSelectors, ...secondSelectors].join();
422
433
  receivingBlock.nodes = [];
@@ -464,11 +475,11 @@ function partialMerge(
464
475
  if (merged < original) {
465
476
  first.replaceWith(firstClone);
466
477
  second.replaceWith(secondClone);
467
- [firstClone, receivingBlock, secondClone].forEach((r) => {
478
+ for (const r of [firstClone, receivingBlock, secondClone]) {
468
479
  if (r.nodes.length === 0) {
469
480
  r.remove();
470
481
  }
471
- });
482
+ }
472
483
  if (!secondClone.parent) {
473
484
  ruleCache?.add(receivingBlock);
474
485
  return receivingBlock;
@@ -478,10 +489,64 @@ function partialMerge(
478
489
  ruleMeta?.delete(first);
479
490
  ruleMeta?.delete(second);
480
491
  return secondClone;
481
- } else {
482
- receivingBlock.remove();
492
+ }
493
+
494
+ receivingBlock.remove();
495
+ return second;
496
+ }
497
+
498
+ /**
499
+ * @param {Rule} first
500
+ * @param {Rule} second
501
+ * @param {string[]} browsers
502
+ * @param {Map<string, boolean>} compatibilityCache
503
+ * @param {WeakSet<Rule>} ruleCache
504
+ * @param {WeakMap<Rule, RuleMeta>} ruleMeta
505
+ * @return {Rule} mergedRule
506
+ */
507
+ function partialMerge(
508
+ first,
509
+ second,
510
+ browsers,
511
+ compatibilityCache,
512
+ ruleCache,
513
+ ruleMeta
514
+ ) {
515
+ if (ruleMeta) {
516
+ flush(first, ruleMeta);
517
+ }
518
+ const metaFirst = getMeta(first, ruleMeta);
519
+ const metaSecond = getMeta(second, ruleMeta);
520
+ let intersection = intersect(metaFirst.declarations, metaSecond.declarations);
521
+ if (intersection.length === 0) {
483
522
  return second;
484
523
  }
524
+ const mergedNext = mergeWithNextRule(
525
+ first,
526
+ second,
527
+ intersection,
528
+ browsers,
529
+ compatibilityCache,
530
+ ruleCache,
531
+ ruleMeta
532
+ );
533
+ first = mergedNext.first;
534
+ second = mergedNext.second;
535
+ intersection = mergedNext.intersection;
536
+
537
+ const metaFirstActual = getMeta(first, ruleMeta);
538
+ const metaSecondActual = getMeta(second, ruleMeta);
539
+ const firstDecls = [...metaFirstActual.declarations];
540
+ const secondDecls = [...metaSecondActual.declarations];
541
+
542
+ intersection = filterIntersections(intersection, firstDecls, secondDecls);
543
+
544
+ if (intersection.length === 0) {
545
+ // Nothing to merge
546
+ return second;
547
+ }
548
+
549
+ return buildMergedRule(first, second, intersection, ruleCache, ruleMeta);
485
550
  }
486
551
 
487
552
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;AAII,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAQxC,OAAQ,KAAA,EAAC,WAAW,EAAO,MAAM,SAAS,CAAA;AAmD3C,YAAkB,QAAQ,GAC1B;;;;IAAqB,SAAS,EAAnB,MAAM,EAAE,CACnB;;;;IAA0B,YAAY,EAA3B,WAAW,EAAE,CACxB;;;;IAAoB,KAAK,EAAd,OAAO,CACpB;CAAA,CAAA;AAygBE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":";;AAII,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAQxC,OAAQ,KAAA,EAAC,WAAW,EAAO,MAAM,SAAS,CAAA;AAmD3C,YAAkB,QAAQ,GAC1B;;;;IAAqB,SAAS,EAAnB,MAAM,EAAE,CACnB;;;;IAA0B,YAAY,EAA3B,WAAW,EAAE,CACxB;;;;IAAoB,KAAK,EAAd,OAAO,CACpB;CAAA,CAAA;AA0kBE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAAqD,OAAO,GAAlD,mBAAmB,GAAG,mBAAmB,CAAS"}