postcss-merge-rules
Advanced tools
Comparing version
@@ -1,241 +0,445 @@ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.default = void 0; | ||
var _browserslist = require('browserslist'); | ||
var _browserslist = _interopRequireDefault(require("browserslist")); | ||
var _browserslist2 = _interopRequireDefault(_browserslist); | ||
var _vendors = _interopRequireDefault(require("vendors")); | ||
var _postcss = require('postcss'); | ||
var _cssnanoUtils = require("cssnano-utils"); | ||
var _postcss2 = _interopRequireDefault(_postcss); | ||
var _ensureCompatibility = _interopRequireDefault(require("./lib/ensureCompatibility")); | ||
var _vendors = require('vendors'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
var _vendors2 = _interopRequireDefault(_vendors); | ||
/** @type {string[]} */ | ||
const prefixes = _vendors.default.map(v => `-${v}-`); | ||
/** | ||
* @param {postcss.Declaration} a | ||
* @param {postcss.Declaration} b | ||
* @return {boolean} | ||
*/ | ||
var _cssnanoUtilSameParent = require('cssnano-util-same-parent'); | ||
var _cssnanoUtilSameParent2 = _interopRequireDefault(_cssnanoUtilSameParent); | ||
function declarationIsEqual(a, b) { | ||
return a.important === b.important && a.prop === b.prop && a.value === b.value; | ||
} | ||
/** | ||
* @param {postcss.Declaration[]} array | ||
* @param {postcss.Declaration} decl | ||
* @return {number} | ||
*/ | ||
var _ensureCompatibility = require('./lib/ensureCompatibility'); | ||
var _ensureCompatibility2 = _interopRequireDefault(_ensureCompatibility); | ||
function indexOfDeclaration(array, decl) { | ||
return array.findIndex(d => declarationIsEqual(d, decl)); | ||
} | ||
/** | ||
* Returns filtered array of matched or unmatched declarations | ||
* @param {postcss.Declaration[]} a | ||
* @param {postcss.Declaration[]} b | ||
* @param {boolean} [not=false] | ||
* @return {postcss.Declaration[]} | ||
*/ | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
const prefixes = _vendors2.default.map(v => `-${v}-`); | ||
function intersect(a, b, not) { | ||
return a.filter(c => { | ||
const index = ~b.indexOf(c); | ||
return not ? !index : index; | ||
}); | ||
return a.filter(c => { | ||
const index = ~indexOfDeclaration(b, c); | ||
return not ? !index : index; | ||
}); | ||
} | ||
/** | ||
* @param {postcss.Declaration[]} a | ||
* @param {postcss.Declaration[]} b | ||
* @return {boolean} | ||
*/ | ||
// Internet Explorer use :-ms-input-placeholder. | ||
function sameDeclarationsAndOrder(a, b) { | ||
if (a.length !== b.length) { | ||
return false; | ||
} | ||
return a.every((d, index) => declarationIsEqual(d, b[index])); | ||
} // Internet Explorer use :-ms-input-placeholder. | ||
// Microsoft Edge use ::-ms-input-placeholder. | ||
const findMsInputPlaceholder = selector => ~selector.search(/-ms-input-placeholder/i); | ||
const different = (a, b) => intersect(a, b, true).concat(intersect(b, a, true)); | ||
const filterPrefixes = selector => intersect(prefixes, selector); | ||
/** | ||
* @param {string} selector | ||
* @return {string[]} | ||
*/ | ||
function filterPrefixes(selector) { | ||
return prefixes.filter(prefix => selector.indexOf(prefix) !== -1); | ||
} | ||
function sameVendor(selectorsA, selectorsB) { | ||
let same = selectors => selectors.map(filterPrefixes).join(); | ||
let findMsVendor = selectors => selectors.find(findMsInputPlaceholder); | ||
return same(selectorsA) === same(selectorsB) && !(findMsVendor(selectorsA) && findMsVendor(selectorsB)); | ||
let same = selectors => selectors.map(filterPrefixes).join(); | ||
let findMsVendor = selectors => selectors.find(findMsInputPlaceholder); | ||
return same(selectorsA) === same(selectorsB) && !(findMsVendor(selectorsA) && findMsVendor(selectorsB)); | ||
} | ||
/** | ||
* @param {string} selector | ||
* @return {boolean} | ||
*/ | ||
const noVendor = selector => !filterPrefixes(selector).length; | ||
function noVendor(selector) { | ||
return !filterPrefixes(selector).length; | ||
} | ||
/** | ||
* @param {postcss.Rule} ruleA | ||
* @param {postcss.Rule} ruleB | ||
* @param {string[]=} browsers | ||
* @param {Object.<string, boolean>=} compatibilityCache | ||
* @return {boolean} | ||
*/ | ||
function canMerge(ruleA, ruleB, browsers, compatibilityCache) { | ||
const a = ruleA.selectors; | ||
const b = ruleB.selectors; | ||
const a = ruleA.selectors; | ||
const b = ruleB.selectors; | ||
const selectors = a.concat(b); | ||
const selectors = a.concat(b); | ||
if (!(0, _ensureCompatibility.default)(selectors, browsers, compatibilityCache)) { | ||
return false; | ||
} | ||
if (!(0, _ensureCompatibility2.default)(selectors, browsers, compatibilityCache)) { | ||
return false; | ||
} | ||
const parent = (0, _cssnanoUtils.sameParent)(ruleA, ruleB); | ||
const { | ||
name | ||
} = ruleA.parent; | ||
const parent = (0, _cssnanoUtilSameParent2.default)(ruleA, ruleB); | ||
const { name } = ruleA.parent; | ||
if (parent && name && ~name.indexOf('keyframes')) { | ||
return false; | ||
} | ||
return parent && (selectors.every(noVendor) || sameVendor(a, b)); | ||
if (parent && name && ~name.indexOf('keyframes')) { | ||
return false; | ||
} | ||
return parent && (selectors.every(noVendor) || sameVendor(a, b)); | ||
} | ||
/** | ||
* @param {postcss.Rule} rule | ||
* @return {postcss.Declaration[]} | ||
*/ | ||
const getDecls = rule => rule.nodes && rule.nodes.map(String); | ||
function getDecls(rule) { | ||
return rule.nodes.filter(node => node.type === 'decl'); | ||
} | ||
const joinSelectors = (...rules) => rules.map(s => s.selector).join(); | ||
function ruleLength(...rules) { | ||
return rules.map(r => r.nodes.length ? String(r) : '').join('').length; | ||
return rules.map(r => r.nodes.length ? String(r) : '').join('').length; | ||
} | ||
/** | ||
* @param {string} prop | ||
* @return {{prefix: string, base:string, rest:string[]}} | ||
*/ | ||
function splitProp(prop) { | ||
const parts = prop.split('-'); | ||
let base, rest; | ||
// Treat vendor prefixed properties as if they were unprefixed; | ||
// moving them when combined with non-prefixed properties can | ||
// cause issues. e.g. moving -webkit-background-clip when there | ||
// is a background shorthand definition. | ||
if (prop[0] === '-') { | ||
base = parts[2]; | ||
rest = parts.slice(3); | ||
} else { | ||
base = parts[0]; | ||
rest = parts.slice(1); | ||
} | ||
return [base, rest]; | ||
// Treat vendor prefixed properties as if they were unprefixed; | ||
// moving them when combined with non-prefixed properties can | ||
// cause issues. e.g. moving -webkit-background-clip when there | ||
// is a background shorthand definition. | ||
const parts = prop.split('-'); | ||
if (prop[0] !== '-') { | ||
return { | ||
prefix: '', | ||
base: parts[0], | ||
rest: parts.slice(1) | ||
}; | ||
} // Don't split css variables | ||
if (prop[1] === '-') { | ||
return { | ||
prefix: null, | ||
base: null, | ||
rest: [prop] | ||
}; | ||
} // Found prefix | ||
return { | ||
prefix: parts[1], | ||
base: parts[2], | ||
rest: parts.slice(3) | ||
}; | ||
} | ||
/** | ||
* @param {string} propA | ||
* @param {string} propB | ||
*/ | ||
function isConflictingProp(propA, propB) { | ||
if (propA === propB) { | ||
return true; | ||
} | ||
const a = splitProp(propA); | ||
const b = splitProp(propB); | ||
return a[0] === b[0] && a[1].length !== b[1].length; | ||
if (propA === propB) { | ||
// Same specificity | ||
return true; | ||
} | ||
const a = splitProp(propA); | ||
const b = splitProp(propB); // Don't resort css variables | ||
if (!a.base && !b.base) { | ||
return true; | ||
} // Different base; | ||
if (a.base !== b.base) { | ||
return false; | ||
} // Conflict if rest-count mismatches | ||
if (a.rest.length !== b.rest.length) { | ||
return true; | ||
} // Conflict if rest parameters are equal (same but unprefixed) | ||
return a.rest.every((s, index) => b.rest[index] === s); | ||
} | ||
/** | ||
* @param {postcss.Rule} first | ||
* @param {postcss.Rule} second | ||
* @return {boolean} merged | ||
*/ | ||
function hasConflicts(declProp, notMoved) { | ||
return notMoved.some(prop => isConflictingProp(prop, declProp)); | ||
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 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; | ||
} | ||
let nextRule = second.next(); | ||
if (nextRule && nextRule.type === 'rule' && canMerge(second, nextRule)) { | ||
let nextIntersection = intersect(getDecls(second), getDecls(nextRule)); | ||
if (nextIntersection.length > intersection.length) { | ||
first = second;second = nextRule;intersection = nextIntersection; | ||
} | ||
} | ||
const firstDecls = getDecls(first); // Filter out intersections with later conflicts in First | ||
intersection = intersection.filter((decl, intersectIndex) => { | ||
const index = indexOfDeclaration(firstDecls, decl); | ||
const nextConflictInFirst = firstDecls.slice(index + 1).find(d => isConflictingProp(d.prop, decl.prop)); | ||
if (!nextConflictInFirst) { | ||
return true; | ||
} | ||
const recievingBlock = second.clone(); | ||
recievingBlock.selector = joinSelectors(first, second); | ||
recievingBlock.nodes = []; | ||
const difference = different(getDecls(first), getDecls(second)); | ||
const filterConflicts = (decls, intersectn) => { | ||
let willNotMove = []; | ||
return decls.reduce((willMove, decl) => { | ||
let intersects = ~intersectn.indexOf(decl); | ||
let prop = decl.split(':')[0]; | ||
let base = prop.split('-')[0]; | ||
let canMove = difference.every(d => d.split(':')[0] !== base); | ||
if (intersects && canMove && !hasConflicts(prop, willNotMove)) { | ||
willMove.push(decl); | ||
} else { | ||
willNotMove.push(prop); | ||
} | ||
return willMove; | ||
}, []); | ||
}; | ||
const containsAllDeclaration = intersectionList => { | ||
return intersectionList.some(declaration => { | ||
return declaration.split(':')[0].toLowerCase() === 'all'; | ||
}); | ||
}; | ||
intersection = filterConflicts(getDecls(first).reverse(), intersection); | ||
intersection = filterConflicts(getDecls(second), intersection); | ||
// Rules with "all" declarations must be on top | ||
if (containsAllDeclaration(intersection)) { | ||
second.parent.insertBefore(first, recievingBlock); | ||
} else { | ||
second.parent.insertBefore(second, recievingBlock); | ||
const nextConflictInIntersection = intersection.slice(intersectIndex + 1).find(d => isConflictingProp(d.prop, decl.prop)); | ||
if (!nextConflictInIntersection) { | ||
return false; | ||
} | ||
const firstClone = first.clone(); | ||
const secondClone = second.clone(); | ||
const moveDecl = callback => { | ||
return decl => { | ||
if (~intersection.indexOf(String(decl))) { | ||
callback.call(this, decl); | ||
} | ||
}; | ||
if (declarationIsEqual(nextConflictInFirst, nextConflictInIntersection)) { | ||
return true; | ||
} | ||
return false; | ||
}); // Filter out intersections with previous conflicts in Second | ||
const secondDecls = getDecls(second); | ||
intersection = intersection.filter(decl => { | ||
const nextConflictIndex = secondDecls.findIndex(d => isConflictingProp(d.prop, decl.prop)); | ||
if (nextConflictIndex === -1) { | ||
return false; | ||
} | ||
if (!declarationIsEqual(secondDecls[nextConflictIndex], decl)) { | ||
return false; | ||
} | ||
if (decl.prop.toLowerCase() !== 'direction' && decl.prop.toLowerCase() !== 'unicode-bidi' && secondDecls.some(declaration => declaration.prop.toLowerCase() === 'all')) { | ||
return false; | ||
} | ||
secondDecls.splice(nextConflictIndex, 1); | ||
return true; | ||
}); | ||
if (!intersection.length) { | ||
// Nothing to merge | ||
return second; | ||
} | ||
const receivingBlock = second.clone(); | ||
receivingBlock.selector = joinSelectors(first, second); | ||
receivingBlock.nodes = []; | ||
second.parent.insertBefore(second, receivingBlock); | ||
const firstClone = first.clone(); | ||
const secondClone = second.clone(); | ||
/** | ||
* @param {function(postcss.Declaration):void} callback | ||
* @return {function(postcss.Declaration)} | ||
*/ | ||
function moveDecl(callback) { | ||
return decl => { | ||
if (~indexOfDeclaration(intersection, decl)) { | ||
callback.call(this, decl); | ||
} | ||
}; | ||
firstClone.walkDecls(moveDecl(decl => { | ||
decl.remove(); | ||
recievingBlock.append(decl); | ||
})); | ||
secondClone.walkDecls(moveDecl(decl => decl.remove())); | ||
const merged = ruleLength(firstClone, recievingBlock, secondClone); | ||
const original = ruleLength(first, second); | ||
if (merged < original) { | ||
first.replaceWith(firstClone); | ||
second.replaceWith(secondClone); | ||
[firstClone, recievingBlock, secondClone].forEach(r => { | ||
if (!r.nodes.length) { | ||
r.remove(); | ||
} | ||
}); | ||
if (!secondClone.parent) { | ||
return recievingBlock; | ||
} | ||
return secondClone; | ||
} else { | ||
recievingBlock.remove(); | ||
return second; | ||
} | ||
firstClone.walkDecls(moveDecl(decl => { | ||
decl.remove(); | ||
receivingBlock.append(decl); | ||
})); | ||
secondClone.walkDecls(moveDecl(decl => decl.remove())); | ||
const merged = ruleLength(firstClone, receivingBlock, secondClone); | ||
const original = ruleLength(first, second); | ||
if (merged < original) { | ||
first.replaceWith(firstClone); | ||
second.replaceWith(secondClone); | ||
[firstClone, receivingBlock, secondClone].forEach(r => { | ||
if (!r.nodes.length) { | ||
r.remove(); | ||
} | ||
}); | ||
if (!secondClone.parent) { | ||
return receivingBlock; | ||
} | ||
return secondClone; | ||
} else { | ||
receivingBlock.remove(); | ||
return second; | ||
} | ||
} | ||
/** | ||
* @param {string[]} browsers | ||
* @param {Object.<string, boolean>} compatibilityCache | ||
* @return {function(postcss.Rule)} | ||
*/ | ||
function selectorMerger(browsers, compatibilityCache) { | ||
let cache = null; | ||
return function (rule) { | ||
// Prime the cache with the first rule, or alternately ensure that it is | ||
// safe to merge both declarations before continuing | ||
if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) { | ||
cache = rule; | ||
return; | ||
/** @type {postcss.Rule} */ | ||
let cache = null; | ||
return function (rule) { | ||
// Prime the cache with the first rule, or alternately ensure that it is | ||
// safe to merge both declarations before continuing | ||
if (!cache || !canMerge(rule, cache, browsers, compatibilityCache)) { | ||
cache = rule; | ||
return; | ||
} // Ensure that we don't deduplicate the same rule; this is sometimes | ||
// caused by a partial merge | ||
if (cache === rule) { | ||
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))) { | ||
rule.selector = joinSelectors(cache, rule); | ||
cache.remove(); | ||
cache = rule; | ||
return; | ||
} // Merge when both selectors are exactly equal | ||
// e.g. a { color: blue } a { font-weight: bold } | ||
if (cache.selector === rule.selector) { | ||
const cached = getDecls(cache); | ||
rule.walk(decl => { | ||
if (~indexOfDeclaration(cached, decl)) { | ||
return decl.remove(); | ||
} | ||
// Ensure that we don't deduplicate the same rule; this is sometimes | ||
// caused by a partial merge | ||
if (cache === rule) { | ||
cache = rule; | ||
return; | ||
cache.append(decl); | ||
}); | ||
rule.remove(); | ||
return; | ||
} // Partial merge: check if the rule contains a subset of the last; if | ||
// so create a joined selector with the subset, if smaller. | ||
cache = partialMerge(cache, rule); | ||
}; | ||
} | ||
function pluginCreator() { | ||
return { | ||
postcssPlugin: 'postcss-merge-rules', | ||
prepare(result) { | ||
const resultOpts = result.opts || {}; | ||
const browsers = (0, _browserslist.default)(null, { | ||
stats: resultOpts.stats, | ||
path: __dirname, | ||
env: resultOpts.env | ||
}); | ||
const compatibilityCache = {}; | ||
return { | ||
OnceExit(css) { | ||
css.walkRules(selectorMerger(browsers, compatibilityCache)); | ||
} | ||
// Merge when declarations are exactly equal | ||
// e.g. h1 { color: red } h2 { color: red } | ||
if (getDecls(rule).join(';') === getDecls(cache).join(';')) { | ||
rule.selector = joinSelectors(cache, rule); | ||
cache.remove(); | ||
cache = rule; | ||
return; | ||
} | ||
// Merge when both selectors are exactly equal | ||
// e.g. a { color: blue } a { font-weight: bold } | ||
if (cache.selector === rule.selector) { | ||
const cached = getDecls(cache); | ||
rule.walk(decl => { | ||
if (~cached.indexOf(String(decl))) { | ||
return decl.remove(); | ||
} | ||
cache.append(decl); | ||
}); | ||
rule.remove(); | ||
return; | ||
} | ||
// Partial merge: check if the rule contains a subset of the last; if | ||
// so create a joined selector with the subset, if smaller. | ||
cache = partialMerge(cache, rule); | ||
}; | ||
}; | ||
} | ||
}; | ||
} | ||
exports.default = _postcss2.default.plugin('postcss-merge-rules', () => { | ||
return (css, result) => { | ||
const resultOpts = result.opts || {}; | ||
const browsers = (0, _browserslist2.default)(null, { | ||
stats: resultOpts.stats, | ||
path: __dirname, | ||
env: resultOpts.env | ||
}); | ||
const compatibilityCache = {}; | ||
css.walkRules(selectorMerger(browsers, compatibilityCache)); | ||
}; | ||
}); | ||
module.exports = exports['default']; | ||
pluginCreator.postcss = true; | ||
var _default = pluginCreator; | ||
exports.default = _default; | ||
module.exports = exports.default; |
@@ -1,19 +0,16 @@ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
value: true | ||
}); | ||
exports.pseudoElements = undefined; | ||
exports.default = ensureCompatibility; | ||
exports.pseudoElements = void 0; | ||
var _caniuseApi = require('caniuse-api'); | ||
var _caniuseApi = require("caniuse-api"); | ||
var _postcssSelectorParser = require('postcss-selector-parser'); | ||
var _postcssSelectorParser = _interopRequireDefault(require("postcss-selector-parser")); | ||
var _postcssSelectorParser2 = _interopRequireDefault(_postcssSelectorParser); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
const simpleSelectorRe = /^#?[-._a-z0-9 ]+$/i; | ||
const cssSel2 = 'css-sel2'; | ||
@@ -25,132 +22,150 @@ const cssSel3 = 'css-sel3'; | ||
const cssInOutOfRange = 'css-in-out-of-range'; | ||
const pseudoElements = exports.pseudoElements = { | ||
':active': cssSel2, | ||
':after': cssGencontent, | ||
':before': cssGencontent, | ||
':checked': cssSel3, | ||
':default': 'css-default-pseudo', | ||
':dir': 'css-dir-pseudo', | ||
':disabled': cssSel3, | ||
':empty': cssSel3, | ||
':enabled': cssSel3, | ||
':first-child': cssSel2, | ||
':first-letter': cssFirstLetter, | ||
':first-line': cssFirstLine, | ||
':first-of-type': cssSel3, | ||
':focus': cssSel2, | ||
':focus-within': 'css-focus-within', | ||
':has': 'css-has', | ||
':hover': cssSel2, | ||
':in-range': cssInOutOfRange, | ||
':indeterminate': 'css-indeterminate-pseudo', | ||
':lang': cssSel2, | ||
':last-child': cssSel3, | ||
':last-of-type': cssSel3, | ||
':matches': 'css-matches-pseudo', | ||
':not': cssSel3, | ||
':nth-child': cssSel3, | ||
':nth-last-child': cssSel3, | ||
':nth-last-of-type': cssSel3, | ||
':nth-of-type': cssSel3, | ||
':only-child': cssSel3, | ||
':only-of-type': cssSel3, | ||
':optional': 'css-optional-pseudo', | ||
':out-of-range': cssInOutOfRange, | ||
':placeholder-shown': 'css-placeholder-shown', | ||
':root': cssSel3, | ||
':target': cssSel3, | ||
'::after': cssGencontent, | ||
'::backdrop': 'dialog', | ||
'::before': cssGencontent, | ||
'::first-letter': cssFirstLetter, | ||
'::first-line': cssFirstLine, | ||
'::marker': 'css-marker-pseudo', | ||
'::placeholder': 'css-placeholder', | ||
'::selection': 'css-selection' | ||
const pseudoElements = { | ||
':active': cssSel2, | ||
':after': cssGencontent, | ||
':before': cssGencontent, | ||
':checked': cssSel3, | ||
':default': 'css-default-pseudo', | ||
':dir': 'css-dir-pseudo', | ||
':disabled': cssSel3, | ||
':empty': cssSel3, | ||
':enabled': cssSel3, | ||
':first-child': cssSel2, | ||
':first-letter': cssFirstLetter, | ||
':first-line': cssFirstLine, | ||
':first-of-type': cssSel3, | ||
':focus': cssSel2, | ||
':focus-within': 'css-focus-within', | ||
':focus-visible': 'css-focus-visible', | ||
':has': 'css-has', | ||
':hover': cssSel2, | ||
':in-range': cssInOutOfRange, | ||
':indeterminate': 'css-indeterminate-pseudo', | ||
':lang': cssSel2, | ||
':last-child': cssSel3, | ||
':last-of-type': cssSel3, | ||
':matches': 'css-matches-pseudo', | ||
':not': cssSel3, | ||
':nth-child': cssSel3, | ||
':nth-last-child': cssSel3, | ||
':nth-last-of-type': cssSel3, | ||
':nth-of-type': cssSel3, | ||
':only-child': cssSel3, | ||
':only-of-type': cssSel3, | ||
':optional': 'css-optional-pseudo', | ||
':out-of-range': cssInOutOfRange, | ||
':placeholder-shown': 'css-placeholder-shown', | ||
':root': cssSel3, | ||
':target': cssSel3, | ||
'::after': cssGencontent, | ||
'::backdrop': 'dialog', | ||
'::before': cssGencontent, | ||
'::first-letter': cssFirstLetter, | ||
'::first-line': cssFirstLine, | ||
'::marker': 'css-marker-pseudo', | ||
'::placeholder': 'css-placeholder', | ||
'::selection': 'css-selection' | ||
}; | ||
exports.pseudoElements = pseudoElements; | ||
function isCssMixin(selector) { | ||
return selector[selector.length - 1] === ':'; | ||
return selector[selector.length - 1] === ':'; | ||
} | ||
const isSupportedCache = {}; | ||
const isSupportedCache = {}; // Move to util in future | ||
// Move to util in future | ||
function isSupportedCached(feature, browsers) { | ||
const key = JSON.stringify({ feature, browsers }); | ||
let result = isSupportedCache[key]; | ||
const key = JSON.stringify({ | ||
feature, | ||
browsers | ||
}); | ||
let result = isSupportedCache[key]; | ||
if (!result) { | ||
result = (0, _caniuseApi.isSupported)(feature, browsers); | ||
isSupportedCache[key] = result; | ||
} | ||
if (!result) { | ||
result = (0, _caniuseApi.isSupported)(feature, browsers); | ||
isSupportedCache[key] = result; | ||
} | ||
return result; | ||
return result; | ||
} | ||
function ensureCompatibility(selectors, browsers, compatibilityCache) { | ||
// Should not merge mixins | ||
if (selectors.some(isCssMixin)) { | ||
return false; | ||
// Should not merge mixins | ||
if (selectors.some(isCssMixin)) { | ||
return false; | ||
} | ||
return selectors.every(selector => { | ||
if (simpleSelectorRe.test(selector)) { | ||
return true; | ||
} | ||
return selectors.every(selector => { | ||
if (simpleSelectorRe.test(selector)) { | ||
return true; | ||
if (compatibilityCache && selector in compatibilityCache) { | ||
return compatibilityCache[selector]; | ||
} | ||
let compatible = true; | ||
(0, _postcssSelectorParser.default)(ast => { | ||
ast.walk(node => { | ||
const { | ||
type, | ||
value | ||
} = node; | ||
if (type === 'pseudo') { | ||
const entry = pseudoElements[value]; | ||
if (entry && compatible) { | ||
compatible = isSupportedCached(entry, browsers); | ||
} | ||
} | ||
if (compatibilityCache && selector in compatibilityCache) { | ||
return compatibilityCache[selector]; | ||
if (type === 'combinator') { | ||
if (~value.indexOf('~')) { | ||
compatible = isSupportedCached(cssSel3, browsers); | ||
} | ||
if (~value.indexOf('>') || ~value.indexOf('+')) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} | ||
} | ||
let compatible = true; | ||
(0, _postcssSelectorParser2.default)(ast => { | ||
ast.walk(node => { | ||
const { type, value } = node; | ||
if (type === 'pseudo') { | ||
const entry = pseudoElements[value]; | ||
if (entry && compatible) { | ||
compatible = isSupportedCached(entry, browsers); | ||
} | ||
} | ||
if (type === 'combinator') { | ||
if (~value.indexOf('~')) { | ||
compatible = isSupportedCached(cssSel3, browsers); | ||
} | ||
if (~value.indexOf('>') || ~value.indexOf('+')) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} | ||
} | ||
if (type === 'attribute' && node.attribute) { | ||
// [foo] | ||
if (!node.operator) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} | ||
if (value) { | ||
// [foo="bar"], [foo~="bar"], [foo|="bar"] | ||
if (~['=', '~=', '|='].indexOf(node.operator)) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} | ||
// [foo^="bar"], [foo$="bar"], [foo*="bar"] | ||
if (~['^=', '$=', '*='].indexOf(node.operator)) { | ||
compatible = isSupportedCached(cssSel3, browsers); | ||
} | ||
} | ||
if (type === 'attribute' && node.attribute) { | ||
// [foo] | ||
if (!node.operator) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} | ||
// [foo="bar" i] | ||
if (node.insensitive) { | ||
compatible = isSupportedCached('css-case-insensitive', browsers); | ||
} | ||
} | ||
if (!compatible) { | ||
// If this node was not compatible, | ||
// break out early from walking the rest | ||
return false; | ||
} | ||
}); | ||
}).processSync(selector); | ||
if (compatibilityCache) { | ||
compatibilityCache[selector] = compatible; | ||
if (value) { | ||
// [foo="bar"], [foo~="bar"], [foo|="bar"] | ||
if (~['=', '~=', '|='].indexOf(node.operator)) { | ||
compatible = isSupportedCached(cssSel2, browsers); | ||
} // [foo^="bar"], [foo$="bar"], [foo*="bar"] | ||
if (~['^=', '$=', '*='].indexOf(node.operator)) { | ||
compatible = isSupportedCached(cssSel3, browsers); | ||
} | ||
} // [foo="bar" i] | ||
if (node.insensitive) { | ||
compatible = isSupportedCached('css-case-insensitive', browsers); | ||
} | ||
} | ||
return compatible; | ||
}); | ||
if (!compatible) { | ||
// If this node was not compatible, | ||
// break out early from walking the rest | ||
return false; | ||
} | ||
}); | ||
}).processSync(selector); | ||
if (compatibilityCache) { | ||
compatibilityCache[selector] = compatible; | ||
} | ||
return compatible; | ||
}); | ||
} |
{ | ||
"name": "postcss-merge-rules", | ||
"version": "4.0.3", | ||
"version": "5.0.0-rc.0", | ||
"description": "Merge CSS rules with PostCSS.", | ||
@@ -11,3 +11,5 @@ "main": "dist/index.js", | ||
"scripts": { | ||
"prepublish": "cross-env BABEL_ENV=publish babel src --out-dir dist --ignore /__tests__/" | ||
"prebuild": "del-cli dist", | ||
"build": "cross-env BABEL_ENV=publish babel src --config-file ../../babel.config.js --out-dir dist --ignore \"**/__tests__/\"", | ||
"prepublish": "yarn build" | ||
}, | ||
@@ -21,8 +23,2 @@ "keywords": [ | ||
"license": "MIT", | ||
"devDependencies": { | ||
"babel-cli": "^6.0.0", | ||
"cross-env": "^5.0.0", | ||
"postcss-discard-comments": "^4.0.0", | ||
"postcss-simple-vars": "^5.0.1" | ||
}, | ||
"homepage": "https://github.com/cssnano/cssnano", | ||
@@ -36,8 +32,7 @@ "author": { | ||
"dependencies": { | ||
"browserslist": "^4.0.0", | ||
"browserslist": "^4.16.0", | ||
"caniuse-api": "^3.0.0", | ||
"cssnano-util-same-parent": "^4.0.0", | ||
"postcss": "^7.0.0", | ||
"postcss-selector-parser": "^3.0.0", | ||
"vendors": "^1.0.0" | ||
"cssnano-utils": "^2.0.0-rc.0", | ||
"postcss-selector-parser": "^6.0.4", | ||
"vendors": "^1.0.3" | ||
}, | ||
@@ -48,4 +43,11 @@ "bugs": { | ||
"engines": { | ||
"node": ">=6.9.0" | ||
} | ||
"node": "^10 || ^12 || >=14.0" | ||
}, | ||
"devDependencies": { | ||
"postcss": "^8.2.1" | ||
}, | ||
"peerDependencies": { | ||
"postcss": "^8.2.1" | ||
}, | ||
"gitHead": "8c16e67a4d24a13ac7e09a36d4faf504196efd0f" | ||
} |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
21682
29.79%1
-75%6
20%485
39.77%1
Infinity%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated