@thejaredwilcurt/csslop 0.0.30 → 0.0.32

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
@@ -2,7 +2,7 @@
2
2
  "name": "@thejaredwilcurt/csslop",
3
3
  "main": "index.js",
4
4
  "type": "module",
5
- "version": "0.0.30",
5
+ "version": "0.0.32",
6
6
  "description": "Experimental CSS minification",
7
7
  "scripts": {
8
8
  "prestart": "node ./scripts/prestart.js",
@@ -5,6 +5,7 @@
5
5
  import { minifyValue } from '../value/minify.js';
6
6
 
7
7
  import { shorthandMap } from './config.js';
8
+ import { buildLayerPositionAndSize } from './image-layers.js';
8
9
 
9
10
  const BACKGROUND_POSITION_KEYWORDS = new Set(['left', 'center', 'right', 'top', 'bottom']);
10
11
  const BACKGROUND_REPEAT_KEYWORDS = new Set(['repeat', 'no-repeat', 'repeat-x', 'repeat-y', 'space', 'round']);
@@ -246,16 +247,7 @@ function buildBackgroundShorthandValue (valueMap, importantSuffix) {
246
247
  if (image && image !== 'none') {
247
248
  result.push(image);
248
249
  }
249
- if (position && position !== '0 0' && position !== '0% 0%') {
250
- result.push(position);
251
- }
252
- if (size && size !== 'auto') {
253
- if (position && position !== '0 0' && position !== '0% 0%') {
254
- result.push('/' + size);
255
- } else {
256
- result.push('0 0/' + size);
257
- }
258
- }
250
+ result.push(...buildLayerPositionAndSize(position, size));
259
251
  if (repeat && repeat !== 'repeat') {
260
252
  result.push(repeat);
261
253
  }
@@ -80,7 +80,7 @@ const shorthandMap = {
80
80
  'border-block-width': ['border-block-start-width', 'border-block-end-width'],
81
81
  transition: ['transition-property', 'transition-duration', 'transition-timing-function', 'transition-delay'],
82
82
  animation: ['animation-name', 'animation-duration', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction', 'animation-fill-mode', 'animation-play-state'],
83
- mask: ['mask-image', 'mask-repeat', 'mask-size'],
83
+ mask: ['mask-image', 'mask-position', 'mask-size', 'mask-repeat'],
84
84
  'position-try': ['position-try-order', 'position-try-fallbacks'],
85
85
  font: ['font-style', 'font-weight', 'font-size', 'line-height', 'font-family'],
86
86
  marker: ['marker-start', 'marker-mid', 'marker-end'],
@@ -92,7 +92,7 @@ const shorthandOverrideMap = {
92
92
  columns: ['column-wrap'],
93
93
  border: ['border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat'],
94
94
  font: ['font-variant', 'font-variant-alternates', 'font-variant-caps', 'font-variant-east-asian', 'font-variant-ligatures', 'font-variant-numeric', 'font-variant-position', 'font-feature-settings', 'font-kerning', 'font-language-override', 'font-optical-sizing', 'font-size-adjust', 'font-variation-settings'],
95
- mask: ['mask-border', 'mask-border-source', 'mask-border-slice', 'mask-border-width', 'mask-border-outset', 'mask-border-repeat', 'mask-border-mode', 'mask-position', 'mask-origin', 'mask-clip', 'mask-composite', 'mask-mode']
95
+ mask: ['mask-border', 'mask-border-source', 'mask-border-slice', 'mask-border-width', 'mask-border-outset', 'mask-border-repeat', 'mask-border-mode', 'mask-origin', 'mask-clip', 'mask-composite', 'mask-mode']
96
96
  };
97
97
 
98
98
  /**
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @file Shared grammar helpers for the layered image shorthands, `background` and `mask`, whose layers only accept a size behind a slash that follows a position.
3
+ */
4
+
5
+ /**
6
+ * The layer positions a shorthand may leave unstated, since they already match
7
+ * the initial position that the shorthand resets the layer to.
8
+ *
9
+ * @type {Set<string>}
10
+ */
11
+ const INITIAL_LAYER_POSITIONS = new Set(['0 0', '0% 0%']);
12
+
13
+ /**
14
+ * The shortest spelling of the initial layer position, used whenever a stated
15
+ * size needs a position in front of it.
16
+ *
17
+ * @type {string}
18
+ */
19
+ const SHORTEST_INITIAL_LAYER_POSITION = '0 0';
20
+
21
+ /**
22
+ * The layer size a shorthand may leave unstated, since it already matches the
23
+ * initial size that the shorthand resets the layer to.
24
+ *
25
+ * @type {string}
26
+ */
27
+ const INITIAL_LAYER_SIZE = 'auto';
28
+
29
+ /**
30
+ * Reduces a layer position to what the shorthand has to spell out, which is
31
+ * nothing at all while the position holds its initial value.
32
+ *
33
+ * @param {string} position The layer position, or a falsy value when the layer states none.
34
+ * @return {string} The position to write, or an empty string when it can be left out.
35
+ */
36
+ function resolveStatedLayerPosition (position) {
37
+ if (!position || INITIAL_LAYER_POSITIONS.has(position)) {
38
+ return '';
39
+ }
40
+ return position;
41
+ }
42
+
43
+ /**
44
+ * Reduces a layer size to what the shorthand has to spell out, which is nothing
45
+ * at all while the size holds its initial value.
46
+ *
47
+ * @param {string} size The layer size, or a falsy value when the layer states none.
48
+ * @return {string} The size to write, or an empty string when it can be left out.
49
+ */
50
+ function resolveStatedLayerSize (size) {
51
+ if (!size || size === INITIAL_LAYER_SIZE) {
52
+ return '';
53
+ }
54
+ return size;
55
+ }
56
+
57
+ /**
58
+ * Builds the position and size components of a layered image shorthand value.
59
+ * The layer grammar only reads a size directly behind a `/` that follows a
60
+ * position, so a stated size pulls the initial position back into the output
61
+ * whenever the layer states no position of its own.
62
+ *
63
+ * @param {string} position The layer position, or a falsy value when the layer states none.
64
+ * @param {string} size The layer size, or a falsy value when the layer states none.
65
+ * @return {Array} The shorthand components covering the position and the size.
66
+ */
67
+ function buildLayerPositionAndSize (position, size) {
68
+ const statedPosition = resolveStatedLayerPosition(position);
69
+ const statedSize = resolveStatedLayerSize(size);
70
+ if (statedSize) {
71
+ return [(statedPosition || SHORTEST_INITIAL_LAYER_POSITION) + '/' + statedSize];
72
+ }
73
+ if (statedPosition) {
74
+ return [statedPosition];
75
+ }
76
+ return [];
77
+ }
78
+
79
+ export { buildLayerPositionAndSize };
@@ -7,6 +7,7 @@ import { splitTopLevelComponents } from '../value/syntax.js';
7
7
 
8
8
  import { buildBackgroundShorthandValue } from './background.js';
9
9
  import { UNIFORM_VALUE_SHORTHANDS } from './config.js';
10
+ import { buildLayerPositionAndSize } from './image-layers.js';
10
11
 
11
12
  /**
12
13
  * @typedef {object} ShorthandComponents
@@ -124,26 +125,33 @@ function buildBackgroundValue ({ valueMap, importantSuffix }) {
124
125
  }
125
126
 
126
127
  /**
127
- * Builds a `mask` value, attaching the size after the position separator.
128
+ * Builds a `mask` value. A mask layer follows the same grammar as a background
129
+ * layer, so the image leads, the size only reads as a size while it sits behind
130
+ * a `/` that follows the position, and the repeat comes after both of them. A
131
+ * component holding its initial value is left out, since the shorthand already
132
+ * resets every longhand it covers back to that value.
128
133
  *
129
134
  * @param {ShorthandComponents} components The collected longhand values.
130
135
  * @return {string|null} The shorthand value, or null when it cannot be built.
131
136
  */
132
137
  function buildMaskValue ({ valueMap, importantSuffix }) {
133
138
  const image = valueMap.get('mask-image');
134
- const repeat = valueMap.get('mask-repeat');
135
- const size = valueMap.get('mask-size');
136
139
  if (!image) {
137
140
  return null;
138
141
  }
139
- let result = image;
140
- if (repeat) {
141
- result += ' ' + repeat;
142
+ const repeat = valueMap.get('mask-repeat');
143
+ const result = [];
144
+ if (image !== 'none') {
145
+ result.push(image);
146
+ }
147
+ result.push(...buildLayerPositionAndSize(valueMap.get('mask-position'), valueMap.get('mask-size')));
148
+ if (repeat && repeat !== 'repeat') {
149
+ result.push(repeat);
142
150
  }
143
- if (size) {
144
- result += '/' + size;
151
+ if (!result.length) {
152
+ return 'none' + importantSuffix;
145
153
  }
146
- return result + importantSuffix;
154
+ return result.join(' ') + importantSuffix;
147
155
  }
148
156
 
149
157
  /**
@@ -980,12 +980,31 @@ const POSITION_OFFSET_SOURCE = '(?:left|center|right|top|bottom|[+-]?(?:\\d+|\\d
980
980
  const POSITION_COMPONENT_SOURCE = '(' + POSITION_OFFSET_SOURCE + '(?:\\s+' + POSITION_OFFSET_SOURCE + ')?)';
981
981
 
982
982
  /**
983
- * Matches a close-paren directly followed by a position that no slash follows,
984
- * which is the position that needs its separator put back.
983
+ * Matches everything a `url()` holds up to but not including its own closing
984
+ * parenthesis. A url is written without nested parentheses, so the first one
985
+ * that closes it is the end of the token.
986
+ *
987
+ * @type {string}
988
+ */
989
+ const URL_TOKEN_BODY_SOURCE = 'url\\([^()]*';
990
+
991
+ /**
992
+ * Matches a close-paren that ends an image function, directly followed by a
993
+ * position that no slash follows, which is the position that needs its
994
+ * separator put back. The parenthesis that ends a `url()` is skipped, since
995
+ * that one closes a token rather than a function.
996
+ *
997
+ * @type {RegExp}
998
+ */
999
+ const UNSEPARATED_FUNCTION_POSITION_PATTERN = new RegExp('(?<!' + URL_TOKEN_BODY_SOURCE + ')\\)' + POSITION_COMPONENT_SOURCE + '(?!\\/)', 'gi');
1000
+
1001
+ /**
1002
+ * Matches a `url()` separated from the position that follows it, which is the
1003
+ * separator a url does not need.
985
1004
  *
986
1005
  * @type {RegExp}
987
1006
  */
988
- const UNSEPARATED_IMAGE_POSITION_PATTERN = new RegExp('\\)' + POSITION_COMPONENT_SOURCE + '(?!\\/)', 'gi');
1007
+ const SEPARATED_URL_POSITION_PATTERN = new RegExp('(' + URL_TOKEN_BODY_SOURCE + '\\))\\s+' + POSITION_COMPONENT_SOURCE, 'gi');
989
1008
 
990
1009
  /**
991
1010
  * Matches a close-paren separated from a position that a slash follows, where
@@ -996,18 +1015,21 @@ const UNSEPARATED_IMAGE_POSITION_PATTERN = new RegExp('\\)' + POSITION_COMPONENT
996
1015
  const SEPARATED_IMAGE_SIZE_POSITION_PATTERN = new RegExp('\\)\\s+' + POSITION_COMPONENT_SOURCE + '(?=\\/)', 'gi');
997
1016
 
998
1017
  /**
999
- * Restores the separator between an image function and the position that
1018
+ * Normalizes the separator between the image of a layer and the position that
1000
1019
  * follows it. Both `background` and `mask` take a `<position> [ / <size> ]`
1001
1020
  * component after their image, and a bare position only reads as its own
1002
- * component while whitespace separates it from the image function. A position
1003
- * that a `/` follows needs no separator, since the slash delimits the pair.
1021
+ * component while whitespace separates it from an image function. Neither a
1022
+ * position that a `/` follows nor one that follows a `url()` needs the
1023
+ * separator, since the slash delimits the pair and a url is consumed whole as a
1024
+ * single token that ends at its own closing parenthesis.
1004
1025
  *
1005
1026
  * @param {string} value The layered image value, with the parenthesis padding removed.
1006
- * @return {string} The value with the position separator restored.
1027
+ * @return {string} The value with the position separator normalized.
1007
1028
  */
1008
- function restoreImagePositionSeparator (value) {
1009
- const separated = value.replace(UNSEPARATED_IMAGE_POSITION_PATTERN, ') $1');
1010
- return separated.replace(SEPARATED_IMAGE_SIZE_POSITION_PATTERN, ')$1');
1029
+ function normalizeImagePositionSeparator (value) {
1030
+ const separatedFromFunction = value.replace(UNSEPARATED_FUNCTION_POSITION_PATTERN, ') $1');
1031
+ const joinedToUrl = separatedFromFunction.replace(SEPARATED_URL_POSITION_PATTERN, '$1$2');
1032
+ return joinedToUrl.replace(SEPARATED_IMAGE_SIZE_POSITION_PATTERN, ')$1');
1011
1033
  }
1012
1034
 
1013
1035
  /**
@@ -1227,11 +1249,11 @@ function applyPropertyOptimizations (val, property, allowsSeparatorElision) {
1227
1249
  if (normalized) {
1228
1250
  val = normalized;
1229
1251
  }
1230
- val = restoreImagePositionSeparator(val);
1252
+ val = normalizeImagePositionSeparator(val);
1231
1253
  }
1232
1254
 
1233
1255
  if (property === 'mask') {
1234
- val = restoreImagePositionSeparator(val);
1256
+ val = normalizeImagePositionSeparator(val);
1235
1257
  }
1236
1258
 
1237
1259
  if (property === 'border') {