'use strict'; const selectorParser = require('postcss-selector-parser'); /** * @param {string} selectors * @return {string} */ function generateUniqueSelector(selectors) { /** @type {Map} */ const uniqueSelectors = new Map(); /** @type {selectorParser.SyncProcessor} */ const collectUniqueSelectors = (selNode) => { for (const node of selNode.nodes) { /** @type {string[]} */ const comments = []; // Duplicates are removed by stripping the comments and using the results as the Map key. const keyNode = node.clone(); keyNode.walk((sel) => { if (sel.type === 'comment') { comments.push(sel.value); sel.remove(); } }); const key = keyNode.toString().trim(); const dupeSelector = uniqueSelectors.get(key); if (!dupeSelector) { uniqueSelectors.set(key, node.toString()); } else if (comments.length) { // If the duplicate selector has a comment, it is concatenated to the end of the selector. uniqueSelectors.set(key, `${dupeSelector}${comments.join('')}`); } } }; selectorParser(collectUniqueSelectors).processSync(selectors); return [...uniqueSelectors.entries()] .sort(([a], [b]) => (a > b ? 1 : a < b ? -1 : 0)) .map(([, selector]) => selector) .join(); } /** * @type {import('postcss').PluginCreator} * @return {import('postcss').Plugin} */ function pluginCreator() { return { postcssPlugin: 'postcss-unique-selectors', OnceExit(css) { css.walkRules((nodes) => { if (nodes.raws.selector && nodes.raws.selector.raw) { nodes.raws.selector.raw = generateUniqueSelector( nodes.raws.selector.raw ); } else { nodes.selector = generateUniqueSelector(nodes.selector); } }); }, }; } pluginCreator.postcss = true; module.exports = pluginCreator;