diff --git a/README.md b/README.md index 75e20ca..b1874f4 100644 --- a/README.md +++ b/README.md @@ -249,6 +249,20 @@ postcss([ ]).process(css); ``` +### Cascade Layer, Scope & Supports + +Merge (without sorting) cascade layers, scope and support rules as well. + +```js +postcss([ + sortMediaQueries({ + mergeAtRules: true, + }) +]).process(css); +``` + +This will only merge the ones that are not nested within selectors + --- ## Changelog diff --git a/index.js b/index.js index b474163..b8f2e17 100644 --- a/index.js +++ b/index.js @@ -1,75 +1,91 @@ function sortAtRules(queries, sort, sortCSSmq) { - if (typeof sort !== 'function') { - sort = sort === 'desktop-first' ? sortCSSmq.desktopFirst : sortCSSmq + if (typeof sort !== "function") { + sort = sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq; } - return queries.sort(sort) + return queries.sort(sort); } module.exports = (opts = {}) => { - opts = Object.assign( { - sort: 'mobile-first', + sort: "mobile-first", configuration: false, onlyTopLevel: false, + mergeAtRules: false, }, opts - ) + ); - const createSort = require('sort-css-media-queries/lib/create-sort'); - const sortCSSmq = opts.configuration ? createSort(opts.configuration) : require('sort-css-media-queries'); + const createSort = require("sort-css-media-queries/lib/create-sort"); + const sortCSSmq = opts.configuration + ? createSort(opts.configuration) + : require("sort-css-media-queries"); return { - postcssPlugin: 'postcss-sort-media-queries', - OnceExit (root, { AtRule }) { + postcssPlugin: "postcss-sort-media-queries", + OnceExit(root, { AtRule }) { + + /** + * Recursively merge media queries & layer (optionally) + * + * @param {object} localRoot Root or atRule instance + * @returns an object with queries as key values + */ + function recursivelyMergeAtRules(localRoot, match) { + let atRules = {}; - let atRules = []; + localRoot.walkAtRules(match || /(media|container)/mi, (atRule) => { + const query = atRule.params; - root.walkAtRules('media', atRule => { - if (opts.onlyTopLevel && atRule.parent.type === 'root') { - let query = atRule.params + if (!match && opts.onlyTopLevel && atRule.parent.type !== "root") return; if (!atRules[query]) { atRules[query] = new AtRule({ name: atRule.name, params: atRule.params, - source: atRule.source - }) + }); } - atRule.nodes.forEach(node => { - atRules[query].append(node.clone()) - }) + if (!opts.onlyTopLevel || match) recursivelyMergeAtRules(atRule); - atRule.remove() - } + if (atRule.nodes) { + atRule.nodes.forEach((node) => { + atRules[query].append(node.clone()); + }); + } - if (!opts.onlyTopLevel) { - let query = atRule.params + atRule.remove(); + }); - if (!atRules[query]) { - atRules[query] = new AtRule({ - name: atRule.name, - params: atRule.params, - source: atRule.source - }) - } + return atRules; + } - atRule.nodes.forEach(node => { - atRules[query].append(node.clone()) - }) + const atRules = recursivelyMergeAtRules(root); - atRule.remove() - } - }) + if (opts.mergeAtRules) { + const atRuleRegex = /(layer|scope|supports)/mi; + const rootAtRules = recursivelyMergeAtRules(root, atRuleRegex); + Object.keys(rootAtRules).reverse().forEach(nestedAtRuleKey => { + root.prepend(rootAtRules[nestedAtRuleKey]) + }); - if (atRules) { - sortAtRules(Object.keys(atRules), opts.sort, sortCSSmq).forEach(query => { - root.append(atRules[query]) + Object.keys(atRules).forEach(query => { + const nestedAtRules = recursivelyMergeAtRules(atRules[query], atRuleRegex); + Object.keys(nestedAtRules).reverse().forEach(nestedAtRuleKey => { + atRules[query].prepend(nestedAtRules[nestedAtRuleKey]) + }) }) } - } - } -} -module.exports.postcss = true + + if (atRules) { + let sortedAtRules = sortAtRules(Object.keys(atRules), opts.sort, sortCSSmq).map(query => atRules[query]); + + sortedAtRules.forEach((atRule) => { + root.append(atRule); + }); + } + }, + }; +}; +module.exports.postcss = true;