postcss-merge-longhand
Version:
Merge longhand properties into shorthand with PostCSS.
82 lines (77 loc) • 2.37 kB
JavaScript
import cssnanoUtils from 'cssnano-utils';
import stylehacks from 'stylehacks';
import { normalizeValue } from './minifyShorthandValues.js';
const { asciiLowerCase } = cssnanoUtils;
/**
* Shorthand properties whose redundant axes, sides, or default components can be
* algebraically folded in-place without inter-declaration data dependencies.
*/
export const foldableShorthands = new Set([
'aspect-ratio',
'border-spacing',
'gap',
'inset',
'overflow',
'overscroll-behavior',
'place-content',
'place-items',
'place-self',
'scroll-margin',
'scroll-padding',
'transition',
'-webkit-transition',
]);
/**
* In-place peephole rewrite: folds identity components of a shorthand declaration
* using a memoization table to cache pure value transformations across the AST.
*
* @param {import('postcss').Declaration} decl
* @param {Map<string, string | null>} [memoTable]
* @param {string} [property] - the lowercased property name, when known
* @return {void}
*/
export function foldShorthandDeclaration(
decl,
memoTable,
property = asciiLowerCase(decl.prop)
) {
if (!foldableShorthands.has(property) || stylehacks.detect(decl)) return;
const sourceValue =
decl.raws.value?.value === decl.value
? (decl.raws.value.raw ?? decl.value)
: decl.value;
const memoKey = `${property}\0${sourceValue}`;
let canonicalValue;
if (memoTable) {
canonicalValue = memoTable.get(memoKey);
if (canonicalValue === undefined && !memoTable.has(memoKey)) {
canonicalValue = normalizeValue(property, sourceValue);
memoTable.set(memoKey, canonicalValue);
}
} else {
canonicalValue = normalizeValue(property, sourceValue);
}
if (
canonicalValue === undefined ||
canonicalValue === null ||
canonicalValue === decl.value
) {
return;
}
const oldValue = decl.value;
decl.value = canonicalValue;
if (decl.raws.value?.value === oldValue) {
decl.raws.value = { raw: canonicalValue, value: canonicalValue };
}
}
/**
* Full AST traversal fallback for standalone shorthand identity canonicalization.
*
* @param {import('postcss').Root} root
* @return {void}
*/
export default function minifyShorthandIdentities(root) {
/** @type {Map<string, string | null>} */
const memoTable = new Map();
root.walkDecls((decl) => foldShorthandDeclaration(decl, memoTable));
}