Skip to content

Commit

Permalink
feat(postcss-merge-rules): merge at-rules (#722)
Browse files Browse the repository at this point in the history
  • Loading branch information
clshortfuse authored and evilebottnawi committed Mar 21, 2019
1 parent 863cf2b commit 8d4610a
Show file tree
Hide file tree
Showing 14 changed files with 115 additions and 155 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

205 changes: 63 additions & 142 deletions packages/postcss-merge-rules/src/__tests__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -621,187 +621,108 @@ test(
);

test(
'should not merge !important',
passthroughCSS,
'should merge with same at-rule parent',
processCSS,
[
'.a{left:0}',
'.b{left:0 !important}',
'@media print{h1{display:block}}',
'@media print{h1{color:red}h2{padding:10px}}',
].join(''),
[
'@media print{h1{display:block;color:red}h2{padding:10px}}',
'@media print{}',
].join('')
);

test(
'should not merge if would change prefixes overrides',
passthroughCSS,
'should merge with same at-rule parent (2)',
processCSS,
[
'.a{-webkit-transform:scaleX(-1);transform:scale(1)}',
'.b{-webkit-transform:scaleX(-1)}',
'@media (width:40px){.red{color:red}}',
'@media (width:40px){.green{color:green}}',
'@media (width:40px){.blue{color:blue}}',
'@supports (--var:var){.white{color:white}}',
'@supports (--var:var){.black{color:black}}',
].join(''),
[
'@media (width:40px){.red{color:red}.green{color:green}.blue{color:blue}}',
'@media (width:40px){}',
'@media (width:40px){}',
'@supports (--var:var){.white{color:white}.black{color:black}}',
'@supports (--var:var){}',
].join('')
);

test(
'should merge if it would not change prefix overrides',
'should merge with same nested at-rule parents',
processCSS,
[
'.a{-webkit-transform:scaleX(-1);transform:scaleY(1)}',
'.b{transform:scaleY(1);-webkit-transform:scaleX(-1)}',
'@media (width:40px){.red{color:red}}',
'@media (width:40px){.green{color:green}}',
'@media (width:40px){.blue{color:blue}}',
'@supports (--var:var){@media (width:40px){.white{color:white}}}',
'@supports (--var:var){@media (width:40px){.black{color:black}}}',
].join(''),
'.a{-webkit-transform:scaleX(-1)}.a,.b{transform:scaleY(1)}.b{-webkit-transform:scaleX(-1)}',
[
'@media (width:40px){.red{color:red}.green{color:green}.blue{color:blue}}',
'@media (width:40px){}',
'@media (width:40px){}',
'@supports (--var:var){@media (width:40px){.white{color:white}.black{color:black}}}',
'@supports (--var:var){@media (width:40px){}}',
].join('')
);

test(
'should merge if it would not change prefix overrides (2)',
processCSS,
'should not merge with different at-rule parent',
passthroughCSS,
[
'.a{transform:scaleY(1);-webkit-transform:scaleX(-1)}',
'.b{-webkit-transform:scaleX(-1);transform:scaleY(1)}',
].join(''),
'.a{transform:scaleY(1)}.a,.b{-webkit-transform:scaleX(-1)}.b{transform:scaleY(1)}',
'@media print{h1{display:block}}',
'@media screen{h1{color:red}h2{padding:10px}}',
].join('')
);

test(
'should merge if it would not change prefix overrides (3)',
processCSS,
'should not merge with different nested at-rules parents',
passthroughCSS,
[
'.a{-webkit-transform:scaleX(-1);transform:scaleY(1)}',
'.b{display:block;transform:scaleY(1)}',
'.c{display:block;-webkit-transform:scaleX(-1)}',
].join(''),
'.a{-webkit-transform:scaleX(-1)}.a,.b{transform:scaleY(1)}.b,.c{display:block}.c{-webkit-transform:scaleX(-1)}',
'@media (min-width: 48rem){.wrapper{display: block}}',
'@supports (display: flex){@media (min-width: 48rem){.wrapper{display:flex}}}',
].join('')
);

test(
'should merge if it would not change prefix overrides (4)',
processCSS,
'should not merge with different nested at-rule parents (2)',
passthroughCSS,
[
'.a{-webkit-transform:scaleX(-1);transform:scaleY(1)}',
'.b{transform:scaleY(1)}',
'.c{transform:scaleY(1);-webkit-transform:scaleX(-1)}',
].join(''),
'.a{-webkit-transform:scaleX(-1)}.a,.b,.c{transform:scaleY(1)}.c{-webkit-transform:scaleX(-1)}',

'@media print{h1{display:block}}',
'@support (color:red){@media print (color:red){h1{color:red}h2{padding:10px}}}',
].join('')
);

test(
'should merge with reorder by grouping similar prop bases',
processCSS,
`
.form-inline label {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: center;
justify-content: center;
margin-bottom: 0;
}
.form-inline .form-group {
display: -ms-flexbox;
display: flex;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
-ms-flex-align: center;
align-items: center;
margin-bottom: 0;
}
`,
`
.form-inline label {
-ms-flex-align: center;
-ms-flex-pack: center;
justify-content: center;
}
.form-inline label,.form-inline .form-group {
display: -ms-flexbox;
display: flex;
align-items: center;
margin-bottom: 0;
}
.form-inline .form-group {
-ms-flex: 0 0 auto;
flex: 0 0 auto;
-ms-flex-flow: row wrap;
flex-flow: row wrap;
-ms-flex-align: center;
}
`
);

test(
'should merge with reorder by grouping similar prop bases (2)',
processCSS,
`
.a {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
padding: 0.5rem 1rem;
}
.b {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
}
`,
`
.a {
position: relative;
padding: 0.5rem 1rem;
}
.a,.b {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
}
`
);

test(
'should allow merging of same property without changing specificity',
'should merge multiple values across at-rules',
processCSS,
[
'.a{display:block}',
'.b{display:block}',
'.c{display:block;display:inline-block}',
'@media (width:40px){h1{border:1px solid red;background-color:red;background-position:50% 100%}}',
'@media (width:40px){h1{border:1px solid red;background-color:red}}',
'@media (width:40px){h1{border:1px solid red}}',
].join(''),
'.a,.b,.c{display:block}.c{display:inline-block}',
);

test(
'should not merge if would change css variable order',
passthroughCSS,
[
'.a{--color:orange;--color-alt:pink}',
'.b{--color:orange}',
'@media (width:40px){h1{border:1px solid red;background-color:red;background-position:50% 100%}}',
'@media (width:40px){}',
'@media (width:40px){}',
].join(''),
);

test(
'should merge if it would not change css variable order',
'should partially merge selectors in the opposite direction across at-rules',
processCSS,
[
'.a{--color:orange;--color-alt:pink}',
'.b{--color-alt:pink}',
'@media (width:40px){h1{color:black}h2{color:black;font-weight:bold}}',
'@media (width:40px){h3{color:black;font-weight:bold}}',
].join(''),
[
'.a{--color:orange}',
'.a,.b{--color-alt:pink}',
'@media (width:40px){h1{color:black}h2,h3{color:black;font-weight:bold}}',
'@media (width:40px){}',
].join('')

);
41 changes: 40 additions & 1 deletion packages/postcss-merge-rules/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,17 +182,46 @@ function isConflictingProp (propA, propB) {
/**
* @param {postcss.Rule} first
* @param {postcss.Rule} second
* @return {postcss.Rule}
* @return {boolean} merged
*/
function mergeParents (first, second) {
// Null check for detached rules
if (!first.parent || !second.parent) {
return false;
}

// Check if parents share node
if (first.parent === second.parent) {
return false;
}

// sameParent() already called by canMerge()

second.remove();
first.parent.append(second);
return true;
}

/**
* @param {postcss.Rule} first
* @param {postcss.Rule} second
* @return {postcss.Rule} mergedRule
*/
function partialMerge (first, second) {
let intersection = intersect(getDecls(first), getDecls(second));
if (!intersection.length) {
return second;
}
let nextRule = second.next();
if (!nextRule) {
// Grab next cousin
const parentSibling = second.parent.next();
nextRule = parentSibling && parentSibling.nodes && parentSibling.nodes[0];
}
if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) {
let nextIntersection = intersect(getDecls(second), getDecls(nextRule));
if (nextIntersection.length > intersection.length) {
mergeParents(second, nextRule);
first = second; second = nextRule; intersection = nextIntersection;
}
}
Expand Down Expand Up @@ -287,7 +316,13 @@ function partialMerge (first, second) {
}
}

/**
* @param {string[]} browsers
* @param {Object.<string, boolean>} compatibilityCache
* @return {function(postcss.Rule)}
*/
function selectorMerger (browsers, compatibilityCache) {
/** @type {postcss.Rule} */
let cache = null;
return function (rule) {
// Prime the cache with the first rule, or alternately ensure that it is
Expand All @@ -302,6 +337,10 @@ function selectorMerger (browsers, compatibilityCache) {
cache = rule;
return;
}

// Parents merge: check if the rules have same parents, but not same parent nodes
mergeParents(cache, rule);

// Merge when declarations are exactly equal
// e.g. h1 { color: red } h2 { color: red }
if (sameDeclarationsAndOrder(getDecls(rule), getDecls(cache))) {
Expand Down

0 comments on commit 8d4610a

Please sign in to comment.