postcss-colormin 9.0.3 → 9.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postcss-colormin",
3
- "version": "9.0.3",
3
+ "version": "9.0.4",
4
4
  "description": "Minify colors in your CSS files with PostCSS.",
5
5
  "exports": {
6
6
  ".": {
@@ -43,10 +43,11 @@
43
43
  "url": "git+https://github.com/cssnano/cssnano.git"
44
44
  },
45
45
  "dependencies": {
46
- "@colordx/core": "^6.5.0",
47
- "browserslist": "^4.29.0",
46
+ "@colordx/core": "^7.2.0",
47
+ "@csstools/css-tokenizer": "^4.0.2",
48
+ "browserslist": "^4.29.1",
48
49
  "caniuse-api": "^4.0.0",
49
- "postcss-value-parser": "^4.2.0"
50
+ "cssnano-utils": "^8.0.0"
50
51
  },
51
52
  "bugs": {
52
53
  "url": "https://github.com/cssnano/cssnano/issues"
@@ -55,6 +56,7 @@
55
56
  "node": "^22.22.3 || ^24.15.0 || >=26.0"
56
57
  },
57
58
  "devDependencies": {
59
+ "@webref/css": "8.7.5",
58
60
  "postcss": "^8.5.28"
59
61
  },
60
62
  "peerDependencies": {
@@ -62,6 +64,7 @@
62
64
  },
63
65
  "type": "module",
64
66
  "scripts": {
65
- "test": "node --test"
67
+ "test": "node --test",
68
+ "acquire": "node ./script/acquire.mjs"
66
69
  }
67
70
  }
@@ -0,0 +1,60 @@
1
+ [
2
+ "-webkit-text-fill-color",
3
+ "-webkit-text-stroke",
4
+ "-webkit-text-stroke-color",
5
+ "accent-color",
6
+ "background",
7
+ "background-color",
8
+ "background-image",
9
+ "border",
10
+ "border-block",
11
+ "border-block-color",
12
+ "border-block-end",
13
+ "border-block-end-color",
14
+ "border-block-start",
15
+ "border-block-start-color",
16
+ "border-bottom",
17
+ "border-bottom-color",
18
+ "border-color",
19
+ "border-image",
20
+ "border-image-source",
21
+ "border-inline",
22
+ "border-inline-color",
23
+ "border-inline-end",
24
+ "border-inline-end-color",
25
+ "border-inline-start",
26
+ "border-inline-start-color",
27
+ "border-left",
28
+ "border-left-color",
29
+ "border-right",
30
+ "border-right-color",
31
+ "border-top",
32
+ "border-top-color",
33
+ "box-shadow",
34
+ "box-shadow-color",
35
+ "caret",
36
+ "caret-color",
37
+ "color",
38
+ "column-rule",
39
+ "column-rule-color",
40
+ "fill",
41
+ "fill-color",
42
+ "flood-color",
43
+ "lighting-color",
44
+ "outline",
45
+ "outline-color",
46
+ "row-rule",
47
+ "row-rule-color",
48
+ "rule",
49
+ "rule-color",
50
+ "scrollbar-color",
51
+ "stop-color",
52
+ "stroke",
53
+ "stroke-color",
54
+ "tap-highlight-color",
55
+ "text-decoration",
56
+ "text-decoration-color",
57
+ "text-emphasis",
58
+ "text-emphasis-color",
59
+ "text-shadow"
60
+ ]
package/src/index.js CHANGED
@@ -1,29 +1,26 @@
1
1
  import getBrowsersList from '#getBrowsersList';
2
2
  import caniuseApi from 'caniuse-api';
3
- import valueParser from 'postcss-value-parser';
3
+ import { colordx as colord } from '@colordx/core';
4
+ import cssnanoUtils from 'cssnano-utils';
5
+ import colorPropertiesData from './data/colorProperties.json' with { type: 'json' };
4
6
  import minifyColor from './minifyColor.js';
5
7
 
8
+ /** @import {CSSToken} from '@csstools/css-tokenizer' */
6
9
  const { isSupported } = caniuseApi;
10
+ const {
11
+ applyEdits,
12
+ asciiLowerCase,
13
+ balancedTokens,
14
+ decoded,
15
+ mathFunctions,
16
+ TokenType,
17
+ tokenEnd,
18
+ tokenStart,
19
+ } = cssnanoUtils;
7
20
  /** @import browserslist from 'browserslist' */
8
21
 
9
- const rgbOrHslRegex = /^(rgb|hsl)a?$/i;
10
- const notMinifiableRegex =
11
- /^(composes|font|src$|filter|-webkit-tap-highlight-color)/i;
12
- /**
13
- * @param {{nodes: valueParser.Node[]}} parent
14
- * @param {(node: valueParser.Node, index: number, parent: {nodes: valueParser.Node[]}) => false | undefined} callback
15
- * @return {void}
16
- */
17
- function walk(parent, callback) {
18
- for (const [index, node] of parent.nodes.entries()) {
19
- const bubble = callback(node, index, parent);
20
-
21
- if (node.type === 'function' && bubble !== false) {
22
- walk(node, callback);
23
- }
24
- }
25
- }
26
-
22
+ const colorProperties = new Set(colorPropertiesData);
23
+ const colorFunctionRegex = /^(?:rgb|hsl)a?$|^hwb$/v;
27
24
  /*
28
25
  * IE 8 & 9 do not properly handle clicks on elements
29
26
  * with a `transparent` `background-color`.
@@ -31,60 +28,96 @@ function walk(parent, callback) {
31
28
  * https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer
32
29
  */
33
30
  const browsersWithTransparentBug = new Set(['ie 8', 'ie 9']);
34
- const mathFunctions = new Set(['calc', 'min', 'max', 'clamp']);
35
-
36
- /**
37
- * @param {valueParser.Node} node
38
- * @return {boolean}
39
- */
40
- function isMathFunctionNode(node) {
41
- if (node.type !== 'function') {
42
- return false;
31
+ const tokensRequiringSeparator = new Set([
32
+ TokenType.Ident,
33
+ TokenType.Function,
34
+ TokenType.URL,
35
+ TokenType.BadURL,
36
+ TokenType.Hash,
37
+ TokenType.Number,
38
+ TokenType.Dimension,
39
+ TokenType.Percentage,
40
+ ]);
41
+
42
+ /** @param {string} value @param {Options} options @return {string} */
43
+ function transform(value, options) {
44
+ const structure = balancedTokens(value);
45
+ if (!structure) {
46
+ return value;
47
+ }
48
+ const input = structure.tokens;
49
+ /** @type {{start:number,end:number,text:string}[]} */
50
+ const replacements = [];
51
+
52
+ /** @param {number} end @param {number} index */
53
+ function separator(end, index) {
54
+ const next = input[index + 1];
55
+ return next &&
56
+ tokensRequiringSeparator.has(next[0]) &&
57
+ end === tokenStart(next)
58
+ ? ' '
59
+ : '';
43
60
  }
44
- return mathFunctions.has(node.value.toLowerCase());
45
- }
46
61
 
47
- /**
48
- * @param {string} value
49
- * @param {Options} options
50
- * @return {string}
51
- */
52
- function transform(value, options) {
53
- const parsed = valueParser(value);
54
-
55
- walk(parsed, (node, index, parent) => {
56
- if (node.type === 'function') {
57
- if (rgbOrHslRegex.test(node.value)) {
58
- const { value: originalValue } = node;
59
-
60
- node.value = minifyColor(valueParser.stringify(node), options);
61
- /** @type {string} */ (node.type) = 'word';
62
-
63
- const next = parent.nodes[index + 1];
64
-
65
- if (
66
- node.value !== originalValue &&
67
- next &&
68
- (next.type === 'word' || next.type === 'function')
69
- ) {
70
- parent.nodes.splice(
71
- index + 1,
72
- 0,
73
- /** @type {valueParser.SpaceNode} */ ({
74
- type: 'space',
75
- value: ' ',
76
- })
77
- );
62
+ for (let i = 0; i < input.length; i++) {
63
+ const t = input[i];
64
+ if (t[0] === TokenType.Function) {
65
+ const name = asciiLowerCase(decoded(t));
66
+ const closeIndex = structure.endForOpening(i);
67
+ if (closeIndex === undefined) {
68
+ continue;
69
+ }
70
+ if (name === 'url' || mathFunctions.has(name)) {
71
+ i = closeIndex;
72
+ continue;
73
+ }
74
+ if (colorFunctionRegex.test(name)) {
75
+ const closeToken = input[closeIndex];
76
+ const raw = value.slice(tokenStart(t), tokenEnd(closeToken));
77
+ const inputForMinify =
78
+ name + '(' + value.slice(tokenEnd(t), tokenEnd(closeToken));
79
+ if (colord(inputForMinify).isValid()) {
80
+ const out = minifyColor(inputForMinify, options);
81
+ if (out !== raw) {
82
+ replacements.push({
83
+ start: tokenStart(t),
84
+ end: tokenEnd(closeToken),
85
+ text: out + separator(tokenEnd(closeToken), closeIndex),
86
+ });
87
+ }
88
+ }
89
+ i = closeIndex;
90
+ continue;
91
+ }
92
+ } else if (t[0] === TokenType.Ident) {
93
+ const dec = decoded(t);
94
+ if (!dec.startsWith('#') && colord(dec).isValid()) {
95
+ const out = minifyColor(dec, options);
96
+ if (out !== t[1] && out.length <= t[1].length) {
97
+ replacements.push({
98
+ start: tokenStart(t),
99
+ end: tokenEnd(t),
100
+ text: out + separator(tokenEnd(t), i),
101
+ });
102
+ }
103
+ }
104
+ } else if (t[0] === TokenType.Hash) {
105
+ const dec = decoded(t);
106
+ const hexCandidate = '#' + dec;
107
+ if (colord(hexCandidate).isValid()) {
108
+ const out = minifyColor(hexCandidate, options);
109
+ if (out !== t[1] && out.length <= t[1].length) {
110
+ replacements.push({
111
+ start: tokenStart(t),
112
+ end: tokenEnd(t),
113
+ text: out + separator(tokenEnd(t), i),
114
+ });
78
115
  }
79
- } else if (isMathFunctionNode(node)) {
80
- return false;
81
116
  }
82
- } else if (node.type === 'word') {
83
- node.value = minifyColor(node.value, options);
84
117
  }
85
- });
118
+ }
86
119
 
87
- return parsed.toString();
120
+ return applyEdits(value, replacements);
88
121
  }
89
122
 
90
123
  /**
@@ -94,9 +127,9 @@ function transform(value, options) {
94
127
  */
95
128
  function addPluginDefaults(options, browsers) {
96
129
  const defaults = {
97
- // Does the browser support 4 & 8 character hex notation
98
- transparent: new Set(browsers).isDisjointFrom(browsersWithTransparentBug),
99
130
  // Does the browser support "transparent" value properly
131
+ transparent: new Set(browsers).isDisjointFrom(browsersWithTransparentBug),
132
+ // Does the browser support 4 & 8 character hex notation
100
133
  alphaHex: isSupported('css-rrggbbaa', browsers),
101
134
  name: true,
102
135
  };
@@ -143,36 +176,50 @@ function pluginCreator(config = {}) {
143
176
  */
144
177
  OnceExit(css) {
145
178
  css.walkDecls((decl) => {
146
- if (notMinifiableRegex.test(decl.prop)) {
179
+ if (!decl.prop) {
147
180
  return;
148
181
  }
149
182
 
150
- if (
151
- /** @type Options */ (config).transformCustomProperties ===
152
- false &&
153
- decl.prop.startsWith('--')
154
- ) {
155
- return;
183
+ const lowerProp = asciiLowerCase(decl.prop);
184
+ if (lowerProp.startsWith('--')) {
185
+ if (
186
+ /** @type Options */ (config).transformCustomProperties ===
187
+ false
188
+ ) {
189
+ return;
190
+ }
191
+ } else {
192
+ const unprefixed = lowerProp.replace(/^-\w+-/v, '');
193
+ if (
194
+ (!colorProperties.has(lowerProp) &&
195
+ !colorProperties.has(unprefixed)) ||
196
+ lowerProp === '-webkit-tap-highlight-color'
197
+ ) {
198
+ return;
199
+ }
156
200
  }
157
201
 
158
- const value = decl.value;
202
+ const rawValue =
203
+ decl.raws.value?.value === decl.value
204
+ ? (decl.raws.value.raw ?? decl.value)
205
+ : decl.value;
159
206
 
160
- if (!value) {
207
+ if (!rawValue) {
161
208
  return;
162
209
  }
163
210
 
164
- const cacheKey = JSON.stringify({ value, options, browsers });
165
-
166
- if (cache.has(cacheKey)) {
167
- decl.value = cache.get(cacheKey);
168
-
169
- return;
211
+ let newValue;
212
+ if (cache.has(rawValue)) {
213
+ newValue = cache.get(rawValue);
214
+ } else {
215
+ newValue = transform(rawValue, options);
216
+ cache.set(rawValue, newValue);
170
217
  }
171
218
 
172
- const newValue = transform(value, options);
173
-
174
219
  decl.value = newValue;
175
- cache.set(cacheKey, newValue);
220
+ if (decl.raws.value?.raw) {
221
+ decl.raws.value = { raw: newValue, value: newValue };
222
+ }
176
223
  });
177
224
  },
178
225
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAMI,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAoGzC,YAAkB,kBAAkB,GACpC;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,IAAI,AAAzB,CACA,EADW,OAAO,CAClB;IAAqB,WAAW,AAAhC,CACA,EADW,OAAO,CAClB;;;;IAAqB,yBAAyB,AAA9C,CACF,EADa,OAAO,CACpB;CAAA,CAAA;AAGE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAA0E,OAAO,GAAvE,kBAAkB,GAAG,mBAAmB,GAAG,mBAAmB,CAAS;AAdpF;;;;;;;;;GASG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,iBAAS,aAAa,CAAC,MAAM,GAHlB,OAGuB,GAFtB,OAAO,SAAS,EAAE,MAAM,CAwDnC;kBAtDQ,aAAa;iBAuDX,IAAI;;AAEf,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":"AAmBI,OAAQ,KAAA,YAAY,MAAM,cAAc,CAAA;AAwHzC,YAAkB,kBAAkB,GACpC;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,QAAQ,AAA7B,CACA,EADW,OAAO,CAClB;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,GAAG,AAAxB,CACA,EADW,OAAO,CAClB;IAAqB,IAAI,AAAzB,CACA,EADW,OAAO,CAClB;IAAqB,WAAW,AAAhC,CACA,EADW,OAAO,CAClB;;;;IAAqB,yBAAyB,AAA9C,CACF,EADa,OAAO,CACpB;CAAA,CAAA;AAGE,YAAwD,mBAAmB,GAAjE;IAAE,oBAAoB,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,CAAqB;AAC3E,YAAgE,mBAAmB,GAAzE,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAqB;AACnF,YAA0E,OAAO,GAAvE,kBAAkB,GAAG,mBAAmB,GAAG,mBAAmB,CAAS;AAdpF;;;;;;;;;GASG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,iBAAS,aAAa,CAAC,MAAM,GAHlB,OAGuB,GAFtB,OAAO,SAAS,EAAE,MAAM,CAsEnC;kBApEQ,aAAa;iBAqEX,IAAI;;AAEf,QAAA,MAAM,aAAa,sBAAgB,CAAC;AAEpC,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,aAAa,IAAI,gBAAgB,EAAE,CAAC"}