UNPKG

postcss-colormin

Version:

Minify colors in your CSS files with PostCSS.

180 lines (154 loc) 4.77 kB
'use strict'; const { dirname } = require('path'); const browserslist = require('browserslist'); const { isSupported } = require('caniuse-api'); const valueParser = require('postcss-value-parser'); const minifyColor = require('./minifyColor'); /** * @param {{nodes: valueParser.Node[]}} parent * @param {(node: valueParser.Node, index: number, parent: {nodes: valueParser.Node[]}) => false | undefined} callback * @return {void} */ function walk(parent, callback) { parent.nodes.forEach((node, index) => { const bubble = callback(node, index, parent); if (node.type === 'function' && bubble !== false) { walk(node, callback); } }); } /* * IE 8 & 9 do not properly handle clicks on elements * with a `transparent` `background-color`. * * https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer */ const browsersWithTransparentBug = new Set(['ie 8', 'ie 9']); const mathFunctions = new Set(['calc', 'min', 'max', 'clamp']); /** * @param {valueParser.Node} node * @return {boolean} */ function isMathFunctionNode(node) { if (node.type !== 'function') { return false; } return mathFunctions.has(node.value.toLowerCase()); } /** * @param {string} value * @param {Options} options * @return {string} */ function transform(value, options) { const parsed = valueParser(value); walk(parsed, (node, index, parent) => { if (node.type === 'function') { if (/^(rgb|hsl)a?$/i.test(node.value)) { const { value: originalValue } = node; node.value = minifyColor(valueParser.stringify(node), options); /** @type {string} */ (node.type) = 'word'; const next = parent.nodes[index + 1]; if ( node.value !== originalValue && next && (next.type === 'word' || next.type === 'function') ) { parent.nodes.splice( index + 1, 0, /** @type {valueParser.SpaceNode} */ ({ type: 'space', value: ' ', }) ); } } else if (isMathFunctionNode(node)) { return false; } } else if (node.type === 'word') { node.value = minifyColor(node.value, options); } }); return parsed.toString(); } /** * @param {Options} options * @param {string[]} browsers * @return {Options} */ function addPluginDefaults(options, browsers) { const defaults = { // Does the browser support 4 & 8 character hex notation transparent: browsers.some((b) => browsersWithTransparentBug.has(b)) === false, // Does the browser support "transparent" value properly alphaHex: isSupported('css-rrggbbaa', browsers), name: true, }; return { ...defaults, ...options }; } /** * @typedef {object} MinifyColorOptions * @property {boolean} [hex] * @property {boolean} [alphaHex] * @property {boolean} [rgb] * @property {boolean} [hsl] * @property {boolean} [name] * @property {boolean} [transparent] */ /** * @typedef {{ overrideBrowserslist?: string | string[] }} AutoprefixerOptions * @typedef {Pick<browserslist.Options, 'stats' | 'path' | 'env'>} BrowserslistOptions * @typedef {MinifyColorOptions & AutoprefixerOptions & BrowserslistOptions} Options */ /** * @type {import('postcss').PluginCreator<Options>} * @param {Options} config * @return {import('postcss').Plugin} */ function pluginCreator(config = {}) { return { postcssPlugin: 'postcss-colormin', /** * @param {import('postcss').Result & {opts: BrowserslistOptions & {file?: string}}} result */ prepare(result) { const { stats, env, from, file } = result.opts || {}; const browsers = browserslist(config.overrideBrowserslist, { stats: config.stats || stats, path: config.path || dirname(from || file || __filename), env: config.env || env, }); const cache = new Map(); const options = addPluginDefaults(config, browsers); return { OnceExit(css) { css.walkDecls((decl) => { if ( /^(composes|font|src$|filter|-webkit-tap-highlight-color)/i.test( decl.prop ) ) { return; } const value = decl.value; if (!value) { return; } const cacheKey = JSON.stringify({ value, options, browsers }); if (cache.has(cacheKey)) { decl.value = cache.get(cacheKey); return; } const newValue = transform(value, options); decl.value = newValue; cache.set(cacheKey, newValue); }); }, }; }, }; } pluginCreator.postcss = true; module.exports = pluginCreator;