UNPKG

cssnano

Version:

A modular CSS minifier, built on top of the PostCSS ecosystem.

106 lines (98 loc) • 3.11 kB
import cssnanoUtils from '#utils'; import minifyMediaAll, { dropRedundantRangeFeatures } from './media.js'; import { minifyAspectRatios, minifyWhitespace, parentIndexes, } from './whitespace.js'; import { serializeSegments } from './tokenUtils.js'; const { TokenType, balancedTokens, endsWithEscapingBackslash, tokenEnd, tokens: tokenizeValue, } = cssnanoUtils; /** * @param {boolean} legacy * @param {import('postcss').AtRule} rule * @return {void} */ export default function transform(legacy, rule) { const ruleName = rule.name.toLowerCase(); // We should re-arrange parameters only for `@media` and `@supports` at-rules if (!rule.params || !['media', 'supports'].includes(ruleName)) return; const source = rule.raws.params?.value === rule.params ? (rule.raws.params.raw ?? rule.params) : rule.params; const structure = balancedTokens(source); if (!structure) { if (rule.raws.params?.raw) { rule.raws.params = { raw: rule.params, value: rule.params }; } return; } /** @type {{start:number,end:number,text:string}[]} */ const changes = []; const parents = parentIndexes(structure); // One walk over the top-level queries, shared by every pass below. const queries = structure.topLevelSegments(); const mediaIsUnconditional = ruleName === 'media' && minifyMediaAll(legacy, structure, queries, changes); if (mediaIsUnconditional) { rule.params = ''; if (rule.raws.params?.raw) rule.raws.params = { raw: '', value: '' }; updateAfterName(rule); return; } if (ruleName === 'media') dropRedundantRangeFeatures(structure, queries, changes); minifyWhitespace(structure, parents, changes); minifyAspectRatios(ruleName === 'supports', structure, parents, changes); const { tokens: input } = structure; const segmentRanges = queries.map(({ startIndex, endIndex }) => { const start = input[startIndex]?.[2] ?? source.length; const end = endIndex > startIndex ? tokenEnd(input[endIndex - 1]) : start; return { start, end }; }); changes.sort((a, b) => a.start - b.start); const segments = serializeSegments(source, segmentRanges, changes); rule.params = [...new Set(segments)].toSorted().join(); if (rule.raws.params?.raw) { rule.raws.params = { raw: rule.params, value: rule.params }; } updateAfterName(rule); } /** * Strip whitespace after @media or @supports when parameters begin with (. * * @param {import('postcss').AtRule} rule * @return {void} */ function updateAfterName(rule) { if (!rule.params.length) { rule.raws.afterName = ''; } else if ( rule.params.startsWith('(') && !endsWithEscapingBackslash(rule.name) ) { rule.raws.afterName = stripWhitespacePreservingComments( rule.raws.afterName ); } } /** * @param {string | undefined} raw * @return {string} */ function stripWhitespacePreservingComments(raw) { if (!raw?.includes('/*')) return ''; let result = ''; for (const [type, tokenRaw] of tokenizeValue(raw)) { if (type !== TokenType.Whitespace) { result += tokenRaw; } } return result; }