@thejaredwilcurt/csslop 0.0.49 → 0.0.51
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/README.md +0 -1
- package/package.json +2 -2
- package/src/declarations/border.js +1 -1
- package/src/declarations/process.js +155 -1
- package/src/index.js +7 -7
- package/src/rules/optimize/at-rules.js +113 -0
- package/src/rules/optimize/declaration-writes.js +70 -0
- package/src/rules/optimize/empty-rules.js +51 -0
- package/src/rules/optimize/expand-nesting.js +236 -0
- package/src/rules/optimize/factor-parents.js +343 -0
- package/src/rules/optimize/index.js +71 -0
- package/src/rules/optimize/merge-declarations.js +199 -0
- package/src/rules/optimize/merge-nested-rules.js +117 -0
- package/src/rules/optimize/merge-selectors.js +252 -0
- package/src/rules/optimize/nest-rules.js +202 -0
- package/src/rules/optimize/override-index.js +264 -0
- package/src/rules/optimize/prune-overridden.js +267 -0
- package/src/rules/optimize/selector-normalization.js +60 -0
- package/src/rules/optimize/specificity.js +280 -0
- package/src/{position-try.js → rules/position-try.js} +1 -1
- package/src/rules/stringify.js +13 -0
- package/src/utilities.js +14 -0
- package/src/value/border.js +85 -0
- package/src/value/{colors.js → color/conversion.js} +3 -2
- package/src/value/color/functions.js +463 -0
- package/src/value/{light-dark.js → color/light-dark.js} +1 -1
- package/src/value/{color-mix.js → color/mix.js} +3 -3
- package/src/value/{relative-colors.js → color/relative.js} +3 -3
- package/src/value/color/shorten.js +93 -0
- package/src/value/gradients.js +1 -1
- package/src/value/hyphenate-limit-chars.js +72 -0
- package/src/value/initial-keywords.js +128 -0
- package/src/value/lengths.js +55 -0
- package/src/value/math/evaluate.js +36 -0
- package/src/value/math/fold.js +315 -0
- package/src/value/math/format.js +230 -0
- package/src/value/math/functions.js +302 -0
- package/src/value/math/index.js +219 -0
- package/src/value/math/parser.js +301 -0
- package/src/value/math/property-ranges.js +174 -0
- package/src/value/math/rewrite.js +222 -0
- package/src/value/math/tokenizer.js +160 -0
- package/src/value/math/unit-totals.js +194 -0
- package/src/value/minify.js +127 -1669
- package/src/value/numbers.js +28 -0
- package/src/value/positions.js +152 -0
- package/src/value/property-optimizations.js +485 -0
- package/src/value/segments.js +74 -0
- package/src/value/{svg-data-uri.js → svg/data-uri.js} +1 -1
- package/src/value/{svg-markup.js → svg/markup.js} +1 -1
- package/src/value/time.js +75 -0
- package/src/value/urls.js +242 -0
- package/src/value/whitespace.js +88 -0
- package/src/rules/optimize.js +0 -2314
- package/src/value/math-expression.js +0 -1156
- package/src/value/math.js +0 -886
- /package/src/value/{named-colors.js → color/named.js} +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Rewrites hex colors and named color keywords in their shortest equivalent spelling.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { replaceOutsideStringsAndUrls } from '../segments.js';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
parseHex,
|
|
9
|
+
shortestColor
|
|
10
|
+
} from './conversion.js';
|
|
11
|
+
import { namedColors } from './named.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Regex matching hex color tokens (#rgb, #rgba, #rrggbb, #rrggbbaa) and CSS named
|
|
15
|
+
* color keywords. Hex patterns are ordered longest-first to avoid partial matches.
|
|
16
|
+
* Named colors are sorted longest-first so longer names like `darkslategray` are
|
|
17
|
+
* matched before shorter substrings. A named color only counts as a color keyword
|
|
18
|
+
* when it is a complete identifier, so hyphens on either side disqualify it (this
|
|
19
|
+
* keeps identifiers such as the custom property name in `var(--grey)` intact).
|
|
20
|
+
*
|
|
21
|
+
* @type {RegExp}
|
|
22
|
+
*/
|
|
23
|
+
const COLOR_TOKEN_PATTERN = new RegExp(
|
|
24
|
+
'#[0-9a-fA-F]{8}(?![0-9a-fA-F])|' +
|
|
25
|
+
'#[0-9a-fA-F]{6}(?![0-9a-fA-F])|' +
|
|
26
|
+
'#[0-9a-fA-F]{4}(?![0-9a-fA-F])|' +
|
|
27
|
+
'#[0-9a-fA-F]{3}(?![0-9a-fA-F])|' +
|
|
28
|
+
'(?<![\\w-])(?:' +
|
|
29
|
+
Object.keys(namedColors).sort((first, second) => {
|
|
30
|
+
return second.length - first.length;
|
|
31
|
+
}).join('|') +
|
|
32
|
+
')(?![\\w-])',
|
|
33
|
+
'gi'
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Replaces every hex color and named color keyword in a CSS value segment with
|
|
38
|
+
* the shortest equivalent representation, comparing full hex, shortened hex,
|
|
39
|
+
* and any matching named color keyword.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} segment A CSS value segment (outside strings and urls).
|
|
42
|
+
* @param {boolean} rewritesEqualLengthSpelling Whether a spelling of the same length is worth switching to.
|
|
43
|
+
* @return {string} The segment with all colors shortened to their minimal form.
|
|
44
|
+
*/
|
|
45
|
+
function shortenColorValues (segment, rewritesEqualLengthSpelling = true) {
|
|
46
|
+
// Match "color-mix(" as a whole word, case-insensitive
|
|
47
|
+
const hasColorMix = /\bcolor-mix\(/i.test(segment);
|
|
48
|
+
return segment.replace(COLOR_TOKEN_PATTERN, (match) => {
|
|
49
|
+
let channels;
|
|
50
|
+
if (match.startsWith('#')) {
|
|
51
|
+
channels = parseHex(match);
|
|
52
|
+
} else {
|
|
53
|
+
if (hasColorMix) {
|
|
54
|
+
return match;
|
|
55
|
+
}
|
|
56
|
+
const rgb = namedColors[match.toLowerCase()];
|
|
57
|
+
if (rgb) {
|
|
58
|
+
// transparent has alpha=0, all other named colors have alpha=1
|
|
59
|
+
const alpha = match.toLowerCase() === 'transparent' ? 0 : 1;
|
|
60
|
+
channels = [rgb[0], rgb[1], rgb[2], alpha];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!channels) {
|
|
64
|
+
return match;
|
|
65
|
+
}
|
|
66
|
+
const shortest = shortestColor(channels[0], channels[1], channels[2], channels[3]);
|
|
67
|
+
if (!rewritesEqualLengthSpelling && shortest.length >= match.length) {
|
|
68
|
+
return match;
|
|
69
|
+
}
|
|
70
|
+
return shortest;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Lowercases every hex color token in a CSS value, since uppercase hex digits
|
|
76
|
+
* compress worse and are equivalent to their lowercase form.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} value The CSS value string.
|
|
79
|
+
* @return {string} The value with all hex color tokens lowercased.
|
|
80
|
+
*/
|
|
81
|
+
function lowercaseHexColors (value) {
|
|
82
|
+
return replaceOutsideStringsAndUrls(value, (segment) => {
|
|
83
|
+
// Match hex color tokens of 3 to 8 hex digits
|
|
84
|
+
return segment.replace(/#([0-9a-fA-F]{3,8})\b/gi, (hexColor) => {
|
|
85
|
+
return hexColor.toLowerCase();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export {
|
|
91
|
+
lowercaseHexColors,
|
|
92
|
+
shortenColorValues
|
|
93
|
+
};
|
package/src/value/gradients.js
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Collapses `hyphenate-limit-chars` values to their shortest equivalent form.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { hasSubstitutedParts } from './shared.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Matches a trailing `!important` flag, so the components in front of it can be
|
|
9
|
+
* rewritten without the flag being mistaken for part of the last one.
|
|
10
|
+
*
|
|
11
|
+
* @type {RegExp}
|
|
12
|
+
*/
|
|
13
|
+
const IMPORTANT_SUFFIX_PATTERN = /!important$/i;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Separates a value from the `!important` flag it may end with.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} value The declaration value, possibly flagged important.
|
|
19
|
+
* @return {object} The components in front of the flag, and the flag itself or an empty string.
|
|
20
|
+
*/
|
|
21
|
+
function splitImportantSuffix (value) {
|
|
22
|
+
const match = value.match(IMPORTANT_SUFFIX_PATTERN);
|
|
23
|
+
if (!match) {
|
|
24
|
+
return { components: value, importantSuffix: '' };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
components: value.slice(0, match.index).trim(),
|
|
28
|
+
importantSuffix: match[0]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Collapses a `hyphenate-limit-chars` value to its shortest equivalent form.
|
|
34
|
+
* The property states up to three components: the shortest word that may be
|
|
35
|
+
* hyphenated, then the fewest characters allowed before a hyphen, then the
|
|
36
|
+
* fewest allowed after it. An unstated "before" component is read as `auto`,
|
|
37
|
+
* and an unstated "after" component repeats whatever "before" holds, so a
|
|
38
|
+
* trailing component that only restates the default it stands in for is
|
|
39
|
+
* redundant.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} value The `hyphenate-limit-chars` value.
|
|
42
|
+
* @return {string} The value with its redundant trailing components dropped.
|
|
43
|
+
*/
|
|
44
|
+
function collapseHyphenateLimitChars (value) {
|
|
45
|
+
const {
|
|
46
|
+
components,
|
|
47
|
+
importantSuffix
|
|
48
|
+
} = splitImportantSuffix(value);
|
|
49
|
+
const parts = components.split(' ').filter(Boolean);
|
|
50
|
+
if (hasSubstitutedParts(parts)) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
const isAfterMatchingBefore = (
|
|
54
|
+
parts.length === 3 &&
|
|
55
|
+
parts[2].toLowerCase() === parts[1].toLowerCase()
|
|
56
|
+
);
|
|
57
|
+
if (isAfterMatchingBefore) {
|
|
58
|
+
parts.pop();
|
|
59
|
+
}
|
|
60
|
+
const isBeforeLeftAtDefault = (
|
|
61
|
+
parts.length === 2 &&
|
|
62
|
+
parts[1].toLowerCase() === 'auto'
|
|
63
|
+
);
|
|
64
|
+
if (isBeforeLeftAtDefault) {
|
|
65
|
+
parts.pop();
|
|
66
|
+
}
|
|
67
|
+
return parts.join(' ') + importantSuffix;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export {
|
|
71
|
+
collapseHyphenateLimitChars
|
|
72
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Rewrites a property's `initial` keyword into the shorter value it stands for, and a lengthy initial value into the shorter `initial` keyword.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties whose `initial` value is a number the browser resolves, so the
|
|
7
|
+
* keyword only ever shortens for the two that have a shorter written form.
|
|
8
|
+
*
|
|
9
|
+
* @type {Set<string>}
|
|
10
|
+
*/
|
|
11
|
+
const NUMERIC_INITIAL_PROPERTIES = new Set([
|
|
12
|
+
'opacity',
|
|
13
|
+
'z-index',
|
|
14
|
+
'flex-grow',
|
|
15
|
+
'flex-shrink',
|
|
16
|
+
'order',
|
|
17
|
+
'line-height',
|
|
18
|
+
'zoom'
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Properties whose `initial` value is zero.
|
|
23
|
+
*
|
|
24
|
+
* @type {Set<string>}
|
|
25
|
+
*/
|
|
26
|
+
const ZERO_INITIAL_PROPERTIES = new Set(['margin', 'padding']);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Properties whose `initial` value is `auto`.
|
|
30
|
+
*
|
|
31
|
+
* @type {Set<string>}
|
|
32
|
+
*/
|
|
33
|
+
const AUTO_INITIAL_PROPERTIES = new Set([
|
|
34
|
+
'min-width',
|
|
35
|
+
'min-height',
|
|
36
|
+
'hyphenate-limit-chars'
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Properties whose initial value is a single keyword longer than the global
|
|
41
|
+
* `initial` keyword itself. These replacements are applied when serializing
|
|
42
|
+
* declarations, because shorthand builders reconstruct their value from the
|
|
43
|
+
* actual longhand keywords, and a CSS-wide keyword such as `initial` is only
|
|
44
|
+
* valid as a declaration's entire value.
|
|
45
|
+
*
|
|
46
|
+
* @type {Map<string, string>}
|
|
47
|
+
*/
|
|
48
|
+
const LENGTHY_INITIAL_KEYWORDS = new Map([
|
|
49
|
+
['background-clip', 'border-box'],
|
|
50
|
+
['background-origin', 'padding-box'],
|
|
51
|
+
['border-collapse', 'separate'],
|
|
52
|
+
['border-color', 'currentcolor'],
|
|
53
|
+
['border-top-color', 'currentcolor'],
|
|
54
|
+
['border-right-color', 'currentcolor'],
|
|
55
|
+
['border-bottom-color', 'currentcolor'],
|
|
56
|
+
['border-left-color', 'currentcolor'],
|
|
57
|
+
['box-sizing', 'content-box'],
|
|
58
|
+
['mask-clip', 'border-box'],
|
|
59
|
+
['mask-origin', 'border-box'],
|
|
60
|
+
['vertical-align', 'baseline'],
|
|
61
|
+
['writing-mode', 'horizontal-tb']
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolves a property's `initial` keyword into the value it stands for, for the
|
|
66
|
+
* properties where that value is shorter to write than the keyword.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} value The CSS value string after generic minification.
|
|
69
|
+
* @param {string} property The CSS property name.
|
|
70
|
+
* @return {string} The value with its `initial` keyword resolved where that is shorter.
|
|
71
|
+
*/
|
|
72
|
+
function resolveInitialKeyword (value, property) {
|
|
73
|
+
if (value !== 'initial') {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
if (NUMERIC_INITIAL_PROPERTIES.has(property)) {
|
|
77
|
+
// Just leaving them or mapping some: opacity: initial -> opacity: 1
|
|
78
|
+
if (property === 'opacity') {
|
|
79
|
+
return '1';
|
|
80
|
+
}
|
|
81
|
+
if (property === 'z-index') {
|
|
82
|
+
return 'auto';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (ZERO_INITIAL_PROPERTIES.has(property)) {
|
|
86
|
+
return '0';
|
|
87
|
+
}
|
|
88
|
+
if (AUTO_INITIAL_PROPERTIES.has(property)) {
|
|
89
|
+
return 'auto';
|
|
90
|
+
}
|
|
91
|
+
// background-color: initial should become #0000 (transparent)
|
|
92
|
+
if (property === 'background-color') {
|
|
93
|
+
return '#0000';
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Rewrites a value that spells its property's initial value with a keyword
|
|
100
|
+
* longer than the global `initial` keyword. Both are equivalent, but `initial`
|
|
101
|
+
* is shorter than these particular keywords and, being repeated across
|
|
102
|
+
* properties, gives gzip more opportunities to deduplicate it.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} value The minified declaration value, possibly flagged important.
|
|
105
|
+
* @param {string} property The CSS property name.
|
|
106
|
+
* @return {string} The value with its initial keyword shortened to `initial` when that saves bytes.
|
|
107
|
+
*/
|
|
108
|
+
function shortenLengthyInitialKeyword (value, property) {
|
|
109
|
+
// A keyword spelling the property's own initial value is shorter as the
|
|
110
|
+
// global keyword (box-sizing: content-box -> box-sizing: initial)
|
|
111
|
+
const initialKeyword = LENGTHY_INITIAL_KEYWORDS.get(property?.toLowerCase());
|
|
112
|
+
if (!initialKeyword || 'initial'.length >= initialKeyword.length) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
// A trailing `!important` rides along inside the value, so the keyword
|
|
116
|
+
// comparison and the rewrite only concern what sits in front of it.
|
|
117
|
+
const isImportant = value.endsWith('!important');
|
|
118
|
+
const keyword = isImportant ? value.slice(0, value.length - '!important'.length) : value;
|
|
119
|
+
if (keyword.toLowerCase() !== initialKeyword) {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
return 'initial' + (isImportant ? '!important' : '');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export {
|
|
126
|
+
resolveInitialKeyword,
|
|
127
|
+
shortenLengthyInitialKeyword
|
|
128
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Converts absolute CSS lengths to the pixels they measure whenever the conversion is exact and no longer.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
convertAbsoluteLengthToPx,
|
|
7
|
+
roundCompactNumber
|
|
8
|
+
} from './shared.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Matches an absolute CSS length token: an optionally signed number followed by
|
|
12
|
+
* an absolute length unit. The lookbehind rejects digits that belong to a larger
|
|
13
|
+
* identifier, such as the custom property name in `var(--size-2in)`, where the
|
|
14
|
+
* digits and unit do not form a value of their own.
|
|
15
|
+
*
|
|
16
|
+
* @type {RegExp}
|
|
17
|
+
*/
|
|
18
|
+
const ABSOLUTE_LENGTH_PATTERN = /(?<![\w#.%-])(-?(?:\d+|\d*\.\d+))(pt|pc|in|cm|mm|q)\b/gi;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The largest difference in pixels that a rounded conversion may introduce and
|
|
22
|
+
* still count as exact, which allows for binary floating point error without
|
|
23
|
+
* allowing a visible change to the rendered length.
|
|
24
|
+
*
|
|
25
|
+
* @type {number}
|
|
26
|
+
*/
|
|
27
|
+
const PIXEL_ROUNDING_TOLERANCE = 1e-6;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Converts absolute length values (pt, pc, in, cm, mm, Q) to their pixel
|
|
31
|
+
* equivalent when the conversion is exact and the pixel form is no longer than
|
|
32
|
+
* the original. Normalizing to px also improves compression by reducing the
|
|
33
|
+
* number of distinct unit strings in the output.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} value A CSS value segment, outside strings and urls.
|
|
36
|
+
* @return {string} The segment with eligible absolute lengths converted to px.
|
|
37
|
+
*/
|
|
38
|
+
function convertAbsoluteLengthsToPx (value) {
|
|
39
|
+
return value.replace(ABSOLUTE_LENGTH_PATTERN, (token, amount, unit) => {
|
|
40
|
+
const pixels = convertAbsoluteLengthToPx(amount, unit);
|
|
41
|
+
if (pixels === null) {
|
|
42
|
+
return token;
|
|
43
|
+
}
|
|
44
|
+
const converted = roundCompactNumber(pixels) + 'px';
|
|
45
|
+
const isExact = Math.abs(parseFloat(converted) - pixels) < PIXEL_ROUNDING_TOLERANCE;
|
|
46
|
+
if (!isExact || converted.length > token.length) {
|
|
47
|
+
return token;
|
|
48
|
+
}
|
|
49
|
+
return converted;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
convertAbsoluteLengthsToPx
|
|
55
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Reads the arithmetic a CSS math function states and resolves it into the value it works out to, whenever every operand of it is a number already known at minification time.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { formatUnitTotals } from './format.js';
|
|
6
|
+
import { parseSum } from './parser.js';
|
|
7
|
+
import { tokenizeMathExpression } from './tokenizer.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolves a CSS math function into the value its arithmetic works out to.
|
|
11
|
+
* A function whose operands are not all known at minification time — one
|
|
12
|
+
* referencing a custom property, an environment variable, or an element the
|
|
13
|
+
* value is only resolved against once the page is laid out — states a value
|
|
14
|
+
* that cannot be worked out here, and so resolves to nothing.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} mathFunction The math function as it is written, such as `calc(10 / 2)` or `min(10px, 20px)`.
|
|
17
|
+
* @return {string|null} The value the math function states, or null when it cannot be resolved.
|
|
18
|
+
*/
|
|
19
|
+
function simplifyCalc (mathFunction) {
|
|
20
|
+
const tokens = tokenizeMathExpression(mathFunction);
|
|
21
|
+
if (!tokens) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const reader = { tokens, index: 0, depth: 0 };
|
|
25
|
+
const totals = parseSum(reader);
|
|
26
|
+
// A token left unread means the expression states more than the arithmetic
|
|
27
|
+
// that was resolved, so what was resolved is not the whole of its value
|
|
28
|
+
if (!totals || reader.index !== tokens.length) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return formatUnitTotals(totals);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
simplifyCalc
|
|
36
|
+
};
|