cssnano
Version:
A modular CSS minifier, built on top of the PostCSS ecosystem.
103 lines (95 loc) • 2.97 kB
JavaScript
/**
* What one selector contributes: its vendor prefix, `''` when it has none and
* `undefined` when unknown or when it mixes prefixes.
*
* @typedef {{prefix: string | undefined, msPlaceholder: boolean}} SelectorVendor
*/
/**
* What a selector list contributes to the vendor-prefix merge check.
* Merging a rule for into a rule for the same selector but with different prefixes
* would change where the rule applies.
*/
export class VendorPrefixSummary {
/**
* `prefix` is the one vendor prefix shared by every selector: `null` for an
* empty list, `''` when none is prefixed, and `undefined` when the list mixes
* prefixed and unprefixed selectors or different prefixes.
*
* @type {string | null | undefined}
* */
#prefix;
/** @type {boolean} */
#msPlaceholder;
/**
* @param {string | null | undefined} prefix
* @param {boolean} msPlaceholder
*/
constructor(prefix, msPlaceholder) {
this.#prefix = prefix;
this.#msPlaceholder = msPlaceholder;
}
/**
* @param {SelectorVendor[]} selectors
* @return {VendorPrefixSummary}
*/
static of(selectors) {
const profile = new VendorPrefixSummary(null, false);
for (const selector of selectors) profile.add(selector);
return profile;
}
/**
* Folds one selector in place, so a growing list needs no rescan.
*
* @param {SelectorVendor} selector the vendor facts of one selector;
* an unknown `prefix` blocks every merge
*/
add(selector) {
const own = selector.prefix;
if (this.#prefix === null) {
this.#prefix = own;
} else if (this.#prefix !== own) {
this.#prefix = undefined;
}
this.#msPlaceholder ||= selector.msPlaceholder;
}
/**
* The profile of this selector list followed by another, without
* rescanning. Neither operand changes.
*
* @param {VendorPrefixSummary} other
* @return {VendorPrefixSummary}
*/
concat(other) {
let prefix = this.#prefix;
if (this.#prefix === null) {
prefix = other.#prefix;
} else if (other.#prefix !== null && this.#prefix !== other.#prefix) {
prefix = undefined;
}
return new VendorPrefixSummary(
prefix,
this.#msPlaceholder || other.#msPlaceholder
);
}
/** @return {boolean} */
#isUnprefixed() {
return this.#prefix === null || this.#prefix === '';
}
/**
* Whether two selector lists may share a rule: both unprefixed, or both
* carrying the same single prefix, except that two `-ms-input-placeholder`
* lists never merge because Edge and Internet Explorer disagree on the
* pseudo's spelling.
*
* @param {VendorPrefixSummary} other
* @return {boolean}
*/
allowsMerge(other) {
if (this.#isUnprefixed() && other.#isUnprefixed()) return true;
if (this.#prefix === undefined || other.#prefix === undefined) return false;
return (
this.#prefix === other.#prefix &&
!(this.#msPlaceholder && other.#msPlaceholder)
);
}
}