UNPKG

cssnano

Version:

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

120 lines (109 loc) • 3.73 kB
import cssnanoUtils from '#utils'; const { asciiLowerCase } = cssnanoUtils; const cssWhitespace = new Set([' ', '\t', '\n', '\r', '\f']); // eslint-disable-next-line no-useless-escape const urlTokenEscapeChars = /[\\ \t\n\r\f\(\)"']/gv; const singleQuoteStringEscapeChars = /[\\\n\r\f']/gv; const doubleQuoteStringEscapeChars = /[\\\n\r\f"]/gv; const dataUrlRegex = /^[dD][aA][tT][aA]:/v; // Non-printable code points are a parse error in an unquoted <url-token> // (CSS Syntax 3 §4.3.6); URLs containing them keep their quotes. The // control-character match below is intentional. // oxlint-disable-next-line no-control-regex const nonPrintableRegex = /[\u0000-\u0008\u000B\u000E-\u001F\u007F]/v; /** * @param {string} match * @return {string} */ function replaceEscapeChar(match) { switch (match) { case '\n': return '\\a '; case '\r': return '\\d '; case '\f': return '\\c '; default: return '\\' + match; } } /** * Escape characters that terminate or invalidate an unquoted CSS url() token. * @param {string} value * @return {string} */ function escapeForUrlToken(value) { return value.replace(urlTokenEscapeChars, replaceEscapeChar); } /** * Escape characters for a CSS string token in the given quote context. * @param {string} value * @param {string} quote * @return {string} */ export function escapeForString(value, quote) { const pattern = quote === "'" ? singleQuoteStringEscapeChars : doubleQuoteStringEscapeChars; return value.replace(pattern, replaceEscapeChar); } /** * Trim CSS whitespace only; String.prototype.trim also removes characters * such as U+00A0 that are part of the URL. * @param {string} value * @return {string} */ function trimCSSWhitespace(value) { let start = 0; let end = value.length; while (start < end && cssWhitespace.has(value[start])) start++; while (end > start && cssWhitespace.has(value[end - 1])) end--; return value.slice(start, end); } /** * Normalize and format a url() or src() token value (unquoted or quoted). * Returns undefined when the URL is a data URL. It keeps the URL itself * byte-identical, because dot segments, duplicate slashes, host name dots and * percent-escapes are not guaranteed equivalent for every server or bundler. * @param {string} decoded * @param {string} quote * @param {string} name * @return {string | undefined} */ export function normalizeUrlTokenValue(decoded, quote, name) { let url = trimCSSWhitespace(decoded); const lowerName = asciiLowerCase(name); if (!url) { if (!name) return undefined; return lowerName === 'src' ? `${name}("")` : `${name}()`; } if (dataUrlRegex.test(url)) return undefined; // When name is empty (string with modifiers), or src() function, // quotes must be retained and unquoting is not allowed. if (!name || lowerName === 'src') { const outputQuote = quote || '"'; url = escapeForString(url, outputQuote); return name ? `${name}(${outputQuote}${url}${outputQuote})` : `${outputQuote}${url}${outputQuote}`; } let outputQuote = quote; if (nonPrintableRegex.test(url)) { // Non-printable characters are a parse error in unquoted url tokens // (CSS Syntax 3 §4.3.6), so quoted string syntax must be used. outputQuote = quote || '"'; url = escapeForString(url, outputQuote); } else if (quote) { const unquoted = escapeForUrlToken(url); const quoted = escapeForString(url, quote); if (unquoted.length < quoted.length + 2) { url = unquoted; outputQuote = ''; } else { url = quoted; } } else { url = escapeForUrlToken(url); outputQuote = ''; } return `${name}(${outputQuote}${url}${outputQuote})`; }