webpack 5.112.1 → 5.112.2
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/lib/Compilation.js +46 -1
- package/lib/css/CssGenerator.js +0 -10
- package/lib/css/CssModulesPlugin.js +11 -4
- package/lib/css/syntax-parser.js +171 -32
- package/lib/css/syntax-printer.js +1081 -1019
- package/lib/optimize/ConcatenatedModule.js +2 -2
- package/lib/util/SourceProcessor.js +48 -14
- package/package.json +3 -2
|
@@ -4893,6 +4893,1071 @@ const _printAttributeSelector = (path, children, writer) => {
|
|
|
4893
4893
|
return parts;
|
|
4894
4894
|
};
|
|
4895
4895
|
|
|
4896
|
+
/**
|
|
4897
|
+
* The `printer` case for a function.
|
|
4898
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
4899
|
+
* @param {PrintContext} writer the print context (children's printed text)
|
|
4900
|
+
* @param {boolean} minify whether minifying
|
|
4901
|
+
* @returns {string} the node's serialized text
|
|
4902
|
+
*/
|
|
4903
|
+
const _printFunctionNode = (path, writer, minify) => {
|
|
4904
|
+
const written = _rawName(path.node);
|
|
4905
|
+
// An escape spells the same name (§4.3.7): `c\61 lc(` is `calc()`, so
|
|
4906
|
+
// every table below reads what it spells.
|
|
4907
|
+
const name = written.includes("\\") ? unescapeIdentifier(written) : written;
|
|
4908
|
+
// A container style query asks whether a custom property's value is the one
|
|
4909
|
+
// written here, comparing token streams as written (CSS Conditional 5 §5),
|
|
4910
|
+
// so a squeezed whitespace run asks another question. Nothing is rewritten.
|
|
4911
|
+
if (
|
|
4912
|
+
minify &&
|
|
4913
|
+
!path.within(NodeType.Declaration) &&
|
|
4914
|
+
_S._inMediaConditionPrelude &&
|
|
4915
|
+
asciiCaseInsensitiveMatch(name, "style")
|
|
4916
|
+
) {
|
|
4917
|
+
const source = _printedSource(path.node);
|
|
4918
|
+
// Unclosed at EOF, the source carries no `)` for the walk to have
|
|
4919
|
+
// closed — printed as usual so the parenthesis is written back.
|
|
4920
|
+
if (source.endsWith(")")) {
|
|
4921
|
+
// Written whole, comments included, so the queued copies are claimed
|
|
4922
|
+
// here rather than flushed a second time after the rule.
|
|
4923
|
+
writer.takeInserts(path.start(), path.end());
|
|
4924
|
+
return source;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
let inner;
|
|
4928
|
+
if (path.childCount() === 1) {
|
|
4929
|
+
// Half of all functions take one argument, which `_join` would hand
|
|
4930
|
+
// straight back — and a trim only ever parts two of them, so no
|
|
4931
|
+
// universal a compound implies can stand beside anything here either.
|
|
4932
|
+
const only = writer.get(path.child(0));
|
|
4933
|
+
inner = only === _SEP ? "" : only;
|
|
4934
|
+
} else {
|
|
4935
|
+
// A selector function's argument is a selector, so its combinators need
|
|
4936
|
+
// no whitespace, as in the enclosing prelude; an `@import`'s
|
|
4937
|
+
// `supports()` and a container's `scroll-state()` hold conditions.
|
|
4938
|
+
const lower = name.toLowerCase();
|
|
4939
|
+
const trim =
|
|
4940
|
+
_S._mathFunctionDepth !== 0
|
|
4941
|
+
? _TRIM_MATH
|
|
4942
|
+
: path.within(NodeType.Declaration)
|
|
4943
|
+
? _TRIM_NOTHING
|
|
4944
|
+
: SELECTOR_FUNCTIONS.has(lower)
|
|
4945
|
+
? _TRIM_COMBINATORS
|
|
4946
|
+
: (_S._inSupportsPrelude && lower === "supports") ||
|
|
4947
|
+
(_S._inMediaConditionPrelude && lower === "scroll-state")
|
|
4948
|
+
? _TRIM_CONDITIONS
|
|
4949
|
+
: _TRIM_NOTHING;
|
|
4950
|
+
/** @type {string[]} */
|
|
4951
|
+
const parts = [];
|
|
4952
|
+
_appendChildTexts(path.node, writer, parts);
|
|
4953
|
+
if (minify && trim === _TRIM_CONDITIONS) _keepKeywordComments(parts);
|
|
4954
|
+
// A selector function's argument is a selector like the prelude's, so
|
|
4955
|
+
// the universal a compound implies says nothing there either — except
|
|
4956
|
+
// under test in a `@supports` condition, where only whitespace goes.
|
|
4957
|
+
if (minify && trim === _TRIM_COMBINATORS && !_S._inSupportsPrelude) {
|
|
4958
|
+
if (_foldPseudoNames(parts) && _S._transforms.shortenSelectors) {
|
|
4959
|
+
_unwrapLoneIs(parts);
|
|
4960
|
+
}
|
|
4961
|
+
_dropImpliedUniversalSelector(parts);
|
|
4962
|
+
}
|
|
4963
|
+
inner = _join(parts, !minify, minify ? trim : _TRIM_NOTHING);
|
|
4964
|
+
// A leading `+` in An+B is a sign, not a combinator: `+ n` is no
|
|
4965
|
+
// argument an engine reads, and `+n` would be one.
|
|
4966
|
+
if (minify && NTH_PSEUDO_FUNCTIONS.has(lower)) {
|
|
4967
|
+
const at = parts.findIndex((part) => part.length !== 0 && part !== _SEP);
|
|
4968
|
+
if (parts[at] === "+" && parts[at + 1] === _SEP) {
|
|
4969
|
+
inner = `+ ${inner.slice(1)}`;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
// Chrome throws out `attr( name unit )` where `attr( name unit)`
|
|
4973
|
+
// parses, so the space before the `)` decides whether the declaration
|
|
4974
|
+
// runs — `_join` drops a trailing one like any other.
|
|
4975
|
+
if (
|
|
4976
|
+
minify &&
|
|
4977
|
+
parts[parts.length - 1] === _SEP &&
|
|
4978
|
+
asciiCaseInsensitiveMatch(name, "attr")
|
|
4979
|
+
) {
|
|
4980
|
+
inner += " ";
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
if (!minify) return `${written}(${inner})`;
|
|
4984
|
+
// Function names match ASCII case-insensitively, so one lowercase name
|
|
4985
|
+
// is what every table below is keyed by; the eleven transforms spelled
|
|
4986
|
+
// with a capital are printed the way everything else writes them.
|
|
4987
|
+
const fn = asciiLowercaseName(name);
|
|
4988
|
+
// As with a unit: a name already lowercase is its own answer, and only a
|
|
4989
|
+
// shouted one is looked up. The lookup key still folds inside a
|
|
4990
|
+
// substituted value; only the printed spelling stays as written.
|
|
4991
|
+
let out =
|
|
4992
|
+
_S._inSubstitutedValue ||
|
|
4993
|
+
_S._inSupportsSelector ||
|
|
4994
|
+
!_S._transforms.foldCase ||
|
|
4995
|
+
fn.startsWith("--")
|
|
4996
|
+
? name
|
|
4997
|
+
: fn === name
|
|
4998
|
+
? fn
|
|
4999
|
+
: CANONICAL_NAMES.get(fn) || fn;
|
|
5000
|
+
if (written !== name) {
|
|
5001
|
+
out = _S._transforms.rewriteEscapes ? escapeName(out) : written;
|
|
5002
|
+
}
|
|
5003
|
+
// An+B in a selector: `2n+1` is what `odd` names, and shorter written so.
|
|
5004
|
+
if (!path.within(NodeType.Declaration) && NTH_PSEUDO_FUNCTIONS.has(fn)) {
|
|
5005
|
+
return _S._inSupportsSelector
|
|
5006
|
+
? `${out}(${inner})`
|
|
5007
|
+
: _minifyAnPlusB(out, inner);
|
|
5008
|
+
}
|
|
5009
|
+
// Every rewrite below is keyed by a name none of these carry.
|
|
5010
|
+
if (
|
|
5011
|
+
SUBSTITUTION_FUNCTIONS.has(fn) ||
|
|
5012
|
+
(!path.within(NodeType.Declaration) && SELECTOR_FUNCTIONS.has(fn))
|
|
5013
|
+
) {
|
|
5014
|
+
return `${out}(${inner})`;
|
|
5015
|
+
}
|
|
5016
|
+
// WHY: an `rgb()` or `rgba()` collapses to the shortest color and anything
|
|
5017
|
+
// else keeps its `name(args)` form. A color inside a `@supports` condition
|
|
5018
|
+
// is syntax under test — the engines reading `#0000` are not those reading
|
|
5019
|
+
// `rgba(0,0,0,0)` — so rewriting it asks another question, and esbuild,
|
|
5020
|
+
// clean-css and lightningcss leave the condition alone too. A color read as
|
|
5021
|
+
// numbers, in a gradient stop, a `color-mix()` argument or a relative
|
|
5022
|
+
// color's origin, stays as written unless converting it is exact, which the
|
|
5023
|
+
// legacy sRGB forms are where their channels are bytes.
|
|
5024
|
+
let color = _S._inSupportsPrelude
|
|
5025
|
+
? null
|
|
5026
|
+
: _minifyColorFunction(
|
|
5027
|
+
fn,
|
|
5028
|
+
inner,
|
|
5029
|
+
_S._hexAlphaAllowed,
|
|
5030
|
+
_S._inColorOperand
|
|
5031
|
+
) ||
|
|
5032
|
+
(_S._inColorOperand
|
|
5033
|
+
? null
|
|
5034
|
+
: _minifyPolarColorFunction(fn, inner, _S._hexAlphaAllowed));
|
|
5035
|
+
// A mix no byte holds is written back in the space it was computed in,
|
|
5036
|
+
// which can be longer than the call — then the call is the shorter one.
|
|
5037
|
+
if (color !== null && color.length > fn.length + inner.length + 2) {
|
|
5038
|
+
color = null;
|
|
5039
|
+
}
|
|
5040
|
+
if (color !== null && (color !== "transparent" || !_inTapHighlightColor())) {
|
|
5041
|
+
return color === "transparent" && _S._hexAlphaAllowed ? "#0000" : color;
|
|
5042
|
+
}
|
|
5043
|
+
// `light-dark()` names a color per scheme, which the pair above says with
|
|
5044
|
+
// a substitution the scheme itself switches. Two arguments only: the
|
|
5045
|
+
// grammar takes exactly that many, and anything else is no such call.
|
|
5046
|
+
if (
|
|
5047
|
+
_S._loweringLightDark &&
|
|
5048
|
+
fn === "light-dark" &&
|
|
5049
|
+
path.within(NodeType.Declaration) &&
|
|
5050
|
+
!_S._inCustomProperty &&
|
|
5051
|
+
!_S._inSupportsPrelude &&
|
|
5052
|
+
!_S._inSubstitutedValue
|
|
5053
|
+
) {
|
|
5054
|
+
const halves = _splitTopLevelArguments(inner);
|
|
5055
|
+
// A half the target cannot read would substitute into a `var()` and make
|
|
5056
|
+
// the declaration invalid at computed-value time, where nothing before it
|
|
5057
|
+
// is read — so it is left alone rather than lowered.
|
|
5058
|
+
if (halves.length === 2 && !_namesUnreadableColor(inner)) {
|
|
5059
|
+
_S._lightDarkUsed = true;
|
|
5060
|
+
return `var(${LIGHT_PROPERTY},${halves[0].trim()})var(${DARK_PROPERTY},${halves[1].trim()})`;
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
// A math function over constants of one unit is that value, written back as
|
|
5064
|
+
// a `calc()`: the parentheses stay, since dropping them turns a clamped
|
|
5065
|
+
// negative into a parse error, and only the declaration printer does.
|
|
5066
|
+
if (!_S._inSupportsPrelude && !_S._inCustomProperty) {
|
|
5067
|
+
const term = _foldMathFunction(fn, inner);
|
|
5068
|
+
if (term !== null) {
|
|
5069
|
+
// A term that means the same bare needs no parentheses anywhere, so
|
|
5070
|
+
// it is written as itself; the rest keep a `calc()` for the
|
|
5071
|
+
// declaration printer to judge against the property.
|
|
5072
|
+
const shape =
|
|
5073
|
+
_S._mathFunctionDepth > 1 || _inNumberArgument(path)
|
|
5074
|
+
? _NESTED_TERM_RE
|
|
5075
|
+
: _BARE_TERM_RE;
|
|
5076
|
+
const folded =
|
|
5077
|
+
(shape.test(term) ||
|
|
5078
|
+
(_S._mathFunctionDepth === 1 &&
|
|
5079
|
+
_NEGATIVE_TERM_RE.test(term) &&
|
|
5080
|
+
_inNegativeAcceptingArgument(path))) &&
|
|
5081
|
+
!_inCalcRejectingProperty() &&
|
|
5082
|
+
!(!/[a-z%]$/i.test(term) && _inNumberOnlyOutsideCalcProperty()) &&
|
|
5083
|
+
!_foldLosesClamp(term) &&
|
|
5084
|
+
!_foldBelowStepCount(term, path)
|
|
5085
|
+
? term
|
|
5086
|
+
: `calc(${term})`;
|
|
5087
|
+
// A division can land on a value needing every digit of a double, which
|
|
5088
|
+
// is longer than the expression producing it. Only the outermost call
|
|
5089
|
+
// is asked, a nested one being a term still to be reduced.
|
|
5090
|
+
if (
|
|
5091
|
+
_S._mathFunctionDepth > 1 ||
|
|
5092
|
+
folded.length < fn.length + inner.length + 2 ||
|
|
5093
|
+
_unwrapsShorter(path, folded, fn.length + inner.length + 2)
|
|
5094
|
+
) {
|
|
5095
|
+
return folded;
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
5098
|
+
const reduced = _reduceMathArguments(fn, inner);
|
|
5099
|
+
// A `calc()` inside another math function is the parentheses it
|
|
5100
|
+
// stands for (CSS Values 4 §10.1), whatever a substitution expands to.
|
|
5101
|
+
if (
|
|
5102
|
+
fn === "calc" &&
|
|
5103
|
+
_S._mathFunctionDepth > 1 &&
|
|
5104
|
+
path.within(NodeType.Declaration) &&
|
|
5105
|
+
_S._transforms.reduceFunctions
|
|
5106
|
+
) {
|
|
5107
|
+
return `(${reduced === null ? inner : reduced})`;
|
|
5108
|
+
}
|
|
5109
|
+
if (reduced !== null) return `${fn}(${reduced})`;
|
|
5110
|
+
}
|
|
5111
|
+
// An easing function is only ever a value; `url()` is also an at-rule
|
|
5112
|
+
// prelude's (`@import url("a.css")`).
|
|
5113
|
+
const easing =
|
|
5114
|
+
path.within(NodeType.Declaration) && !_S._inCustomProperty
|
|
5115
|
+
? _minifyEasingFunction(fn, inner)
|
|
5116
|
+
: null;
|
|
5117
|
+
if (easing !== null) return easing;
|
|
5118
|
+
// A translation's percentage is of the element's own box, which always
|
|
5119
|
+
// has a size, so a `0%` term beside one length adds nothing to it.
|
|
5120
|
+
if (
|
|
5121
|
+
_S._transforms.reduceFunctions &&
|
|
5122
|
+
path.within(NodeType.Declaration) &&
|
|
5123
|
+
!_S._inSubstitutedValue &&
|
|
5124
|
+
fn.startsWith("translate") &&
|
|
5125
|
+
inner.includes("0%")
|
|
5126
|
+
) {
|
|
5127
|
+
const args = _splitTopLevelArguments(inner);
|
|
5128
|
+
let changed = false;
|
|
5129
|
+
for (let i = 0; i < args.length; i++) {
|
|
5130
|
+
// A z slot takes no percentage, so a `0%` there makes the whole
|
|
5131
|
+
// declaration invalid, which dropping it would revive.
|
|
5132
|
+
if (fn === "translatez" || (fn === "translate3d" && i === 2)) {
|
|
5133
|
+
continue;
|
|
5134
|
+
}
|
|
5135
|
+
const term = _dropZeroPercentTerm(args[i].trim());
|
|
5136
|
+
if (term !== null) {
|
|
5137
|
+
args[i] = term;
|
|
5138
|
+
changed = true;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
// The rewrites below read the arguments as they now stand.
|
|
5142
|
+
if (changed) inner = args.join(",");
|
|
5143
|
+
}
|
|
5144
|
+
// `translateX(v)` is `translate(v)` and `skewX(a)` is `skew(a)`, the second
|
|
5145
|
+
// component defaulting to 0. Only for one plain component: a substitution
|
|
5146
|
+
// could expand to two, reviving a declaration the browser drops.
|
|
5147
|
+
const oneAxis = _S._transforms.reduceFunctions
|
|
5148
|
+
? X_AXIS_TRANSFORMS.get(fn)
|
|
5149
|
+
: undefined;
|
|
5150
|
+
if (
|
|
5151
|
+
oneAxis !== undefined &&
|
|
5152
|
+
path.within(NodeType.Declaration) &&
|
|
5153
|
+
!_S._inSubstitutedValue &&
|
|
5154
|
+
!inner.includes(",") &&
|
|
5155
|
+
!containsSubstitutionFunction(inner)
|
|
5156
|
+
) {
|
|
5157
|
+
return `${oneAxis}(${inner})`;
|
|
5158
|
+
}
|
|
5159
|
+
if (path.within(NodeType.Declaration) && !_S._inSubstitutedValue) {
|
|
5160
|
+
// An argument that is the amount an omitted one means says nothing,
|
|
5161
|
+
// whether or not the zero still carries the unit its grammar drops.
|
|
5162
|
+
const omitted = _S._transforms.reduceFunctions
|
|
5163
|
+
? FILTER_FUNCTION_OMITTED.get(fn)
|
|
5164
|
+
: undefined;
|
|
5165
|
+
if (omitted !== undefined) {
|
|
5166
|
+
const bare = _dropCallZeroUnit(fn, inner);
|
|
5167
|
+
if (bare === omitted || (omitted === "1" && bare === "100%")) {
|
|
5168
|
+
return `${out}()`;
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
const reduced = _reduceTransformFunctionDeep(fn, inner);
|
|
5172
|
+
if (reduced !== null) return reduced;
|
|
5173
|
+
// The stops are folded over what dropping the direction leaves, a
|
|
5174
|
+
// gradient carrying both otherwise keeping whichever ran second.
|
|
5175
|
+
const shape = _dropDefaultShapeParts(fn, inner);
|
|
5176
|
+
if (shape !== null) return `${out}(${shape})`;
|
|
5177
|
+
let gradient = _reduceGradientDirection(fn, inner);
|
|
5178
|
+
if (gradient === null) {
|
|
5179
|
+
gradient = _dropDefaultGradientPosition(fn, inner);
|
|
5180
|
+
}
|
|
5181
|
+
let stops = _foldGradientStops(
|
|
5182
|
+
fn,
|
|
5183
|
+
gradient === null ? inner : gradient,
|
|
5184
|
+
_S._doublePositionAllowed
|
|
5185
|
+
);
|
|
5186
|
+
// A double position the target cannot read is written as the two
|
|
5187
|
+
// stops it names, over whatever the folds above left.
|
|
5188
|
+
if (!_S._doublePositionAllowed) {
|
|
5189
|
+
const split = !_S._transforms.lowerUnsupported
|
|
5190
|
+
? null
|
|
5191
|
+
: _splitDoublePositionStops(
|
|
5192
|
+
fn,
|
|
5193
|
+
stops !== null ? stops : gradient !== null ? gradient : inner
|
|
5194
|
+
);
|
|
5195
|
+
if (split !== null) stops = split;
|
|
5196
|
+
}
|
|
5197
|
+
if (stops !== null) return `${out}(${stops})`;
|
|
5198
|
+
if (gradient !== null) return `${out}(${gradient})`;
|
|
5199
|
+
}
|
|
5200
|
+
// A zero angle needs no unit where the grammar names `<zero>` beside
|
|
5201
|
+
// `<angle>` (CSS Transforms 2, Filter Effects 1).
|
|
5202
|
+
if (
|
|
5203
|
+
path.within(NodeType.Declaration) &&
|
|
5204
|
+
!_S._inSubstitutedValue &&
|
|
5205
|
+
_S._transforms.shortenNumbers &&
|
|
5206
|
+
ZERO_ANGLE_FUNCTIONS.has(fn)
|
|
5207
|
+
) {
|
|
5208
|
+
const bare = inner.replace(_ZERO_ANGLE_ARGUMENT_RE, "$10");
|
|
5209
|
+
if (bare !== inner) return `${out}(${bare})`;
|
|
5210
|
+
}
|
|
5211
|
+
// A name kept escaped keeps its call as written: unquoting it rests on the
|
|
5212
|
+
// tokenizer reading `url` through the escape.
|
|
5213
|
+
const url =
|
|
5214
|
+
_S._inSubstitutedValue || (written !== name && out === written)
|
|
5215
|
+
? null
|
|
5216
|
+
: _minifyUrlFunction(fn, inner);
|
|
5217
|
+
if (url !== null) return url;
|
|
5218
|
+
return `${out}(${inner})`;
|
|
5219
|
+
};
|
|
5220
|
+
|
|
5221
|
+
/**
|
|
5222
|
+
* The `printer` case for a `url()` token.
|
|
5223
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5224
|
+
* @param {boolean} minify whether minifying
|
|
5225
|
+
* @returns {string} the node's serialized text
|
|
5226
|
+
*/
|
|
5227
|
+
const _printUrlNode = (path, minify) => {
|
|
5228
|
+
const source = _printedSource(path.node);
|
|
5229
|
+
// Closed at EOF: write the `)` back so the url stops where it stopped for
|
|
5230
|
+
// the tokenizer rather than swallowing what the printer emits next. Before
|
|
5231
|
+
// the rest rather than instead of it, or a second pass is what folds it.
|
|
5232
|
+
const unterminated = _isUnterminatedUrl(source);
|
|
5233
|
+
const raw = unterminated ? _terminate(source, ")", "\uFFFD") : source;
|
|
5234
|
+
// The tokenizer already trimmed the url-token's content, so the padding in
|
|
5235
|
+
// `url( a.png )` carries nothing.
|
|
5236
|
+
if (!minify) return raw;
|
|
5237
|
+
const open = raw.indexOf("(");
|
|
5238
|
+
// `url` matches ASCII case-insensitively like any function name. Read in
|
|
5239
|
+
// place, since cutting the name out would allocate on every url and all but
|
|
5240
|
+
// a shouted one folds to itself. In a substituted value nothing is read.
|
|
5241
|
+
let head = null;
|
|
5242
|
+
if (!_S._inSubstitutedValue && _S._transforms.foldCase) {
|
|
5243
|
+
for (let i = 0; i < open; i++) {
|
|
5244
|
+
const c = raw.charCodeAt(i);
|
|
5245
|
+
if (c >= CC_UPPER_A && c <= CC_UPPER_Z) {
|
|
5246
|
+
head = asciiLowercaseName(raw.slice(0, open + 1));
|
|
5247
|
+
break;
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
const percent = raw.includes("%");
|
|
5252
|
+
/**
|
|
5253
|
+
* @returns {string} the url-token as it prints with nothing rendered
|
|
5254
|
+
*/
|
|
5255
|
+
const plainUrl = () => {
|
|
5256
|
+
// Padding only exists when the content is whitespace-bounded; checking
|
|
5257
|
+
// that costs two char reads, reading the content costs a slice.
|
|
5258
|
+
if (
|
|
5259
|
+
!percent &&
|
|
5260
|
+
!isAsciiWhitespace(raw.charCodeAt(open + 1)) &&
|
|
5261
|
+
!isAsciiWhitespace(raw.charCodeAt(raw.length - 2))
|
|
5262
|
+
) {
|
|
5263
|
+
return head === null ? raw : head + raw.slice(open + 1);
|
|
5264
|
+
}
|
|
5265
|
+
const name = head === null ? raw.slice(0, open + 1) : head;
|
|
5266
|
+
const value = _urlValue(path, unterminated);
|
|
5267
|
+
// A url-token already carrying an escape is one this printer did not
|
|
5268
|
+
// write, so its `\%` is not read as the start of a percent-escape.
|
|
5269
|
+
return `${name}${
|
|
5270
|
+
percent && !value.includes("\\")
|
|
5271
|
+
? _decodePercentEscapes(value, true, "")
|
|
5272
|
+
: value
|
|
5273
|
+
})`;
|
|
5274
|
+
};
|
|
5275
|
+
// Ahead of the padding fast path below: a base64 `data:` url is
|
|
5276
|
+
// whitespace-free and `%`-free, so it would take it and never be offered.
|
|
5277
|
+
if (_S._deferEmbeddedSource !== undefined) {
|
|
5278
|
+
const opener = head === null ? raw.slice(0, open + 1) : head;
|
|
5279
|
+
const deferred = _deferDataUrl(
|
|
5280
|
+
_urlValue(path, unterminated),
|
|
5281
|
+
(url) => `${opener}${_serializeUrl(url, '"')})`,
|
|
5282
|
+
plainUrl()
|
|
5283
|
+
);
|
|
5284
|
+
if (deferred !== null) return deferred;
|
|
5285
|
+
}
|
|
5286
|
+
if (_S._renderEmbeddedSource !== undefined) {
|
|
5287
|
+
const renderedUrl = _renderDataUrl(_urlValue(path, unterminated));
|
|
5288
|
+
if (renderedUrl !== null) {
|
|
5289
|
+
const name = head === null ? raw.slice(0, open + 1) : head;
|
|
5290
|
+
return `${name}${_serializeUrl(renderedUrl, '"')})`;
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
return plainUrl();
|
|
5294
|
+
};
|
|
5295
|
+
|
|
5296
|
+
/**
|
|
5297
|
+
* The `printer` case for a string.
|
|
5298
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5299
|
+
* @param {boolean} minify whether minifying
|
|
5300
|
+
* @returns {string} the node's serialized text
|
|
5301
|
+
*/
|
|
5302
|
+
const _printStringNode = (path, minify) => {
|
|
5303
|
+
const source = _printedSource(path.node);
|
|
5304
|
+
// Closed at EOF: write the quote back, for the same reason as `url()`.
|
|
5305
|
+
// Written here rather than after the transforms below, or they read a
|
|
5306
|
+
// string with no closing quote and decline it, which a second pass undoes.
|
|
5307
|
+
const raw = _isClosedString(source)
|
|
5308
|
+
? source
|
|
5309
|
+
: _terminate(source, source[0], "");
|
|
5310
|
+
// A custom property hands its string back as written, quotes included.
|
|
5311
|
+
if (!minify || _S._inCustomProperty) return raw;
|
|
5312
|
+
// `<family-name>` is `<string> | <custom-ident>+`, so a family whose text
|
|
5313
|
+
// is a run of identifiers means the same unquoted, two bytes shorter.
|
|
5314
|
+
if (
|
|
5315
|
+
!_S._inSupportsPrelude &&
|
|
5316
|
+
!_S._inSubstitutedValue &&
|
|
5317
|
+
((_inFontFamily() && _isLoneFamilyName(path)) || _isLoneFontFamily(path))
|
|
5318
|
+
) {
|
|
5319
|
+
const unquoted = _unquoteFontFamily(raw);
|
|
5320
|
+
if (unquoted !== null) return unquoted;
|
|
5321
|
+
}
|
|
5322
|
+
const written = _minifyString(raw);
|
|
5323
|
+
return (_S._charsetPiece === -1 && !_S._assumeUtf8) ||
|
|
5324
|
+
!_S._transforms.rewriteEscapes
|
|
5325
|
+
? written
|
|
5326
|
+
: _unescapeAstral(written);
|
|
5327
|
+
};
|
|
5328
|
+
|
|
5329
|
+
/**
|
|
5330
|
+
* The `printer` case for an ident.
|
|
5331
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5332
|
+
* @param {boolean} minify whether minifying
|
|
5333
|
+
* @returns {string} the node's serialized text
|
|
5334
|
+
*/
|
|
5335
|
+
const _printIdentNode = (path, minify) => {
|
|
5336
|
+
const raw = _printedSource(path.node);
|
|
5337
|
+
if (!minify) return raw;
|
|
5338
|
+
// A keyword is matched by what an escape spells, not by how it is written.
|
|
5339
|
+
const meaning = raw.includes("\\") ? unescapeIdentifier(raw) : raw;
|
|
5340
|
+
// `transparent` is `#0000` where the target reads a hex alpha — the same
|
|
5341
|
+
// color, six bytes shorter. The tap-highlight guard still holds: that
|
|
5342
|
+
// WebKit bug is about the keyword's *value*, whichever way it is spelled.
|
|
5343
|
+
if (
|
|
5344
|
+
path.within(NodeType.Declaration) &&
|
|
5345
|
+
!_S._inSubstitutedValue &&
|
|
5346
|
+
_S._hexAlphaAllowed &&
|
|
5347
|
+
_S._transforms.shortenColors &&
|
|
5348
|
+
asciiCaseInsensitiveMatch(meaning, "transparent") &&
|
|
5349
|
+
!_inTapHighlightColor()
|
|
5350
|
+
) {
|
|
5351
|
+
return "#0000";
|
|
5352
|
+
}
|
|
5353
|
+
// The keyword names each platform's own UI font, which a target that
|
|
5354
|
+
// does not read it reaches only through the platform's own name for it.
|
|
5355
|
+
if (
|
|
5356
|
+
!_S._systemUiAllowed &&
|
|
5357
|
+
_S._transforms.lowerUnsupported &&
|
|
5358
|
+
path.within(NodeType.Declaration) &&
|
|
5359
|
+
!_S._inCustomProperty &&
|
|
5360
|
+
!_S._inSubstitutedValue &&
|
|
5361
|
+
asciiCaseInsensitiveMatch(meaning, "system-ui") &&
|
|
5362
|
+
_inFontFamily()
|
|
5363
|
+
) {
|
|
5364
|
+
return SYSTEM_UI_STACK;
|
|
5365
|
+
}
|
|
5366
|
+
if (
|
|
5367
|
+
path.within(NodeType.Declaration) &&
|
|
5368
|
+
!_S._inCustomProperty &&
|
|
5369
|
+
!_S._inSupportsPrelude &&
|
|
5370
|
+
!_S._inSubstitutedValue
|
|
5371
|
+
) {
|
|
5372
|
+
// One fold for both questions below, handing back the string where nothing
|
|
5373
|
+
// changes. ASCII only: `pin\212A` ends in a Kelvin sign, not a `k`.
|
|
5374
|
+
const lowered = asciiLowercaseName(meaning);
|
|
5375
|
+
// A named color where the property takes nothing else an identifier could
|
|
5376
|
+
// be: `white` is `#fff`, both computing to `rgb(255, 255, 255)`.
|
|
5377
|
+
// Elsewhere an identifier may be the author's own name.
|
|
5378
|
+
if (_S._transforms.shortenColors) {
|
|
5379
|
+
const shorter = getColorNameToShortest().get(lowered);
|
|
5380
|
+
if (
|
|
5381
|
+
shorter !== undefined &&
|
|
5382
|
+
(_inColorOnlyProperty() ||
|
|
5383
|
+
_inGradientArgument(path) ||
|
|
5384
|
+
_atBackgroundTop(path))
|
|
5385
|
+
) {
|
|
5386
|
+
return shorter;
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
// A keyword-only or color property names nothing of the author's, so an
|
|
5390
|
+
// ident directly in its value (a call's arguments are their own grammar's)
|
|
5391
|
+
// is a keyword matched ASCII case-insensitively, but a dashed ident (§4.2).
|
|
5392
|
+
if (
|
|
5393
|
+
_S._transforms.foldCase &&
|
|
5394
|
+
lowered !== raw &&
|
|
5395
|
+
path.parent === _S._valueDeclaration &&
|
|
5396
|
+
!meaning.startsWith("--") &&
|
|
5397
|
+
_inKeywordOnlyValue()
|
|
5398
|
+
) {
|
|
5399
|
+
const folded =
|
|
5400
|
+
meaning !== raw && _S._transforms.rewriteEscapes
|
|
5401
|
+
? escapeIdentifier(lowered)
|
|
5402
|
+
: asciiLowercaseName(raw);
|
|
5403
|
+
if (folded !== raw) return folded;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
return _S._transforms.rewriteEscapes ? _minifyIdentEscapes(raw) : raw;
|
|
5407
|
+
};
|
|
5408
|
+
|
|
5409
|
+
/**
|
|
5410
|
+
* The `printer` case for a simple block.
|
|
5411
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5412
|
+
* @param {PrintContext} writer the print context (children's printed text)
|
|
5413
|
+
* @param {boolean} minify whether minifying
|
|
5414
|
+
* @returns {string} the node's serialized text
|
|
5415
|
+
*/
|
|
5416
|
+
const _printSimpleBlockNode = (path, writer, minify) => {
|
|
5417
|
+
const open = _blockTokenOf(path.node);
|
|
5418
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
5419
|
+
// Outside a declaration value a `[…]` is an attribute selector and a `(…)`
|
|
5420
|
+
// a query condition. Inside one they are a grid line-name list and a
|
|
5421
|
+
// `calc()` sub-expression, where neither of those rewrites holds.
|
|
5422
|
+
const structural = minify && !path.within(NodeType.Declaration);
|
|
5423
|
+
/** @type {string[]} */
|
|
5424
|
+
let parts;
|
|
5425
|
+
if (structural && open === "[") {
|
|
5426
|
+
parts = _printAttributeSelector(path, path.children(), writer);
|
|
5427
|
+
} else {
|
|
5428
|
+
// One array, rather than a child list and a mapped copy of it.
|
|
5429
|
+
parts = [];
|
|
5430
|
+
_appendChildTexts(path.node, writer, parts);
|
|
5431
|
+
}
|
|
5432
|
+
if (structural && open === "(" && _S._inMediaConditionPrelude) {
|
|
5433
|
+
if (_S._transforms.foldCase) _lowercaseConditionParts(parts);
|
|
5434
|
+
if (_S._rangeSpellingAllowed) _useRangeSpelling(parts);
|
|
5435
|
+
}
|
|
5436
|
+
if (
|
|
5437
|
+
minify &&
|
|
5438
|
+
structural &&
|
|
5439
|
+
open === "(" &&
|
|
5440
|
+
(_S._inSupportsPrelude || _S._inMediaConditionPrelude)
|
|
5441
|
+
) {
|
|
5442
|
+
_keepKeywordComments(parts);
|
|
5443
|
+
}
|
|
5444
|
+
let trim = minify && _S._mathFunctionDepth !== 0 ? _TRIM_MATH : _TRIM_NOTHING;
|
|
5445
|
+
if (structural && open === "(") {
|
|
5446
|
+
// `@scope`'s parentheses hold selector lists, so a `:` there starts a
|
|
5447
|
+
// pseudo-class and the whitespace before it is a descendant combinator
|
|
5448
|
+
// — `@scope (div :hover)` is not `@scope (div:hover)`.
|
|
5449
|
+
trim = _inScopePrelude(path) ? _TRIM_COMBINATORS : _TRIM_CONDITIONS;
|
|
5450
|
+
if (trim === _TRIM_COMBINATORS) _foldPseudoNames(parts);
|
|
5451
|
+
}
|
|
5452
|
+
const inner = _join(parts, !minify, trim);
|
|
5453
|
+
return `${open}${inner}${close}`;
|
|
5454
|
+
};
|
|
5455
|
+
|
|
5456
|
+
/**
|
|
5457
|
+
* The `printer` case for a declaration.
|
|
5458
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5459
|
+
* @param {PrintContext} writer the print context (children's printed text)
|
|
5460
|
+
* @param {boolean} minify whether minifying
|
|
5461
|
+
* @param {string} soft `""` minifying, `" "` beautifying
|
|
5462
|
+
* @returns {string} the node's serialized text
|
|
5463
|
+
*/
|
|
5464
|
+
const _printDeclarationNode = (path, writer, minify, soft) => {
|
|
5465
|
+
const name = _rawName(path.node);
|
|
5466
|
+
// WHY: a property is matched by what its name means, not by how it was
|
|
5467
|
+
// spelled — §4.3.7 lets any of its letters be written as an escape — so
|
|
5468
|
+
// every lookup below reads the unescaped name while the printed one
|
|
5469
|
+
// keeps the source's. It costs an `indexOf` where nothing is escaped.
|
|
5470
|
+
const meaning = path.name();
|
|
5471
|
+
// Counted rather than listed: a declaration's value is one component 92
|
|
5472
|
+
// times in 100, and the list would be built only to be indexed twice.
|
|
5473
|
+
const count = path.childCount();
|
|
5474
|
+
// `@property`'s `initial-value` is read against the sibling `syntax`
|
|
5475
|
+
// descriptor, so it is as opaque as a custom property's own value.
|
|
5476
|
+
const custom =
|
|
5477
|
+
meaning.startsWith("--") ||
|
|
5478
|
+
(_S._inPropertyRule &&
|
|
5479
|
+
asciiCaseInsensitiveMatch(meaning, "initial-value")) ||
|
|
5480
|
+
// `@function`'s `result` is the token stream the call substitutes, so
|
|
5481
|
+
// it is opaque the same way — and empty when the function returns the
|
|
5482
|
+
// guaranteed-invalid value.
|
|
5483
|
+
(_S._inFunctionRule && asciiCaseInsensitiveMatch(meaning, "result")) ||
|
|
5484
|
+
// A `{}` block standing as the whole value is a token stream the
|
|
5485
|
+
// engine holds as written too — no grammar reads it, so a rewrite of
|
|
5486
|
+
// it builds a different CSSOM from the same document.
|
|
5487
|
+
(count === 1 &&
|
|
5488
|
+
path.type(path.child(0)) === NodeType.SimpleBlock &&
|
|
5489
|
+
_blockTokenOf(path.child(0)) === "{");
|
|
5490
|
+
// A value-less declaration is invalid, so it is already ignored — except
|
|
5491
|
+
// on a custom property, where the empty value is the guaranteed-invalid
|
|
5492
|
+
// one a `var()` fallback reads.
|
|
5493
|
+
if (minify && _S._transforms.removeDeadRules && count === 0 && !custom) {
|
|
5494
|
+
return "";
|
|
5495
|
+
}
|
|
5496
|
+
// The name folded to lowercase, where the value below needed it; `name`
|
|
5497
|
+
// itself where it did not, which is what the printed name reads as
|
|
5498
|
+
// "nothing to fold".
|
|
5499
|
+
let lowered = name;
|
|
5500
|
+
let value = "";
|
|
5501
|
+
if (count !== 0) {
|
|
5502
|
+
// Property names match ASCII case-insensitively and a custom property's
|
|
5503
|
+
// does not, so skipping it skips the lowercasing. One fold serves both
|
|
5504
|
+
// which value rules apply and how the name prints.
|
|
5505
|
+
lowered = custom ? meaning : toLowerCaseIfNeeded(meaning);
|
|
5506
|
+
const property = custom ? meaning : _standardSpelling(lowered);
|
|
5507
|
+
if (custom) {
|
|
5508
|
+
// `getPropertyValue()` hands this text back, so a rewritten token
|
|
5509
|
+
// would be a different CSSOM; only the boundaries between them go.
|
|
5510
|
+
const from = path.start(path.child(0));
|
|
5511
|
+
const to = path.end(path.child(count - 1));
|
|
5512
|
+
if (minify) {
|
|
5513
|
+
value = _customPropertyValue(path, path.node, writer, from, to);
|
|
5514
|
+
} else {
|
|
5515
|
+
// Straight from source, so the kept comments in it are already
|
|
5516
|
+
// there — claim them, or the writer emits them a second time
|
|
5517
|
+
// ahead of the next top-level node.
|
|
5518
|
+
writer.takeInserts(from, to);
|
|
5519
|
+
value = _S._input.slice(from, to);
|
|
5520
|
+
if (to === _S._input.length) value = _closeAtEof(value);
|
|
5521
|
+
}
|
|
5522
|
+
// Closed as printed, since a `}` may still follow; the grammar leaves it
|
|
5523
|
+
// open where nothing does, as the engine keeps it.
|
|
5524
|
+
const last = _S._eofPrinted;
|
|
5525
|
+
if (
|
|
5526
|
+
to === _S._input.length &&
|
|
5527
|
+
last !== "" &&
|
|
5528
|
+
value.endsWith(last) &&
|
|
5529
|
+
last !== _S._eofOpen
|
|
5530
|
+
) {
|
|
5531
|
+
_S._openCustomValue = {
|
|
5532
|
+
printed: value,
|
|
5533
|
+
source: value.slice(0, value.length - last.length) + _S._eofOpen
|
|
5534
|
+
};
|
|
5535
|
+
}
|
|
5536
|
+
} else if (property === "unicode-range") {
|
|
5537
|
+
// `U+…` tokenizes as numbers, so the generic numeric normalization
|
|
5538
|
+
// would corrupt it; each range is shortened as the urange it is.
|
|
5539
|
+
let raw = "";
|
|
5540
|
+
for (let at = 0; at < count; at++) {
|
|
5541
|
+
raw += _printedSource(path.child(at));
|
|
5542
|
+
}
|
|
5543
|
+
value = minify
|
|
5544
|
+
? raw
|
|
5545
|
+
.split(",")
|
|
5546
|
+
.map((one) => _minifyUnicodeRange(one.trim()))
|
|
5547
|
+
.join(",")
|
|
5548
|
+
: raw;
|
|
5549
|
+
} else {
|
|
5550
|
+
// Value hashes were already shortened by the hash printer, printing in a
|
|
5551
|
+
// value context, so this joins the children's text. A shorthand whose
|
|
5552
|
+
// value repeats what the notation implies collapses.
|
|
5553
|
+
const shorthand = minify
|
|
5554
|
+
? _collapseShorthand(path, property, path.node, writer)
|
|
5555
|
+
: null;
|
|
5556
|
+
if (shorthand === null && count === 1) {
|
|
5557
|
+
// What a value is 98 times in 100, and `_join` hands a lone fragment
|
|
5558
|
+
// back, so it is rewritten without an array. §5.4.6 step 7 pops a
|
|
5559
|
+
// trailing whitespace, so that child is never the `""` case.
|
|
5560
|
+
value = _valueFragment(writer.get(path.child(0)), property, minify);
|
|
5561
|
+
} else {
|
|
5562
|
+
/** @type {string[]} */
|
|
5563
|
+
let fragments;
|
|
5564
|
+
if (shorthand === null) {
|
|
5565
|
+
fragments = [];
|
|
5566
|
+
_appendChildTexts(path.node, writer, fragments);
|
|
5567
|
+
} else {
|
|
5568
|
+
fragments = shorthand;
|
|
5569
|
+
}
|
|
5570
|
+
// In place: a `map` per transform would allocate an array each.
|
|
5571
|
+
for (let i = 0; i < fragments.length; i++) {
|
|
5572
|
+
fragments[i] = _valueFragment(fragments[i], property, minify);
|
|
5573
|
+
}
|
|
5574
|
+
// The string transforms below read space-separated components, so
|
|
5575
|
+
// only a value none of them covers loses the separators its tokens
|
|
5576
|
+
// do not need — a substitution's tokens included.
|
|
5577
|
+
const separatorsOnly =
|
|
5578
|
+
minify &&
|
|
5579
|
+
!AUTO_SECOND_VALUE_PROPERTIES.has(property) &&
|
|
5580
|
+
!ALPHA_VALUE_PROPERTIES.has(property) &&
|
|
5581
|
+
!RATIO_PROPERTIES.has(property);
|
|
5582
|
+
value = _join(
|
|
5583
|
+
fragments,
|
|
5584
|
+
!minify,
|
|
5585
|
+
separatorsOnly ? _TRIM_SEPARATORS : _TRIM_NOTHING
|
|
5586
|
+
);
|
|
5587
|
+
}
|
|
5588
|
+
if (minify) {
|
|
5589
|
+
value = _dropDefaultSecondValue(property, value);
|
|
5590
|
+
value = _numberAlphaValue(property, value);
|
|
5591
|
+
value = _dropRatioDenominator(property, value);
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
}
|
|
5595
|
+
const important = _isImportant(path.node) ? `${soft}!important` : "";
|
|
5596
|
+
// Property names match ASCII case-insensitively; a custom property's
|
|
5597
|
+
// does not, which is what `custom` already stands for — nor does an
|
|
5598
|
+
// `@font-feature-values` sub-rule's, which names a feature value.
|
|
5599
|
+
const folds =
|
|
5600
|
+
minify && _S._transforms.foldCase && !custom && !_S._inFeatureValuesRule;
|
|
5601
|
+
let printedName = folds && lowered !== name ? asciiLowercaseName(name) : name;
|
|
5602
|
+
// An escape spells the same name, so the name is written the shortest way.
|
|
5603
|
+
if (minify && _S._transforms.rewriteEscapes && meaning !== name) {
|
|
5604
|
+
const spelled = escapeName(folds ? asciiLowercaseName(meaning) : meaning);
|
|
5605
|
+
if (spelled.length <= printedName.length) printedName = spelled;
|
|
5606
|
+
}
|
|
5607
|
+
return `${printedName}:${soft}${value}${important};`;
|
|
5608
|
+
};
|
|
5609
|
+
|
|
5610
|
+
/**
|
|
5611
|
+
* The `printer` case for an at-rule or a qualified rule.
|
|
5612
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5613
|
+
* @param {PrintContext} writer the print context (children's printed text)
|
|
5614
|
+
* @param {boolean} minify whether minifying
|
|
5615
|
+
* @param {string} soft `""` minifying, `" "` beautifying
|
|
5616
|
+
* @returns {string} the node's serialized text
|
|
5617
|
+
*/
|
|
5618
|
+
const _printRuleNode = (path, writer, minify, soft) => {
|
|
5619
|
+
// Prefixing reads the prelude's tokens, not its joined text.
|
|
5620
|
+
const preludeParts = _S._prefixingOn
|
|
5621
|
+
? /** @type {string[]} */ ([])
|
|
5622
|
+
: undefined;
|
|
5623
|
+
const prelude = _rulePrelude(path, writer, minify, preludeParts);
|
|
5624
|
+
const decls = _declarationsOf(path.node);
|
|
5625
|
+
// A qualified rule always has a block; an at-rule has one only when its
|
|
5626
|
+
// declaration list is non-null — else it is `@…;`.
|
|
5627
|
+
if (decls === null) {
|
|
5628
|
+
if (minify) _blockSpans.push(_NO_BLOCK_ENTRY);
|
|
5629
|
+
// `@charset` names the encoding the *source* was read in, and what is
|
|
5630
|
+
// written out is UTF-8 whatever it said — so carrying it over is at
|
|
5631
|
+
// best 22 dead bytes and at worst an encoding the output is not in.
|
|
5632
|
+
if (
|
|
5633
|
+
minify &&
|
|
5634
|
+
_S._transforms.removeDeadRules &&
|
|
5635
|
+
asciiCaseInsensitiveMatch(_rawName(path.node), "charset")
|
|
5636
|
+
) {
|
|
5637
|
+
return "";
|
|
5638
|
+
}
|
|
5639
|
+
// The rule only goes where no target reads one: a condition this has
|
|
5640
|
+
// not come to yet still needs it, and so does an engine that reads it.
|
|
5641
|
+
if (
|
|
5642
|
+
minify &&
|
|
5643
|
+
_S._transforms.customMedia &&
|
|
5644
|
+
_atKeywordOf(path.node) === "custom-media" &&
|
|
5645
|
+
_takeCustomMedia(prelude) &&
|
|
5646
|
+
!_S._customMediaAllowed
|
|
5647
|
+
) {
|
|
5648
|
+
return "";
|
|
5649
|
+
}
|
|
5650
|
+
// No engine reads `@custom-selector`, so the rule always goes once its
|
|
5651
|
+
// name is down — but only where `:is()` can carry what it stands for.
|
|
5652
|
+
if (
|
|
5653
|
+
minify &&
|
|
5654
|
+
_S._transforms.customSelectors &&
|
|
5655
|
+
_S._isSelectorAllowed &&
|
|
5656
|
+
_atKeywordOf(path.node) === "custom-selector" &&
|
|
5657
|
+
_takeCustomSelector(prelude)
|
|
5658
|
+
) {
|
|
5659
|
+
return "";
|
|
5660
|
+
}
|
|
5661
|
+
return `${prelude};`;
|
|
5662
|
+
}
|
|
5663
|
+
// Read before the body: composing it is what leaves the block empty, and
|
|
5664
|
+
// the drop below asks the same question again.
|
|
5665
|
+
const unusedRule = _takeUnusedRule(_S._currentNode);
|
|
5666
|
+
const rules = _rulesOf(path.node);
|
|
5667
|
+
// `nl` prefixes each item: nothing minifying, a line break beautifying
|
|
5668
|
+
// (declarations end in `;`, rules don't).
|
|
5669
|
+
const nl = minify ? "" : "\n";
|
|
5670
|
+
const memoized = _memoizedBlockBody(_S._currentNode);
|
|
5671
|
+
const {
|
|
5672
|
+
body,
|
|
5673
|
+
items,
|
|
5674
|
+
texts,
|
|
5675
|
+
superseded,
|
|
5676
|
+
droppedPrefix,
|
|
5677
|
+
addedPrefix,
|
|
5678
|
+
deadPrefixed,
|
|
5679
|
+
spans
|
|
5680
|
+
} =
|
|
5681
|
+
memoized === undefined
|
|
5682
|
+
? _composeBlockBody(
|
|
5683
|
+
path,
|
|
5684
|
+
decls,
|
|
5685
|
+
rules,
|
|
5686
|
+
writer,
|
|
5687
|
+
minify,
|
|
5688
|
+
nl,
|
|
5689
|
+
_S._currentNode,
|
|
5690
|
+
unusedRule
|
|
5691
|
+
)
|
|
5692
|
+
: // A rule its selector made unused writes nothing, whatever its block.
|
|
5693
|
+
_memoizedComposition(unusedRule ? "" : memoized);
|
|
5694
|
+
_memoizeBlockBody(
|
|
5695
|
+
_S._currentNode,
|
|
5696
|
+
memoized === undefined && !unusedRule ? body : null
|
|
5697
|
+
);
|
|
5698
|
+
if (
|
|
5699
|
+
minify &&
|
|
5700
|
+
// An empty rule paints nothing, so dropping it leaves the cascade as it
|
|
5701
|
+
// was — but only where the block means nothing (`_streamClose` agrees).
|
|
5702
|
+
_S._transforms.removeDeadRules &&
|
|
5703
|
+
body.length === 0 &&
|
|
5704
|
+
// See `_streamClose`: a rule taken out while a `@namespace` after it
|
|
5705
|
+
// could still be read would move one up into a live position.
|
|
5706
|
+
!(path.parent === null && _S._namespacePrologueOpen) &&
|
|
5707
|
+
(path.type() === NodeType.QualifiedRule ||
|
|
5708
|
+
DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atKeywordOf(path.node)) ||
|
|
5709
|
+
unusedRule)
|
|
5710
|
+
) {
|
|
5711
|
+
// The rules written in it are not empty for its being so, and what the
|
|
5712
|
+
// hoist writes for them is rules a later copy can take back.
|
|
5713
|
+
_blockSpans.push(
|
|
5714
|
+
path.type() === NodeType.QualifiedRule
|
|
5715
|
+
? _NO_BLOCK_ENTRY_QUALIFIED
|
|
5716
|
+
: _NO_BLOCK_ENTRY
|
|
5717
|
+
);
|
|
5718
|
+
{
|
|
5719
|
+
const written =
|
|
5720
|
+
_writeNested(path, _S._currentNode, prelude, null, true) || "";
|
|
5721
|
+
if (minify && written.length !== 0) {
|
|
5722
|
+
_recordHoistedRule(written, null, 0);
|
|
5723
|
+
}
|
|
5724
|
+
return written;
|
|
5725
|
+
}
|
|
5726
|
+
}
|
|
5727
|
+
// A rule that says which color scheme it is in says it again as the pair
|
|
5728
|
+
// a lowered `light-dark()` reads, and — where it hands the choice to the
|
|
5729
|
+
// user's own preference — once more under the query that asks for it.
|
|
5730
|
+
let scheme = body;
|
|
5731
|
+
let schemeCopy = "";
|
|
5732
|
+
if (
|
|
5733
|
+
_S._loweringLightDark &&
|
|
5734
|
+
_S._sourceNamesLightDark &&
|
|
5735
|
+
path.type() === NodeType.QualifiedRule &&
|
|
5736
|
+
body.includes("color-scheme:") &&
|
|
5737
|
+
// The pair as this writes it, not either name on its own: a
|
|
5738
|
+
// block carrying that is one a lowering pass wrote. An authored
|
|
5739
|
+
// `--webpack-light` is a declaration of the author's.
|
|
5740
|
+
!body.includes(LIGHT_DARK_WRITTEN) &&
|
|
5741
|
+
!body.includes(LIGHT_DARK_WRITTEN_DARK)
|
|
5742
|
+
) {
|
|
5743
|
+
const written = _COLOR_SCHEME_RE.exec(body);
|
|
5744
|
+
const toggle =
|
|
5745
|
+
written === null
|
|
5746
|
+
? null
|
|
5747
|
+
: _lightDarkToggle(written[1].replace(_IMPORTANT, "").trim());
|
|
5748
|
+
if (toggle !== null) {
|
|
5749
|
+
// The body's own closing `;` went with the minified print, and the
|
|
5750
|
+
// toggle's last is the one this block now ends on.
|
|
5751
|
+
scheme = `${body};${toggle.toggle.slice(0, -1)}`;
|
|
5752
|
+
if (toggle.flip) {
|
|
5753
|
+
schemeCopy = `${_PREFERS_DARK}${soft}{${_OWN_PRELUDE}${soft}{${LIGHT_PROPERTY}:;${DARK_PROPERTY}:initial}}`;
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
// The hoist's copy names the prelude as the run settles it, since one
|
|
5758
|
+
// taken out is resolved against its parent and may be joined into a list.
|
|
5759
|
+
const ownRest = `${soft}{${scheme}${nl}}${schemeCopy}`;
|
|
5760
|
+
const rest =
|
|
5761
|
+
schemeCopy === "" ? ownRest : ownRest.replace(_OWN_PRELUDE, () => prelude);
|
|
5762
|
+
const text = `${prelude}${rest}`;
|
|
5763
|
+
// Prefix copies turn one rule into several, so a rewritten rule is not
|
|
5764
|
+
// offered for joining: its text no longer describes a single block. Each
|
|
5765
|
+
// block's rules are their own siblings, pairing within their scope.
|
|
5766
|
+
let out = text;
|
|
5767
|
+
if (_S._prefixingOn) {
|
|
5768
|
+
// Read and clear here, so the lookahead is the one rule the writer
|
|
5769
|
+
// holds — which only a top-level rule ever is — and never a rule
|
|
5770
|
+
// further back.
|
|
5771
|
+
const parent = path.parent;
|
|
5772
|
+
const top = parent === null;
|
|
5773
|
+
const scope = _prefixScope(parent);
|
|
5774
|
+
out =
|
|
5775
|
+
path.type() === NodeType.AtRule
|
|
5776
|
+
? _prefixAtRule(path, text, prelude, scope, top)
|
|
5777
|
+
: _prefixQualifiedRule(
|
|
5778
|
+
text,
|
|
5779
|
+
/** @type {string[]} */ (preludeParts),
|
|
5780
|
+
soft,
|
|
5781
|
+
body,
|
|
5782
|
+
scope,
|
|
5783
|
+
top
|
|
5784
|
+
);
|
|
5785
|
+
}
|
|
5786
|
+
// WHY: pushed whatever it holds, since the parent takes one off per child
|
|
5787
|
+
// with a body, so a block carrying no rule still has to be there. A
|
|
5788
|
+
// rewritten rule records no nesting — those spans name `text` rather than
|
|
5789
|
+
// `out` — but is still the one rule its text says it is. Only a block that
|
|
5790
|
+
// recorded something needs an entry of its own, a leaf rule taking the
|
|
5791
|
+
// shared one, and nothing is pushed while not minifying.
|
|
5792
|
+
if (minify) {
|
|
5793
|
+
_blockSpans.push(
|
|
5794
|
+
spans === null || out !== text
|
|
5795
|
+
? path.type() === NodeType.QualifiedRule
|
|
5796
|
+
? _NO_BLOCK_ENTRY_QUALIFIED
|
|
5797
|
+
: _NO_BLOCK_ENTRY
|
|
5798
|
+
: {
|
|
5799
|
+
bodyAt: prelude.length + soft.length + 1,
|
|
5800
|
+
prelude,
|
|
5801
|
+
keyPrelude: _openerKey(`${prelude}{`),
|
|
5802
|
+
qualified: path.type() === NodeType.QualifiedRule,
|
|
5803
|
+
spans
|
|
5804
|
+
}
|
|
5805
|
+
);
|
|
5806
|
+
}
|
|
5807
|
+
// A rewritten rule is several rules with preludes of their own, and each
|
|
5808
|
+
// carries this rule's block — so the hoist takes them out. Only where
|
|
5809
|
+
// it holds no rule of its own, those naming one prelude of the several.
|
|
5810
|
+
const copies =
|
|
5811
|
+
out === text ||
|
|
5812
|
+
_prefixCopyPreludes.length === 0 ||
|
|
5813
|
+
_holdsNested(_S._currentNode)
|
|
5814
|
+
? undefined
|
|
5815
|
+
: _prefixCopyPreludes;
|
|
5816
|
+
const nested = _writeNested(
|
|
5817
|
+
path,
|
|
5818
|
+
_S._currentNode,
|
|
5819
|
+
prelude,
|
|
5820
|
+
ownRest,
|
|
5821
|
+
out === text || copies !== undefined,
|
|
5822
|
+
copies,
|
|
5823
|
+
`${soft}{${body}}`
|
|
5824
|
+
);
|
|
5825
|
+
if (nested === null) return "";
|
|
5826
|
+
if (out !== text) return out + nested;
|
|
5827
|
+
// The run wrote this rule's selectors into itself, so what it returns is
|
|
5828
|
+
// the whole of both — `text` being this rule's own `prelude` and `rest`.
|
|
5829
|
+
if (nested.length !== 0) {
|
|
5830
|
+
if (_nestedTookParent) {
|
|
5831
|
+
if (minify) _recordHoistedRule(nested, null, 0);
|
|
5832
|
+
return nested;
|
|
5833
|
+
}
|
|
5834
|
+
if (minify) _recordHoistedRule(nested, text, prelude.length);
|
|
5835
|
+
return text + nested;
|
|
5836
|
+
}
|
|
5837
|
+
// Where the block is what a sibling is compared against, remember the rule
|
|
5838
|
+
// so the parent parts it without re-scanning for the `{`. An at-rule
|
|
5839
|
+
// remembers its block's entries too, so a sibling's rules can join them.
|
|
5840
|
+
if (
|
|
5841
|
+
minify &&
|
|
5842
|
+
path.type() === NodeType.AtRule &&
|
|
5843
|
+
MERGEABLE_AT_RULES.has(_atKeywordOf(path.node))
|
|
5844
|
+
) {
|
|
5845
|
+
/** @type {RuleEntry[]} */
|
|
5846
|
+
const children = [];
|
|
5847
|
+
for (let i = 0; i < texts.length; i++) {
|
|
5848
|
+
if (texts[i].length === 0) continue;
|
|
5849
|
+
if (superseded !== null && superseded.has(i)) continue;
|
|
5850
|
+
// The entries have to spell the block this rule printed, prefixes and
|
|
5851
|
+
// all: a join rebuilds the body from them, and one built from the
|
|
5852
|
+
// unprefixed texts would restore what was dropped.
|
|
5853
|
+
if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
|
|
5854
|
+
if (deadPrefixed !== null && deadPrefixed.has(items[i])) continue;
|
|
5855
|
+
const added = addedPrefix === null ? undefined : addedPrefix.get(i);
|
|
5856
|
+
children.push(
|
|
5857
|
+
added === undefined
|
|
5858
|
+
? _ruleEntryOf(items[i], texts[i])
|
|
5859
|
+
: _opaqueEntry(added + texts[i])
|
|
5860
|
+
);
|
|
5861
|
+
}
|
|
5862
|
+
let head = "";
|
|
5863
|
+
for (let i = 0; i < children.length - 1; i++) head += children[i].text;
|
|
5864
|
+
_setRuleEntry(_S._currentNode, {
|
|
5865
|
+
text,
|
|
5866
|
+
prelude: prelude.length,
|
|
5867
|
+
atRule: true,
|
|
5868
|
+
plain: false,
|
|
5869
|
+
listable: LIST_NO,
|
|
5870
|
+
listKind: LIST_KIND_SELECTOR,
|
|
5871
|
+
children,
|
|
5872
|
+
head,
|
|
5873
|
+
// The block's own merges have run and settled what they grew, so the
|
|
5874
|
+
// text above and the children agree; a list one of them still carries
|
|
5875
|
+
// concatenated is one this block says is there to settle.
|
|
5876
|
+
unordered: _anyUnordered(children),
|
|
5877
|
+
list: null,
|
|
5878
|
+
block: null,
|
|
5879
|
+
declarations: null
|
|
5880
|
+
});
|
|
5881
|
+
}
|
|
5882
|
+
if (minify && path.type() === NodeType.QualifiedRule) {
|
|
5883
|
+
// Only a block of declarations lends its selectors to another's list: a
|
|
5884
|
+
// nested rule's `&` stands for the whole list and `:is(…)` takes its most
|
|
5885
|
+
// specific selector, so joining preludes moves what the nested beat.
|
|
5886
|
+
const plain = rules === null || rules.length === 0;
|
|
5887
|
+
const parent = path.parent;
|
|
5888
|
+
// Which shape a join would read the prelude with — the parent is gone by
|
|
5889
|
+
// then. A keyframe selector is a list of its own shape, which
|
|
5890
|
+
// `_rulePrelude` has already rewritten `from` in.
|
|
5891
|
+
let listKind = LIST_KIND_SELECTOR;
|
|
5892
|
+
if (parent !== null) {
|
|
5893
|
+
const parentType = path.type(parent);
|
|
5894
|
+
if (parentType === NodeType.QualifiedRule) {
|
|
5895
|
+
listKind = LIST_KIND_NESTED;
|
|
5896
|
+
} else if (
|
|
5897
|
+
parentType === NodeType.AtRule &&
|
|
5898
|
+
KEYFRAMES_AT_RULE_RE.test(_atKeywordOf(parent))
|
|
5899
|
+
) {
|
|
5900
|
+
listKind = LIST_KIND_KEYFRAME;
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
_setRuleEntry(_S._currentNode, {
|
|
5904
|
+
text,
|
|
5905
|
+
prelude: prelude.length,
|
|
5906
|
+
atRule: false,
|
|
5907
|
+
plain,
|
|
5908
|
+
listable: plain ? LIST_UNKNOWN : LIST_NO,
|
|
5909
|
+
listKind,
|
|
5910
|
+
children: null,
|
|
5911
|
+
head: "",
|
|
5912
|
+
unordered: false,
|
|
5913
|
+
list: null,
|
|
5914
|
+
block: null,
|
|
5915
|
+
declarations: null
|
|
5916
|
+
});
|
|
5917
|
+
}
|
|
5918
|
+
return text;
|
|
5919
|
+
};
|
|
5920
|
+
|
|
5921
|
+
/**
|
|
5922
|
+
* The `printer` case for a hash.
|
|
5923
|
+
* @param {CssPath} path the accessor positioned on the finished node
|
|
5924
|
+
* @param {boolean} minify whether minifying
|
|
5925
|
+
* @returns {string} the node's serialized text
|
|
5926
|
+
*/
|
|
5927
|
+
const _printHashNode = (path, minify) => {
|
|
5928
|
+
const raw = _printedSource(path.node);
|
|
5929
|
+
if (!minify) return raw;
|
|
5930
|
+
// A hash is a hex color in a value and an id in a selector, and ids are
|
|
5931
|
+
// case-sensitive, so only the former shortens — at any value depth. An id's
|
|
5932
|
+
// escapes still shorten, the `#` held back so its first code point is one.
|
|
5933
|
+
if (!path.within(NodeType.Declaration)) {
|
|
5934
|
+
return _S._transforms.rewriteEscapes
|
|
5935
|
+
? `#${_minifyIdentEscapes(raw.slice(1))}`
|
|
5936
|
+
: raw;
|
|
5937
|
+
}
|
|
5938
|
+
// Inside a function only the known color functions take color hashes, and a
|
|
5939
|
+
// substitution's fallback, being the property's value. `paint()` is the
|
|
5940
|
+
// exception: its arguments reach a worklet rather than a declaration.
|
|
5941
|
+
const parent = path.parent;
|
|
5942
|
+
if (parent !== null && path.type(parent) === NodeType.Function) {
|
|
5943
|
+
const fn = unescapeIdentifier(_rawName(parent)).toLowerCase();
|
|
5944
|
+
if (
|
|
5945
|
+
!COLOR_ARGUMENT_FUNCTIONS.has(fn) &&
|
|
5946
|
+
!(SUBSTITUTION_FUNCTIONS.has(fn) && fn !== "paint")
|
|
5947
|
+
) {
|
|
5948
|
+
return raw;
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
const short = _minifyHash(raw);
|
|
5952
|
+
const text = short === null ? raw : short;
|
|
5953
|
+
// A hex alpha the target cannot read is written as the `rgba()` it names.
|
|
5954
|
+
if (_S._hexAlphaAllowed) return text;
|
|
5955
|
+
const lowered = _S._transforms.lowerUnsupported
|
|
5956
|
+
? _hexAlphaFallback(text)
|
|
5957
|
+
: null;
|
|
5958
|
+
return lowered === null ? text : lowered;
|
|
5959
|
+
};
|
|
5960
|
+
|
|
4896
5961
|
/**
|
|
4897
5962
|
* The default CSS node printer — passed to the `SourceProcessor` and fired once a
|
|
4898
5963
|
* node's visitors and children are done (a developer could supply their own).
|
|
@@ -4939,993 +6004,21 @@ const printer = (path, writer) => {
|
|
|
4939
6004
|
? _normalizeNumericToken(raw, _isRgbChannel(path))
|
|
4940
6005
|
: raw;
|
|
4941
6006
|
}
|
|
4942
|
-
case NodeType.Function:
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
!path.within(NodeType.Declaration) &&
|
|
4955
|
-
_S._inMediaConditionPrelude &&
|
|
4956
|
-
asciiCaseInsensitiveMatch(name, "style")
|
|
4957
|
-
) {
|
|
4958
|
-
const source = _printedSource(path.node);
|
|
4959
|
-
// Unclosed at EOF, the source carries no `)` for the walk to have
|
|
4960
|
-
// closed — printed as usual so the parenthesis is written back.
|
|
4961
|
-
if (source.endsWith(")")) {
|
|
4962
|
-
// Written whole, comments included, so the queued copies are claimed
|
|
4963
|
-
// here rather than flushed a second time after the rule.
|
|
4964
|
-
writer.takeInserts(path.start(), path.end());
|
|
4965
|
-
return source;
|
|
4966
|
-
}
|
|
4967
|
-
}
|
|
4968
|
-
let inner;
|
|
4969
|
-
if (path.childCount() === 1) {
|
|
4970
|
-
// Half of all functions take one argument, which `_join` would hand
|
|
4971
|
-
// straight back — and a trim only ever parts two of them, so no
|
|
4972
|
-
// universal a compound implies can stand beside anything here either.
|
|
4973
|
-
const only = writer.get(path.child(0));
|
|
4974
|
-
inner = only === _SEP ? "" : only;
|
|
4975
|
-
} else {
|
|
4976
|
-
// A selector function's argument is a selector, so its combinators need
|
|
4977
|
-
// no whitespace, as in the enclosing prelude; an `@import`'s
|
|
4978
|
-
// `supports()` and a container's `scroll-state()` hold conditions.
|
|
4979
|
-
const lower = name.toLowerCase();
|
|
4980
|
-
const trim =
|
|
4981
|
-
_S._mathFunctionDepth !== 0
|
|
4982
|
-
? _TRIM_MATH
|
|
4983
|
-
: path.within(NodeType.Declaration)
|
|
4984
|
-
? _TRIM_NOTHING
|
|
4985
|
-
: SELECTOR_FUNCTIONS.has(lower)
|
|
4986
|
-
? _TRIM_COMBINATORS
|
|
4987
|
-
: (_S._inSupportsPrelude && lower === "supports") ||
|
|
4988
|
-
(_S._inMediaConditionPrelude && lower === "scroll-state")
|
|
4989
|
-
? _TRIM_CONDITIONS
|
|
4990
|
-
: _TRIM_NOTHING;
|
|
4991
|
-
/** @type {string[]} */
|
|
4992
|
-
const parts = [];
|
|
4993
|
-
_appendChildTexts(path.node, writer, parts);
|
|
4994
|
-
if (minify && trim === _TRIM_CONDITIONS) _keepKeywordComments(parts);
|
|
4995
|
-
// A selector function's argument is a selector like the prelude's, so
|
|
4996
|
-
// the universal a compound implies says nothing there either — except
|
|
4997
|
-
// under test in a `@supports` condition, where only whitespace goes.
|
|
4998
|
-
if (minify && trim === _TRIM_COMBINATORS && !_S._inSupportsPrelude) {
|
|
4999
|
-
_foldPseudoNames(parts);
|
|
5000
|
-
if (_S._transforms.shortenSelectors) _unwrapLoneIs(parts);
|
|
5001
|
-
_dropImpliedUniversalSelector(parts);
|
|
5002
|
-
}
|
|
5003
|
-
inner = _join(parts, !minify, minify ? trim : _TRIM_NOTHING);
|
|
5004
|
-
// A leading `+` in An+B is a sign, not a combinator: `+ n` is no
|
|
5005
|
-
// argument an engine reads, and `+n` would be one.
|
|
5006
|
-
if (minify && NTH_PSEUDO_FUNCTIONS.has(lower)) {
|
|
5007
|
-
const at = parts.findIndex(
|
|
5008
|
-
(part) => part.length !== 0 && part !== _SEP
|
|
5009
|
-
);
|
|
5010
|
-
if (parts[at] === "+" && parts[at + 1] === _SEP) {
|
|
5011
|
-
inner = `+ ${inner.slice(1)}`;
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
|
-
// Chrome throws out `attr( name unit )` where `attr( name unit)`
|
|
5015
|
-
// parses, so the space before the `)` decides whether the declaration
|
|
5016
|
-
// runs — `_join` drops a trailing one like any other.
|
|
5017
|
-
if (
|
|
5018
|
-
minify &&
|
|
5019
|
-
parts[parts.length - 1] === _SEP &&
|
|
5020
|
-
asciiCaseInsensitiveMatch(name, "attr")
|
|
5021
|
-
) {
|
|
5022
|
-
inner += " ";
|
|
5023
|
-
}
|
|
5024
|
-
}
|
|
5025
|
-
if (!minify) return `${written}(${inner})`;
|
|
5026
|
-
// Function names match ASCII case-insensitively, so one lowercase name
|
|
5027
|
-
// is what every table below is keyed by; the eleven transforms spelled
|
|
5028
|
-
// with a capital are printed the way everything else writes them.
|
|
5029
|
-
const fn = asciiLowercaseName(name);
|
|
5030
|
-
// As with a unit: a name already lowercase is its own answer, and only a
|
|
5031
|
-
// shouted one is looked up. The lookup key still folds inside a
|
|
5032
|
-
// substituted value; only the printed spelling stays as written.
|
|
5033
|
-
let out =
|
|
5034
|
-
_S._inSubstitutedValue ||
|
|
5035
|
-
_S._inSupportsSelector ||
|
|
5036
|
-
!_S._transforms.foldCase ||
|
|
5037
|
-
fn.startsWith("--")
|
|
5038
|
-
? name
|
|
5039
|
-
: fn === name
|
|
5040
|
-
? fn
|
|
5041
|
-
: CANONICAL_NAMES.get(fn) || fn;
|
|
5042
|
-
if (written !== name) {
|
|
5043
|
-
out = _S._transforms.rewriteEscapes ? escapeName(out) : written;
|
|
5044
|
-
}
|
|
5045
|
-
// An+B in a selector: `2n+1` is what `odd` names, and shorter written so.
|
|
5046
|
-
if (!path.within(NodeType.Declaration) && NTH_PSEUDO_FUNCTIONS.has(fn)) {
|
|
5047
|
-
return _S._inSupportsSelector
|
|
5048
|
-
? `${out}(${inner})`
|
|
5049
|
-
: _minifyAnPlusB(out, inner);
|
|
5050
|
-
}
|
|
5051
|
-
// Every rewrite below is keyed by a name none of these carry.
|
|
5052
|
-
if (
|
|
5053
|
-
SUBSTITUTION_FUNCTIONS.has(fn) ||
|
|
5054
|
-
(!path.within(NodeType.Declaration) && SELECTOR_FUNCTIONS.has(fn))
|
|
5055
|
-
) {
|
|
5056
|
-
return `${out}(${inner})`;
|
|
5057
|
-
}
|
|
5058
|
-
// WHY: an `rgb()` or `rgba()` collapses to the shortest color and anything
|
|
5059
|
-
// else keeps its `name(args)` form. A color inside a `@supports` condition
|
|
5060
|
-
// is syntax under test — the engines reading `#0000` are not those reading
|
|
5061
|
-
// `rgba(0,0,0,0)` — so rewriting it asks another question, and esbuild,
|
|
5062
|
-
// clean-css and lightningcss leave the condition alone too. A color read as
|
|
5063
|
-
// numbers, in a gradient stop, a `color-mix()` argument or a relative
|
|
5064
|
-
// color's origin, stays as written unless converting it is exact, which the
|
|
5065
|
-
// legacy sRGB forms are where their channels are bytes.
|
|
5066
|
-
let color = _S._inSupportsPrelude
|
|
5067
|
-
? null
|
|
5068
|
-
: _minifyColorFunction(
|
|
5069
|
-
fn,
|
|
5070
|
-
inner,
|
|
5071
|
-
_S._hexAlphaAllowed,
|
|
5072
|
-
_S._inColorOperand
|
|
5073
|
-
) ||
|
|
5074
|
-
(_S._inColorOperand
|
|
5075
|
-
? null
|
|
5076
|
-
: _minifyPolarColorFunction(fn, inner, _S._hexAlphaAllowed));
|
|
5077
|
-
// A mix no byte holds is written back in the space it was computed in,
|
|
5078
|
-
// which can be longer than the call — then the call is the shorter one.
|
|
5079
|
-
if (color !== null && color.length > fn.length + inner.length + 2) {
|
|
5080
|
-
color = null;
|
|
5081
|
-
}
|
|
5082
|
-
if (
|
|
5083
|
-
color !== null &&
|
|
5084
|
-
(color !== "transparent" || !_inTapHighlightColor())
|
|
5085
|
-
) {
|
|
5086
|
-
return color === "transparent" && _S._hexAlphaAllowed ? "#0000" : color;
|
|
5087
|
-
}
|
|
5088
|
-
// `light-dark()` names a color per scheme, which the pair above says with
|
|
5089
|
-
// a substitution the scheme itself switches. Two arguments only: the
|
|
5090
|
-
// grammar takes exactly that many, and anything else is no such call.
|
|
5091
|
-
if (
|
|
5092
|
-
_S._loweringLightDark &&
|
|
5093
|
-
fn === "light-dark" &&
|
|
5094
|
-
path.within(NodeType.Declaration) &&
|
|
5095
|
-
!_S._inCustomProperty &&
|
|
5096
|
-
!_S._inSupportsPrelude &&
|
|
5097
|
-
!_S._inSubstitutedValue
|
|
5098
|
-
) {
|
|
5099
|
-
const halves = _splitTopLevelArguments(inner);
|
|
5100
|
-
// A half the target cannot read would substitute into a `var()` and make
|
|
5101
|
-
// the declaration invalid at computed-value time, where nothing before it
|
|
5102
|
-
// is read — so it is left alone rather than lowered.
|
|
5103
|
-
if (halves.length === 2 && !_namesUnreadableColor(inner)) {
|
|
5104
|
-
_S._lightDarkUsed = true;
|
|
5105
|
-
return `var(${LIGHT_PROPERTY},${halves[0].trim()})var(${DARK_PROPERTY},${halves[1].trim()})`;
|
|
5106
|
-
}
|
|
5107
|
-
}
|
|
5108
|
-
// A math function over constants of one unit is that value, written back as
|
|
5109
|
-
// a `calc()`: the parentheses stay, since dropping them turns a clamped
|
|
5110
|
-
// negative into a parse error, and only the declaration printer does.
|
|
5111
|
-
if (!_S._inSupportsPrelude && !_S._inCustomProperty) {
|
|
5112
|
-
const term = _foldMathFunction(fn, inner);
|
|
5113
|
-
if (term !== null) {
|
|
5114
|
-
// A term that means the same bare needs no parentheses anywhere, so
|
|
5115
|
-
// it is written as itself; the rest keep a `calc()` for the
|
|
5116
|
-
// declaration printer to judge against the property.
|
|
5117
|
-
const shape =
|
|
5118
|
-
_S._mathFunctionDepth > 1 || _inNumberArgument(path)
|
|
5119
|
-
? _NESTED_TERM_RE
|
|
5120
|
-
: _BARE_TERM_RE;
|
|
5121
|
-
const folded =
|
|
5122
|
-
(shape.test(term) ||
|
|
5123
|
-
(_S._mathFunctionDepth === 1 &&
|
|
5124
|
-
_NEGATIVE_TERM_RE.test(term) &&
|
|
5125
|
-
_inNegativeAcceptingArgument(path))) &&
|
|
5126
|
-
!_inCalcRejectingProperty() &&
|
|
5127
|
-
!(!/[a-z%]$/i.test(term) && _inNumberOnlyOutsideCalcProperty()) &&
|
|
5128
|
-
!_foldLosesClamp(term) &&
|
|
5129
|
-
!_foldBelowStepCount(term, path)
|
|
5130
|
-
? term
|
|
5131
|
-
: `calc(${term})`;
|
|
5132
|
-
// A division can land on a value needing every digit of a double, which
|
|
5133
|
-
// is longer than the expression producing it. Only the outermost call
|
|
5134
|
-
// is asked, a nested one being a term still to be reduced.
|
|
5135
|
-
if (
|
|
5136
|
-
_S._mathFunctionDepth > 1 ||
|
|
5137
|
-
folded.length < fn.length + inner.length + 2 ||
|
|
5138
|
-
_unwrapsShorter(path, folded, fn.length + inner.length + 2)
|
|
5139
|
-
) {
|
|
5140
|
-
return folded;
|
|
5141
|
-
}
|
|
5142
|
-
}
|
|
5143
|
-
const reduced = _reduceMathArguments(fn, inner);
|
|
5144
|
-
// A `calc()` inside another math function is the parentheses it
|
|
5145
|
-
// stands for (CSS Values 4 §10.1), whatever a substitution expands to.
|
|
5146
|
-
if (
|
|
5147
|
-
fn === "calc" &&
|
|
5148
|
-
_S._mathFunctionDepth > 1 &&
|
|
5149
|
-
path.within(NodeType.Declaration) &&
|
|
5150
|
-
_S._transforms.reduceFunctions
|
|
5151
|
-
) {
|
|
5152
|
-
return `(${reduced === null ? inner : reduced})`;
|
|
5153
|
-
}
|
|
5154
|
-
if (reduced !== null) return `${fn}(${reduced})`;
|
|
5155
|
-
}
|
|
5156
|
-
// An easing function is only ever a value; `url()` is also an at-rule
|
|
5157
|
-
// prelude's (`@import url("a.css")`).
|
|
5158
|
-
const easing =
|
|
5159
|
-
path.within(NodeType.Declaration) && !_S._inCustomProperty
|
|
5160
|
-
? _minifyEasingFunction(fn, inner)
|
|
5161
|
-
: null;
|
|
5162
|
-
if (easing !== null) return easing;
|
|
5163
|
-
// A translation's percentage is of the element's own box, which always
|
|
5164
|
-
// has a size, so a `0%` term beside one length adds nothing to it.
|
|
5165
|
-
if (
|
|
5166
|
-
_S._transforms.reduceFunctions &&
|
|
5167
|
-
path.within(NodeType.Declaration) &&
|
|
5168
|
-
!_S._inSubstitutedValue &&
|
|
5169
|
-
fn.startsWith("translate") &&
|
|
5170
|
-
inner.includes("0%")
|
|
5171
|
-
) {
|
|
5172
|
-
const args = _splitTopLevelArguments(inner);
|
|
5173
|
-
let changed = false;
|
|
5174
|
-
for (let i = 0; i < args.length; i++) {
|
|
5175
|
-
// A z slot takes no percentage, so a `0%` there makes the whole
|
|
5176
|
-
// declaration invalid, which dropping it would revive.
|
|
5177
|
-
if (fn === "translatez" || (fn === "translate3d" && i === 2)) {
|
|
5178
|
-
continue;
|
|
5179
|
-
}
|
|
5180
|
-
const term = _dropZeroPercentTerm(args[i].trim());
|
|
5181
|
-
if (term !== null) {
|
|
5182
|
-
args[i] = term;
|
|
5183
|
-
changed = true;
|
|
5184
|
-
}
|
|
5185
|
-
}
|
|
5186
|
-
// The rewrites below read the arguments as they now stand.
|
|
5187
|
-
if (changed) inner = args.join(",");
|
|
5188
|
-
}
|
|
5189
|
-
// `translateX(v)` is `translate(v)` and `skewX(a)` is `skew(a)`, the second
|
|
5190
|
-
// component defaulting to 0. Only for one plain component: a substitution
|
|
5191
|
-
// could expand to two, reviving a declaration the browser drops.
|
|
5192
|
-
const oneAxis = _S._transforms.reduceFunctions
|
|
5193
|
-
? X_AXIS_TRANSFORMS.get(fn)
|
|
5194
|
-
: undefined;
|
|
5195
|
-
if (
|
|
5196
|
-
oneAxis !== undefined &&
|
|
5197
|
-
path.within(NodeType.Declaration) &&
|
|
5198
|
-
!_S._inSubstitutedValue &&
|
|
5199
|
-
!inner.includes(",") &&
|
|
5200
|
-
!containsSubstitutionFunction(inner)
|
|
5201
|
-
) {
|
|
5202
|
-
return `${oneAxis}(${inner})`;
|
|
5203
|
-
}
|
|
5204
|
-
if (path.within(NodeType.Declaration) && !_S._inSubstitutedValue) {
|
|
5205
|
-
// An argument that is the amount an omitted one means says nothing,
|
|
5206
|
-
// whether or not the zero still carries the unit its grammar drops.
|
|
5207
|
-
const omitted = _S._transforms.reduceFunctions
|
|
5208
|
-
? FILTER_FUNCTION_OMITTED.get(fn)
|
|
5209
|
-
: undefined;
|
|
5210
|
-
if (omitted !== undefined) {
|
|
5211
|
-
const bare = _dropCallZeroUnit(fn, inner);
|
|
5212
|
-
if (bare === omitted || (omitted === "1" && bare === "100%")) {
|
|
5213
|
-
return `${out}()`;
|
|
5214
|
-
}
|
|
5215
|
-
}
|
|
5216
|
-
const reduced = _reduceTransformFunctionDeep(fn, inner);
|
|
5217
|
-
if (reduced !== null) return reduced;
|
|
5218
|
-
// The stops are folded over what dropping the direction leaves, a
|
|
5219
|
-
// gradient carrying both otherwise keeping whichever ran second.
|
|
5220
|
-
const shape = _dropDefaultShapeParts(fn, inner);
|
|
5221
|
-
if (shape !== null) return `${out}(${shape})`;
|
|
5222
|
-
let gradient = _reduceGradientDirection(fn, inner);
|
|
5223
|
-
if (gradient === null) {
|
|
5224
|
-
gradient = _dropDefaultGradientPosition(fn, inner);
|
|
5225
|
-
}
|
|
5226
|
-
let stops = _foldGradientStops(
|
|
5227
|
-
fn,
|
|
5228
|
-
gradient === null ? inner : gradient,
|
|
5229
|
-
_S._doublePositionAllowed
|
|
5230
|
-
);
|
|
5231
|
-
// A double position the target cannot read is written as the two
|
|
5232
|
-
// stops it names, over whatever the folds above left.
|
|
5233
|
-
if (!_S._doublePositionAllowed) {
|
|
5234
|
-
const split = !_S._transforms.lowerUnsupported
|
|
5235
|
-
? null
|
|
5236
|
-
: _splitDoublePositionStops(
|
|
5237
|
-
fn,
|
|
5238
|
-
stops !== null ? stops : gradient !== null ? gradient : inner
|
|
5239
|
-
);
|
|
5240
|
-
if (split !== null) stops = split;
|
|
5241
|
-
}
|
|
5242
|
-
if (stops !== null) return `${out}(${stops})`;
|
|
5243
|
-
if (gradient !== null) return `${out}(${gradient})`;
|
|
5244
|
-
}
|
|
5245
|
-
// A zero angle needs no unit where the grammar names `<zero>` beside
|
|
5246
|
-
// `<angle>` (CSS Transforms 2, Filter Effects 1).
|
|
5247
|
-
if (
|
|
5248
|
-
path.within(NodeType.Declaration) &&
|
|
5249
|
-
!_S._inSubstitutedValue &&
|
|
5250
|
-
_S._transforms.shortenNumbers &&
|
|
5251
|
-
ZERO_ANGLE_FUNCTIONS.has(fn)
|
|
5252
|
-
) {
|
|
5253
|
-
const bare = inner.replace(_ZERO_ANGLE_ARGUMENT_RE, "$10");
|
|
5254
|
-
if (bare !== inner) return `${out}(${bare})`;
|
|
5255
|
-
}
|
|
5256
|
-
// A name kept escaped keeps its call as written: unquoting it rests on the
|
|
5257
|
-
// tokenizer reading `url` through the escape.
|
|
5258
|
-
const url =
|
|
5259
|
-
_S._inSubstitutedValue || (written !== name && out === written)
|
|
5260
|
-
? null
|
|
5261
|
-
: _minifyUrlFunction(fn, inner);
|
|
5262
|
-
if (url !== null) return url;
|
|
5263
|
-
return `${out}(${inner})`;
|
|
5264
|
-
}
|
|
5265
|
-
case NodeType.Url: {
|
|
5266
|
-
const source = _printedSource(path.node);
|
|
5267
|
-
// Closed at EOF: write the `)` back so the url stops where it stopped for
|
|
5268
|
-
// the tokenizer rather than swallowing what the printer emits next. Before
|
|
5269
|
-
// the rest rather than instead of it, or a second pass is what folds it.
|
|
5270
|
-
const unterminated = _isUnterminatedUrl(source);
|
|
5271
|
-
const raw = unterminated ? _terminate(source, ")", "\uFFFD") : source;
|
|
5272
|
-
// The tokenizer already trimmed the url-token's content, so the padding in
|
|
5273
|
-
// `url( a.png )` carries nothing.
|
|
5274
|
-
if (!minify) return raw;
|
|
5275
|
-
const open = raw.indexOf("(");
|
|
5276
|
-
// `url` matches ASCII case-insensitively like any function name. Read in
|
|
5277
|
-
// place, since cutting the name out would allocate on every url and all but
|
|
5278
|
-
// a shouted one folds to itself. In a substituted value nothing is read.
|
|
5279
|
-
let head = null;
|
|
5280
|
-
if (!_S._inSubstitutedValue && _S._transforms.foldCase) {
|
|
5281
|
-
for (let i = 0; i < open; i++) {
|
|
5282
|
-
const c = raw.charCodeAt(i);
|
|
5283
|
-
if (c >= CC_UPPER_A && c <= CC_UPPER_Z) {
|
|
5284
|
-
head = asciiLowercaseName(raw.slice(0, open + 1));
|
|
5285
|
-
break;
|
|
5286
|
-
}
|
|
5287
|
-
}
|
|
5288
|
-
}
|
|
5289
|
-
const percent = raw.includes("%");
|
|
5290
|
-
/**
|
|
5291
|
-
* @returns {string} the url-token as it prints with nothing rendered
|
|
5292
|
-
*/
|
|
5293
|
-
const plainUrl = () => {
|
|
5294
|
-
// Padding only exists when the content is whitespace-bounded; checking
|
|
5295
|
-
// that costs two char reads, reading the content costs a slice.
|
|
5296
|
-
if (
|
|
5297
|
-
!percent &&
|
|
5298
|
-
!isAsciiWhitespace(raw.charCodeAt(open + 1)) &&
|
|
5299
|
-
!isAsciiWhitespace(raw.charCodeAt(raw.length - 2))
|
|
5300
|
-
) {
|
|
5301
|
-
return head === null ? raw : head + raw.slice(open + 1);
|
|
5302
|
-
}
|
|
5303
|
-
const name = head === null ? raw.slice(0, open + 1) : head;
|
|
5304
|
-
const value = _urlValue(path, unterminated);
|
|
5305
|
-
// A url-token already carrying an escape is one this printer did not
|
|
5306
|
-
// write, so its `\%` is not read as the start of a percent-escape.
|
|
5307
|
-
return `${name}${
|
|
5308
|
-
percent && !value.includes("\\")
|
|
5309
|
-
? _decodePercentEscapes(value, true, "")
|
|
5310
|
-
: value
|
|
5311
|
-
})`;
|
|
5312
|
-
};
|
|
5313
|
-
// Ahead of the padding fast path below: a base64 `data:` url is
|
|
5314
|
-
// whitespace-free and `%`-free, so it would take it and never be offered.
|
|
5315
|
-
if (_S._deferEmbeddedSource !== undefined) {
|
|
5316
|
-
const opener = head === null ? raw.slice(0, open + 1) : head;
|
|
5317
|
-
const deferred = _deferDataUrl(
|
|
5318
|
-
_urlValue(path, unterminated),
|
|
5319
|
-
(url) => `${opener}${_serializeUrl(url, '"')})`,
|
|
5320
|
-
plainUrl()
|
|
5321
|
-
);
|
|
5322
|
-
if (deferred !== null) return deferred;
|
|
5323
|
-
}
|
|
5324
|
-
if (_S._renderEmbeddedSource !== undefined) {
|
|
5325
|
-
const renderedUrl = _renderDataUrl(_urlValue(path, unterminated));
|
|
5326
|
-
if (renderedUrl !== null) {
|
|
5327
|
-
const name = head === null ? raw.slice(0, open + 1) : head;
|
|
5328
|
-
return `${name}${_serializeUrl(renderedUrl, '"')})`;
|
|
5329
|
-
}
|
|
5330
|
-
}
|
|
5331
|
-
return plainUrl();
|
|
5332
|
-
}
|
|
5333
|
-
case NodeType.String: {
|
|
5334
|
-
const source = _printedSource(path.node);
|
|
5335
|
-
// Closed at EOF: write the quote back, for the same reason as `url()`.
|
|
5336
|
-
// Written here rather than after the transforms below, or they read a
|
|
5337
|
-
// string with no closing quote and decline it, which a second pass undoes.
|
|
5338
|
-
const raw = _isClosedString(source)
|
|
5339
|
-
? source
|
|
5340
|
-
: _terminate(source, source[0], "");
|
|
5341
|
-
// A custom property hands its string back as written, quotes included.
|
|
5342
|
-
if (!minify || _S._inCustomProperty) return raw;
|
|
5343
|
-
// `<family-name>` is `<string> | <custom-ident>+`, so a family whose text
|
|
5344
|
-
// is a run of identifiers means the same unquoted, two bytes shorter.
|
|
5345
|
-
if (
|
|
5346
|
-
!_S._inSupportsPrelude &&
|
|
5347
|
-
!_S._inSubstitutedValue &&
|
|
5348
|
-
((_inFontFamily() && _isLoneFamilyName(path)) ||
|
|
5349
|
-
_isLoneFontFamily(path))
|
|
5350
|
-
) {
|
|
5351
|
-
const unquoted = _unquoteFontFamily(raw);
|
|
5352
|
-
if (unquoted !== null) return unquoted;
|
|
5353
|
-
}
|
|
5354
|
-
const written = _minifyString(raw);
|
|
5355
|
-
return (_S._charsetPiece === -1 && !_S._assumeUtf8) ||
|
|
5356
|
-
!_S._transforms.rewriteEscapes
|
|
5357
|
-
? written
|
|
5358
|
-
: _unescapeAstral(written);
|
|
5359
|
-
}
|
|
5360
|
-
case NodeType.Ident: {
|
|
5361
|
-
const raw = _printedSource(path.node);
|
|
5362
|
-
if (!minify) return raw;
|
|
5363
|
-
// A keyword is matched by what an escape spells, not by how it is written.
|
|
5364
|
-
const meaning = raw.includes("\\") ? unescapeIdentifier(raw) : raw;
|
|
5365
|
-
// `transparent` is `#0000` where the target reads a hex alpha — the same
|
|
5366
|
-
// color, six bytes shorter. The tap-highlight guard still holds: that
|
|
5367
|
-
// WebKit bug is about the keyword's *value*, whichever way it is spelled.
|
|
5368
|
-
if (
|
|
5369
|
-
path.within(NodeType.Declaration) &&
|
|
5370
|
-
!_S._inSubstitutedValue &&
|
|
5371
|
-
_S._hexAlphaAllowed &&
|
|
5372
|
-
_S._transforms.shortenColors &&
|
|
5373
|
-
asciiCaseInsensitiveMatch(meaning, "transparent") &&
|
|
5374
|
-
!_inTapHighlightColor()
|
|
5375
|
-
) {
|
|
5376
|
-
return "#0000";
|
|
5377
|
-
}
|
|
5378
|
-
// The keyword names each platform's own UI font, which a target that
|
|
5379
|
-
// does not read it reaches only through the platform's own name for it.
|
|
5380
|
-
if (
|
|
5381
|
-
!_S._systemUiAllowed &&
|
|
5382
|
-
_S._transforms.lowerUnsupported &&
|
|
5383
|
-
path.within(NodeType.Declaration) &&
|
|
5384
|
-
!_S._inCustomProperty &&
|
|
5385
|
-
!_S._inSubstitutedValue &&
|
|
5386
|
-
asciiCaseInsensitiveMatch(meaning, "system-ui") &&
|
|
5387
|
-
_inFontFamily()
|
|
5388
|
-
) {
|
|
5389
|
-
return SYSTEM_UI_STACK;
|
|
5390
|
-
}
|
|
5391
|
-
if (
|
|
5392
|
-
path.within(NodeType.Declaration) &&
|
|
5393
|
-
!_S._inCustomProperty &&
|
|
5394
|
-
!_S._inSupportsPrelude &&
|
|
5395
|
-
!_S._inSubstitutedValue
|
|
5396
|
-
) {
|
|
5397
|
-
// One fold for both questions below, handing back the string where nothing
|
|
5398
|
-
// changes. ASCII only: `pin\212A` ends in a Kelvin sign, not a `k`.
|
|
5399
|
-
const lowered = asciiLowercaseName(meaning);
|
|
5400
|
-
// A named color where the property takes nothing else an identifier could
|
|
5401
|
-
// be: `white` is `#fff`, both computing to `rgb(255, 255, 255)`.
|
|
5402
|
-
// Elsewhere an identifier may be the author's own name.
|
|
5403
|
-
if (_S._transforms.shortenColors) {
|
|
5404
|
-
const shorter = getColorNameToShortest().get(lowered);
|
|
5405
|
-
if (
|
|
5406
|
-
shorter !== undefined &&
|
|
5407
|
-
(_inColorOnlyProperty() ||
|
|
5408
|
-
_inGradientArgument(path) ||
|
|
5409
|
-
_atBackgroundTop(path))
|
|
5410
|
-
) {
|
|
5411
|
-
return shorter;
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
// A keyword-only or color property names nothing of the author's, so an
|
|
5415
|
-
// ident directly in its value (a call's arguments are their own grammar's)
|
|
5416
|
-
// is a keyword matched ASCII case-insensitively, but a dashed ident (§4.2).
|
|
5417
|
-
if (
|
|
5418
|
-
_S._transforms.foldCase &&
|
|
5419
|
-
lowered !== raw &&
|
|
5420
|
-
path.parent === _S._valueDeclaration &&
|
|
5421
|
-
!meaning.startsWith("--") &&
|
|
5422
|
-
_inKeywordOnlyValue()
|
|
5423
|
-
) {
|
|
5424
|
-
const folded =
|
|
5425
|
-
meaning !== raw && _S._transforms.rewriteEscapes
|
|
5426
|
-
? escapeIdentifier(lowered)
|
|
5427
|
-
: asciiLowercaseName(raw);
|
|
5428
|
-
if (folded !== raw) return folded;
|
|
5429
|
-
}
|
|
5430
|
-
}
|
|
5431
|
-
return _S._transforms.rewriteEscapes ? _minifyIdentEscapes(raw) : raw;
|
|
5432
|
-
}
|
|
5433
|
-
case NodeType.SimpleBlock: {
|
|
5434
|
-
const open = _blockTokenOf(path.node);
|
|
5435
|
-
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
5436
|
-
// Outside a declaration value a `[…]` is an attribute selector and a `(…)`
|
|
5437
|
-
// a query condition. Inside one they are a grid line-name list and a
|
|
5438
|
-
// `calc()` sub-expression, where neither of those rewrites holds.
|
|
5439
|
-
const structural = minify && !path.within(NodeType.Declaration);
|
|
5440
|
-
/** @type {string[]} */
|
|
5441
|
-
let parts;
|
|
5442
|
-
if (structural && open === "[") {
|
|
5443
|
-
parts = _printAttributeSelector(path, path.children(), writer);
|
|
5444
|
-
} else {
|
|
5445
|
-
// One array, rather than a child list and a mapped copy of it.
|
|
5446
|
-
parts = [];
|
|
5447
|
-
_appendChildTexts(path.node, writer, parts);
|
|
5448
|
-
}
|
|
5449
|
-
if (structural && open === "(" && _S._inMediaConditionPrelude) {
|
|
5450
|
-
if (_S._transforms.foldCase) _lowercaseConditionParts(parts);
|
|
5451
|
-
if (_S._rangeSpellingAllowed) _useRangeSpelling(parts);
|
|
5452
|
-
}
|
|
5453
|
-
if (
|
|
5454
|
-
minify &&
|
|
5455
|
-
structural &&
|
|
5456
|
-
open === "(" &&
|
|
5457
|
-
(_S._inSupportsPrelude || _S._inMediaConditionPrelude)
|
|
5458
|
-
) {
|
|
5459
|
-
_keepKeywordComments(parts);
|
|
5460
|
-
}
|
|
5461
|
-
let trim =
|
|
5462
|
-
minify && _S._mathFunctionDepth !== 0 ? _TRIM_MATH : _TRIM_NOTHING;
|
|
5463
|
-
if (structural && open === "(") {
|
|
5464
|
-
// `@scope`'s parentheses hold selector lists, so a `:` there starts a
|
|
5465
|
-
// pseudo-class and the whitespace before it is a descendant combinator
|
|
5466
|
-
// — `@scope (div :hover)` is not `@scope (div:hover)`.
|
|
5467
|
-
trim = _inScopePrelude(path) ? _TRIM_COMBINATORS : _TRIM_CONDITIONS;
|
|
5468
|
-
if (trim === _TRIM_COMBINATORS) _foldPseudoNames(parts);
|
|
5469
|
-
}
|
|
5470
|
-
const inner = _join(parts, !minify, trim);
|
|
5471
|
-
return `${open}${inner}${close}`;
|
|
5472
|
-
}
|
|
5473
|
-
case NodeType.Declaration: {
|
|
5474
|
-
const name = _rawName(path.node);
|
|
5475
|
-
// WHY: a property is matched by what its name means, not by how it was
|
|
5476
|
-
// spelled — §4.3.7 lets any of its letters be written as an escape — so
|
|
5477
|
-
// every lookup below reads the unescaped name while the printed one
|
|
5478
|
-
// keeps the source's. It costs an `indexOf` where nothing is escaped.
|
|
5479
|
-
const meaning = path.name();
|
|
5480
|
-
// Counted rather than listed: a declaration's value is one component 92
|
|
5481
|
-
// times in 100, and the list would be built only to be indexed twice.
|
|
5482
|
-
const count = path.childCount();
|
|
5483
|
-
// `@property`'s `initial-value` is read against the sibling `syntax`
|
|
5484
|
-
// descriptor, so it is as opaque as a custom property's own value.
|
|
5485
|
-
const custom =
|
|
5486
|
-
meaning.startsWith("--") ||
|
|
5487
|
-
(_S._inPropertyRule &&
|
|
5488
|
-
asciiCaseInsensitiveMatch(meaning, "initial-value")) ||
|
|
5489
|
-
// `@function`'s `result` is the token stream the call substitutes, so
|
|
5490
|
-
// it is opaque the same way — and empty when the function returns the
|
|
5491
|
-
// guaranteed-invalid value.
|
|
5492
|
-
(_S._inFunctionRule && asciiCaseInsensitiveMatch(meaning, "result")) ||
|
|
5493
|
-
// A `{}` block standing as the whole value is a token stream the
|
|
5494
|
-
// engine holds as written too — no grammar reads it, so a rewrite of
|
|
5495
|
-
// it builds a different CSSOM from the same document.
|
|
5496
|
-
(count === 1 &&
|
|
5497
|
-
path.type(path.child(0)) === NodeType.SimpleBlock &&
|
|
5498
|
-
_blockTokenOf(path.child(0)) === "{");
|
|
5499
|
-
// A value-less declaration is invalid, so it is already ignored — except
|
|
5500
|
-
// on a custom property, where the empty value is the guaranteed-invalid
|
|
5501
|
-
// one a `var()` fallback reads.
|
|
5502
|
-
if (minify && _S._transforms.removeDeadRules && count === 0 && !custom) {
|
|
5503
|
-
return "";
|
|
5504
|
-
}
|
|
5505
|
-
// The name folded to lowercase, where the value below needed it; `name`
|
|
5506
|
-
// itself where it did not, which is what the printed name reads as
|
|
5507
|
-
// "nothing to fold".
|
|
5508
|
-
let lowered = name;
|
|
5509
|
-
let value = "";
|
|
5510
|
-
if (count !== 0) {
|
|
5511
|
-
// Property names match ASCII case-insensitively and a custom property's
|
|
5512
|
-
// does not, so skipping it skips the lowercasing. One fold serves both
|
|
5513
|
-
// which value rules apply and how the name prints.
|
|
5514
|
-
lowered = custom ? meaning : toLowerCaseIfNeeded(meaning);
|
|
5515
|
-
const property = custom ? meaning : _standardSpelling(lowered);
|
|
5516
|
-
if (custom) {
|
|
5517
|
-
// `getPropertyValue()` hands this text back, so a rewritten token
|
|
5518
|
-
// would be a different CSSOM; only the boundaries between them go.
|
|
5519
|
-
const from = path.start(path.child(0));
|
|
5520
|
-
const to = path.end(path.child(count - 1));
|
|
5521
|
-
if (minify) {
|
|
5522
|
-
value = _customPropertyValue(path, path.node, writer, from, to);
|
|
5523
|
-
} else {
|
|
5524
|
-
// Straight from source, so the kept comments in it are already
|
|
5525
|
-
// there — claim them, or the writer emits them a second time
|
|
5526
|
-
// ahead of the next top-level node.
|
|
5527
|
-
writer.takeInserts(from, to);
|
|
5528
|
-
value = _S._input.slice(from, to);
|
|
5529
|
-
if (to === _S._input.length) value = _closeAtEof(value);
|
|
5530
|
-
}
|
|
5531
|
-
// Closed as printed, since a `}` may still follow; the grammar leaves it
|
|
5532
|
-
// open where nothing does, as the engine keeps it.
|
|
5533
|
-
const last = _S._eofPrinted;
|
|
5534
|
-
if (
|
|
5535
|
-
to === _S._input.length &&
|
|
5536
|
-
last !== "" &&
|
|
5537
|
-
value.endsWith(last) &&
|
|
5538
|
-
last !== _S._eofOpen
|
|
5539
|
-
) {
|
|
5540
|
-
_S._openCustomValue = {
|
|
5541
|
-
printed: value,
|
|
5542
|
-
source: value.slice(0, value.length - last.length) + _S._eofOpen
|
|
5543
|
-
};
|
|
5544
|
-
}
|
|
5545
|
-
} else if (property === "unicode-range") {
|
|
5546
|
-
// `U+…` tokenizes as numbers, so the generic numeric normalization
|
|
5547
|
-
// would corrupt it; each range is shortened as the urange it is.
|
|
5548
|
-
let raw = "";
|
|
5549
|
-
for (let at = 0; at < count; at++) {
|
|
5550
|
-
raw += _printedSource(path.child(at));
|
|
5551
|
-
}
|
|
5552
|
-
value = minify
|
|
5553
|
-
? raw
|
|
5554
|
-
.split(",")
|
|
5555
|
-
.map((one) => _minifyUnicodeRange(one.trim()))
|
|
5556
|
-
.join(",")
|
|
5557
|
-
: raw;
|
|
5558
|
-
} else {
|
|
5559
|
-
// Value hashes were already shortened by the hash printer, printing in a
|
|
5560
|
-
// value context, so this joins the children's text. A shorthand whose
|
|
5561
|
-
// value repeats what the notation implies collapses.
|
|
5562
|
-
const shorthand = minify
|
|
5563
|
-
? _collapseShorthand(path, property, path.node, writer)
|
|
5564
|
-
: null;
|
|
5565
|
-
if (shorthand === null && count === 1) {
|
|
5566
|
-
// What a value is 98 times in 100, and `_join` hands a lone fragment
|
|
5567
|
-
// back, so it is rewritten without an array. §5.4.6 step 7 pops a
|
|
5568
|
-
// trailing whitespace, so that child is never the `""` case.
|
|
5569
|
-
value = _valueFragment(writer.get(path.child(0)), property, minify);
|
|
5570
|
-
} else {
|
|
5571
|
-
/** @type {string[]} */
|
|
5572
|
-
let fragments;
|
|
5573
|
-
if (shorthand === null) {
|
|
5574
|
-
fragments = [];
|
|
5575
|
-
_appendChildTexts(path.node, writer, fragments);
|
|
5576
|
-
} else {
|
|
5577
|
-
fragments = shorthand;
|
|
5578
|
-
}
|
|
5579
|
-
// In place: a `map` per transform would allocate an array each.
|
|
5580
|
-
for (let i = 0; i < fragments.length; i++) {
|
|
5581
|
-
fragments[i] = _valueFragment(fragments[i], property, minify);
|
|
5582
|
-
}
|
|
5583
|
-
// The string transforms below read space-separated components, so
|
|
5584
|
-
// only a value none of them covers loses the separators its tokens
|
|
5585
|
-
// do not need — a substitution's tokens included.
|
|
5586
|
-
const separatorsOnly =
|
|
5587
|
-
minify &&
|
|
5588
|
-
!AUTO_SECOND_VALUE_PROPERTIES.has(property) &&
|
|
5589
|
-
!ALPHA_VALUE_PROPERTIES.has(property) &&
|
|
5590
|
-
!RATIO_PROPERTIES.has(property);
|
|
5591
|
-
value = _join(
|
|
5592
|
-
fragments,
|
|
5593
|
-
!minify,
|
|
5594
|
-
separatorsOnly ? _TRIM_SEPARATORS : _TRIM_NOTHING
|
|
5595
|
-
);
|
|
5596
|
-
}
|
|
5597
|
-
if (minify) {
|
|
5598
|
-
value = _dropDefaultSecondValue(property, value);
|
|
5599
|
-
value = _numberAlphaValue(property, value);
|
|
5600
|
-
value = _dropRatioDenominator(property, value);
|
|
5601
|
-
}
|
|
5602
|
-
}
|
|
5603
|
-
}
|
|
5604
|
-
const important = _isImportant(path.node) ? `${soft}!important` : "";
|
|
5605
|
-
// Property names match ASCII case-insensitively; a custom property's
|
|
5606
|
-
// does not, which is what `custom` already stands for — nor does an
|
|
5607
|
-
// `@font-feature-values` sub-rule's, which names a feature value.
|
|
5608
|
-
const folds =
|
|
5609
|
-
minify &&
|
|
5610
|
-
_S._transforms.foldCase &&
|
|
5611
|
-
!custom &&
|
|
5612
|
-
!_S._inFeatureValuesRule;
|
|
5613
|
-
let printedName =
|
|
5614
|
-
folds && lowered !== name ? asciiLowercaseName(name) : name;
|
|
5615
|
-
// An escape spells the same name, so the name is written the shortest way.
|
|
5616
|
-
if (minify && _S._transforms.rewriteEscapes && meaning !== name) {
|
|
5617
|
-
const spelled = escapeName(
|
|
5618
|
-
folds ? asciiLowercaseName(meaning) : meaning
|
|
5619
|
-
);
|
|
5620
|
-
if (spelled.length <= printedName.length) printedName = spelled;
|
|
5621
|
-
}
|
|
5622
|
-
return `${printedName}:${soft}${value}${important};`;
|
|
5623
|
-
}
|
|
6007
|
+
case NodeType.Function:
|
|
6008
|
+
return _printFunctionNode(path, writer, minify);
|
|
6009
|
+
case NodeType.Url:
|
|
6010
|
+
return _printUrlNode(path, minify);
|
|
6011
|
+
case NodeType.String:
|
|
6012
|
+
return _printStringNode(path, minify);
|
|
6013
|
+
case NodeType.Ident:
|
|
6014
|
+
return _printIdentNode(path, minify);
|
|
6015
|
+
case NodeType.SimpleBlock:
|
|
6016
|
+
return _printSimpleBlockNode(path, writer, minify);
|
|
6017
|
+
case NodeType.Declaration:
|
|
6018
|
+
return _printDeclarationNode(path, writer, minify, soft);
|
|
5624
6019
|
case NodeType.AtRule:
|
|
5625
|
-
case NodeType.QualifiedRule:
|
|
5626
|
-
|
|
5627
|
-
const preludeParts = _S._prefixingOn
|
|
5628
|
-
? /** @type {string[]} */ ([])
|
|
5629
|
-
: undefined;
|
|
5630
|
-
const prelude = _rulePrelude(path, writer, minify, preludeParts);
|
|
5631
|
-
const decls = _declarationsOf(path.node);
|
|
5632
|
-
// A qualified rule always has a block; an at-rule has one only when its
|
|
5633
|
-
// declaration list is non-null — else it is `@…;`.
|
|
5634
|
-
if (decls === null) {
|
|
5635
|
-
if (minify) _blockSpans.push(_NO_BLOCK_ENTRY);
|
|
5636
|
-
// `@charset` names the encoding the *source* was read in, and what is
|
|
5637
|
-
// written out is UTF-8 whatever it said — so carrying it over is at
|
|
5638
|
-
// best 22 dead bytes and at worst an encoding the output is not in.
|
|
5639
|
-
if (
|
|
5640
|
-
minify &&
|
|
5641
|
-
_S._transforms.removeDeadRules &&
|
|
5642
|
-
asciiCaseInsensitiveMatch(_rawName(path.node), "charset")
|
|
5643
|
-
) {
|
|
5644
|
-
return "";
|
|
5645
|
-
}
|
|
5646
|
-
// The rule only goes where no target reads one: a condition this has
|
|
5647
|
-
// not come to yet still needs it, and so does an engine that reads it.
|
|
5648
|
-
if (
|
|
5649
|
-
minify &&
|
|
5650
|
-
_S._transforms.customMedia &&
|
|
5651
|
-
_atKeywordOf(path.node) === "custom-media" &&
|
|
5652
|
-
_takeCustomMedia(prelude) &&
|
|
5653
|
-
!_S._customMediaAllowed
|
|
5654
|
-
) {
|
|
5655
|
-
return "";
|
|
5656
|
-
}
|
|
5657
|
-
// No engine reads `@custom-selector`, so the rule always goes once its
|
|
5658
|
-
// name is down — but only where `:is()` can carry what it stands for.
|
|
5659
|
-
if (
|
|
5660
|
-
minify &&
|
|
5661
|
-
_S._transforms.customSelectors &&
|
|
5662
|
-
_S._isSelectorAllowed &&
|
|
5663
|
-
_atKeywordOf(path.node) === "custom-selector" &&
|
|
5664
|
-
_takeCustomSelector(prelude)
|
|
5665
|
-
) {
|
|
5666
|
-
return "";
|
|
5667
|
-
}
|
|
5668
|
-
return `${prelude};`;
|
|
5669
|
-
}
|
|
5670
|
-
// Read before the body: composing it is what leaves the block empty, and
|
|
5671
|
-
// the drop below asks the same question again.
|
|
5672
|
-
const unusedRule = _takeUnusedRule(_S._currentNode);
|
|
5673
|
-
const rules = _rulesOf(path.node);
|
|
5674
|
-
// `nl` prefixes each item: nothing minifying, a line break beautifying
|
|
5675
|
-
// (declarations end in `;`, rules don't).
|
|
5676
|
-
const nl = minify ? "" : "\n";
|
|
5677
|
-
const memoized = _memoizedBlockBody(_S._currentNode);
|
|
5678
|
-
const {
|
|
5679
|
-
body,
|
|
5680
|
-
items,
|
|
5681
|
-
texts,
|
|
5682
|
-
superseded,
|
|
5683
|
-
droppedPrefix,
|
|
5684
|
-
addedPrefix,
|
|
5685
|
-
deadPrefixed,
|
|
5686
|
-
spans
|
|
5687
|
-
} =
|
|
5688
|
-
memoized === undefined
|
|
5689
|
-
? _composeBlockBody(
|
|
5690
|
-
path,
|
|
5691
|
-
decls,
|
|
5692
|
-
rules,
|
|
5693
|
-
writer,
|
|
5694
|
-
minify,
|
|
5695
|
-
nl,
|
|
5696
|
-
_S._currentNode,
|
|
5697
|
-
unusedRule
|
|
5698
|
-
)
|
|
5699
|
-
: // A rule its selector made unused writes nothing, whatever its block.
|
|
5700
|
-
_memoizedComposition(unusedRule ? "" : memoized);
|
|
5701
|
-
_memoizeBlockBody(
|
|
5702
|
-
_S._currentNode,
|
|
5703
|
-
memoized === undefined && !unusedRule ? body : null
|
|
5704
|
-
);
|
|
5705
|
-
if (
|
|
5706
|
-
minify &&
|
|
5707
|
-
// An empty rule paints nothing, so dropping it leaves the cascade as it
|
|
5708
|
-
// was — but only where the block means nothing (`_streamClose` agrees).
|
|
5709
|
-
_S._transforms.removeDeadRules &&
|
|
5710
|
-
body.length === 0 &&
|
|
5711
|
-
// See `_streamClose`: a rule taken out while a `@namespace` after it
|
|
5712
|
-
// could still be read would move one up into a live position.
|
|
5713
|
-
!(path.parent === null && _S._namespacePrologueOpen) &&
|
|
5714
|
-
(path.type() === NodeType.QualifiedRule ||
|
|
5715
|
-
DROPPABLE_WHEN_EMPTY_AT_RULES.has(_atKeywordOf(path.node)) ||
|
|
5716
|
-
unusedRule)
|
|
5717
|
-
) {
|
|
5718
|
-
// The rules written in it are not empty for its being so, and what the
|
|
5719
|
-
// hoist writes for them is rules a later copy can take back.
|
|
5720
|
-
_blockSpans.push(
|
|
5721
|
-
path.type() === NodeType.QualifiedRule
|
|
5722
|
-
? _NO_BLOCK_ENTRY_QUALIFIED
|
|
5723
|
-
: _NO_BLOCK_ENTRY
|
|
5724
|
-
);
|
|
5725
|
-
{
|
|
5726
|
-
const written =
|
|
5727
|
-
_writeNested(path, _S._currentNode, prelude, null, true) || "";
|
|
5728
|
-
if (minify && written.length !== 0) {
|
|
5729
|
-
_recordHoistedRule(written, null, 0);
|
|
5730
|
-
}
|
|
5731
|
-
return written;
|
|
5732
|
-
}
|
|
5733
|
-
}
|
|
5734
|
-
// A rule that says which color scheme it is in says it again as the pair
|
|
5735
|
-
// a lowered `light-dark()` reads, and — where it hands the choice to the
|
|
5736
|
-
// user's own preference — once more under the query that asks for it.
|
|
5737
|
-
let scheme = body;
|
|
5738
|
-
let schemeCopy = "";
|
|
5739
|
-
if (
|
|
5740
|
-
_S._loweringLightDark &&
|
|
5741
|
-
_S._sourceNamesLightDark &&
|
|
5742
|
-
path.type() === NodeType.QualifiedRule &&
|
|
5743
|
-
body.includes("color-scheme:") &&
|
|
5744
|
-
// The pair as this writes it, not either name on its own: a
|
|
5745
|
-
// block carrying that is one a lowering pass wrote. An authored
|
|
5746
|
-
// `--webpack-light` is a declaration of the author's.
|
|
5747
|
-
!body.includes(LIGHT_DARK_WRITTEN) &&
|
|
5748
|
-
!body.includes(LIGHT_DARK_WRITTEN_DARK)
|
|
5749
|
-
) {
|
|
5750
|
-
const written = _COLOR_SCHEME_RE.exec(body);
|
|
5751
|
-
const toggle =
|
|
5752
|
-
written === null
|
|
5753
|
-
? null
|
|
5754
|
-
: _lightDarkToggle(written[1].replace(_IMPORTANT, "").trim());
|
|
5755
|
-
if (toggle !== null) {
|
|
5756
|
-
// The body's own closing `;` went with the minified print, and the
|
|
5757
|
-
// toggle's last is the one this block now ends on.
|
|
5758
|
-
scheme = `${body};${toggle.toggle.slice(0, -1)}`;
|
|
5759
|
-
if (toggle.flip) {
|
|
5760
|
-
schemeCopy = `${_PREFERS_DARK}${soft}{${_OWN_PRELUDE}${soft}{${LIGHT_PROPERTY}:;${DARK_PROPERTY}:initial}}`;
|
|
5761
|
-
}
|
|
5762
|
-
}
|
|
5763
|
-
}
|
|
5764
|
-
// The hoist's copy names the prelude as the run settles it, since one
|
|
5765
|
-
// taken out is resolved against its parent and may be joined into a list.
|
|
5766
|
-
const ownRest = `${soft}{${scheme}${nl}}${schemeCopy}`;
|
|
5767
|
-
const rest =
|
|
5768
|
-
schemeCopy === ""
|
|
5769
|
-
? ownRest
|
|
5770
|
-
: ownRest.replace(_OWN_PRELUDE, () => prelude);
|
|
5771
|
-
const text = `${prelude}${rest}`;
|
|
5772
|
-
// Prefix copies turn one rule into several, so a rewritten rule is not
|
|
5773
|
-
// offered for joining: its text no longer describes a single block. Each
|
|
5774
|
-
// block's rules are their own siblings, pairing within their scope.
|
|
5775
|
-
let out = text;
|
|
5776
|
-
if (_S._prefixingOn) {
|
|
5777
|
-
// Read and clear here, so the lookahead is the one rule the writer
|
|
5778
|
-
// holds — which only a top-level rule ever is — and never a rule
|
|
5779
|
-
// further back.
|
|
5780
|
-
const parent = path.parent;
|
|
5781
|
-
const top = parent === null;
|
|
5782
|
-
const scope = _prefixScope(parent);
|
|
5783
|
-
out =
|
|
5784
|
-
path.type() === NodeType.AtRule
|
|
5785
|
-
? _prefixAtRule(path, text, prelude, scope, top)
|
|
5786
|
-
: _prefixQualifiedRule(
|
|
5787
|
-
text,
|
|
5788
|
-
/** @type {string[]} */ (preludeParts),
|
|
5789
|
-
soft,
|
|
5790
|
-
body,
|
|
5791
|
-
scope,
|
|
5792
|
-
top
|
|
5793
|
-
);
|
|
5794
|
-
}
|
|
5795
|
-
// WHY: pushed whatever it holds, since the parent takes one off per child
|
|
5796
|
-
// with a body, so a block carrying no rule still has to be there. A
|
|
5797
|
-
// rewritten rule records no nesting — those spans name `text` rather than
|
|
5798
|
-
// `out` — but is still the one rule its text says it is. Only a block that
|
|
5799
|
-
// recorded something needs an entry of its own, a leaf rule taking the
|
|
5800
|
-
// shared one, and nothing is pushed while not minifying.
|
|
5801
|
-
if (minify) {
|
|
5802
|
-
_blockSpans.push(
|
|
5803
|
-
spans === null || out !== text
|
|
5804
|
-
? path.type() === NodeType.QualifiedRule
|
|
5805
|
-
? _NO_BLOCK_ENTRY_QUALIFIED
|
|
5806
|
-
: _NO_BLOCK_ENTRY
|
|
5807
|
-
: {
|
|
5808
|
-
bodyAt: prelude.length + soft.length + 1,
|
|
5809
|
-
prelude,
|
|
5810
|
-
keyPrelude: _openerKey(`${prelude}{`),
|
|
5811
|
-
qualified: path.type() === NodeType.QualifiedRule,
|
|
5812
|
-
spans
|
|
5813
|
-
}
|
|
5814
|
-
);
|
|
5815
|
-
}
|
|
5816
|
-
// A rewritten rule is several rules with preludes of their own, and each
|
|
5817
|
-
// carries this rule's block — so the hoist takes them out. Only where
|
|
5818
|
-
// it holds no rule of its own, those naming one prelude of the several.
|
|
5819
|
-
const copies =
|
|
5820
|
-
out === text ||
|
|
5821
|
-
_prefixCopyPreludes.length === 0 ||
|
|
5822
|
-
_holdsNested(_S._currentNode)
|
|
5823
|
-
? undefined
|
|
5824
|
-
: _prefixCopyPreludes;
|
|
5825
|
-
const nested = _writeNested(
|
|
5826
|
-
path,
|
|
5827
|
-
_S._currentNode,
|
|
5828
|
-
prelude,
|
|
5829
|
-
ownRest,
|
|
5830
|
-
out === text || copies !== undefined,
|
|
5831
|
-
copies,
|
|
5832
|
-
`${soft}{${body}}`
|
|
5833
|
-
);
|
|
5834
|
-
if (nested === null) return "";
|
|
5835
|
-
if (out !== text) return out + nested;
|
|
5836
|
-
// The run wrote this rule's selectors into itself, so what it returns is
|
|
5837
|
-
// the whole of both — `text` being this rule's own `prelude` and `rest`.
|
|
5838
|
-
if (nested.length !== 0) {
|
|
5839
|
-
if (_nestedTookParent) {
|
|
5840
|
-
if (minify) _recordHoistedRule(nested, null, 0);
|
|
5841
|
-
return nested;
|
|
5842
|
-
}
|
|
5843
|
-
if (minify) _recordHoistedRule(nested, text, prelude.length);
|
|
5844
|
-
return text + nested;
|
|
5845
|
-
}
|
|
5846
|
-
// Where the block is what a sibling is compared against, remember the rule
|
|
5847
|
-
// so the parent parts it without re-scanning for the `{`. An at-rule
|
|
5848
|
-
// remembers its block's entries too, so a sibling's rules can join them.
|
|
5849
|
-
if (
|
|
5850
|
-
minify &&
|
|
5851
|
-
path.type() === NodeType.AtRule &&
|
|
5852
|
-
MERGEABLE_AT_RULES.has(_atKeywordOf(path.node))
|
|
5853
|
-
) {
|
|
5854
|
-
/** @type {RuleEntry[]} */
|
|
5855
|
-
const children = [];
|
|
5856
|
-
for (let i = 0; i < texts.length; i++) {
|
|
5857
|
-
if (texts[i].length === 0) continue;
|
|
5858
|
-
if (superseded !== null && superseded.has(i)) continue;
|
|
5859
|
-
// The entries have to spell the block this rule printed, prefixes and
|
|
5860
|
-
// all: a join rebuilds the body from them, and one built from the
|
|
5861
|
-
// unprefixed texts would restore what was dropped.
|
|
5862
|
-
if (droppedPrefix !== null && droppedPrefix.has(i)) continue;
|
|
5863
|
-
if (deadPrefixed !== null && deadPrefixed.has(items[i])) continue;
|
|
5864
|
-
const added = addedPrefix === null ? undefined : addedPrefix.get(i);
|
|
5865
|
-
children.push(
|
|
5866
|
-
added === undefined
|
|
5867
|
-
? _ruleEntryOf(items[i], texts[i])
|
|
5868
|
-
: _opaqueEntry(added + texts[i])
|
|
5869
|
-
);
|
|
5870
|
-
}
|
|
5871
|
-
let head = "";
|
|
5872
|
-
for (let i = 0; i < children.length - 1; i++) head += children[i].text;
|
|
5873
|
-
_setRuleEntry(_S._currentNode, {
|
|
5874
|
-
text,
|
|
5875
|
-
prelude: prelude.length,
|
|
5876
|
-
atRule: true,
|
|
5877
|
-
plain: false,
|
|
5878
|
-
listable: LIST_NO,
|
|
5879
|
-
listKind: LIST_KIND_SELECTOR,
|
|
5880
|
-
children,
|
|
5881
|
-
head,
|
|
5882
|
-
// The block's own merges have run and settled what they grew, so the
|
|
5883
|
-
// text above and the children agree; a list one of them still carries
|
|
5884
|
-
// concatenated is one this block says is there to settle.
|
|
5885
|
-
unordered: _anyUnordered(children),
|
|
5886
|
-
list: null,
|
|
5887
|
-
block: null,
|
|
5888
|
-
declarations: null
|
|
5889
|
-
});
|
|
5890
|
-
}
|
|
5891
|
-
if (minify && path.type() === NodeType.QualifiedRule) {
|
|
5892
|
-
// Only a block of declarations lends its selectors to another's list: a
|
|
5893
|
-
// nested rule's `&` stands for the whole list and `:is(…)` takes its most
|
|
5894
|
-
// specific selector, so joining preludes moves what the nested beat.
|
|
5895
|
-
const plain = rules === null || rules.length === 0;
|
|
5896
|
-
const parent = path.parent;
|
|
5897
|
-
// Which shape a join would read the prelude with — the parent is gone by
|
|
5898
|
-
// then. A keyframe selector is a list of its own shape, which
|
|
5899
|
-
// `_rulePrelude` has already rewritten `from` in.
|
|
5900
|
-
let listKind = LIST_KIND_SELECTOR;
|
|
5901
|
-
if (parent !== null) {
|
|
5902
|
-
const parentType = path.type(parent);
|
|
5903
|
-
if (parentType === NodeType.QualifiedRule) {
|
|
5904
|
-
listKind = LIST_KIND_NESTED;
|
|
5905
|
-
} else if (
|
|
5906
|
-
parentType === NodeType.AtRule &&
|
|
5907
|
-
KEYFRAMES_AT_RULE_RE.test(_atKeywordOf(parent))
|
|
5908
|
-
) {
|
|
5909
|
-
listKind = LIST_KIND_KEYFRAME;
|
|
5910
|
-
}
|
|
5911
|
-
}
|
|
5912
|
-
_setRuleEntry(_S._currentNode, {
|
|
5913
|
-
text,
|
|
5914
|
-
prelude: prelude.length,
|
|
5915
|
-
atRule: false,
|
|
5916
|
-
plain,
|
|
5917
|
-
listable: plain ? LIST_UNKNOWN : LIST_NO,
|
|
5918
|
-
listKind,
|
|
5919
|
-
children: null,
|
|
5920
|
-
head: "",
|
|
5921
|
-
unordered: false,
|
|
5922
|
-
list: null,
|
|
5923
|
-
block: null,
|
|
5924
|
-
declarations: null
|
|
5925
|
-
});
|
|
5926
|
-
}
|
|
5927
|
-
return text;
|
|
5928
|
-
}
|
|
6020
|
+
case NodeType.QualifiedRule:
|
|
6021
|
+
return _printRuleNode(path, writer, minify, soft);
|
|
5929
6022
|
case NodeType.Raw:
|
|
5930
6023
|
// Off-spec passthrough (see `NodeType.Raw`). It sits in a declaration list,
|
|
5931
6024
|
// so it carries the `;` parting it from the next item, and leaves an entry
|
|
@@ -5944,39 +6037,8 @@ const printer = (path, writer) => {
|
|
|
5944
6037
|
// string, a bracket — would swallow the `;` and `}` written after it.
|
|
5945
6038
|
return `${rest === _S._input.length ? _closeAtEof(raw) : raw};`;
|
|
5946
6039
|
}
|
|
5947
|
-
case NodeType.Hash:
|
|
5948
|
-
|
|
5949
|
-
if (!minify) return raw;
|
|
5950
|
-
// A hash is a hex color in a value and an id in a selector, and ids are
|
|
5951
|
-
// case-sensitive, so only the former shortens — at any value depth. An id's
|
|
5952
|
-
// escapes still shorten, the `#` held back so its first code point is one.
|
|
5953
|
-
if (!path.within(NodeType.Declaration)) {
|
|
5954
|
-
return _S._transforms.rewriteEscapes
|
|
5955
|
-
? `#${_minifyIdentEscapes(raw.slice(1))}`
|
|
5956
|
-
: raw;
|
|
5957
|
-
}
|
|
5958
|
-
// Inside a function only the known color functions take color hashes, and a
|
|
5959
|
-
// substitution's fallback, being the property's value. `paint()` is the
|
|
5960
|
-
// exception: its arguments reach a worklet rather than a declaration.
|
|
5961
|
-
const parent = path.parent;
|
|
5962
|
-
if (parent !== null && path.type(parent) === NodeType.Function) {
|
|
5963
|
-
const fn = unescapeIdentifier(_rawName(parent)).toLowerCase();
|
|
5964
|
-
if (
|
|
5965
|
-
!COLOR_ARGUMENT_FUNCTIONS.has(fn) &&
|
|
5966
|
-
!(SUBSTITUTION_FUNCTIONS.has(fn) && fn !== "paint")
|
|
5967
|
-
) {
|
|
5968
|
-
return raw;
|
|
5969
|
-
}
|
|
5970
|
-
}
|
|
5971
|
-
const short = _minifyHash(raw);
|
|
5972
|
-
const text = short === null ? raw : short;
|
|
5973
|
-
// A hex alpha the target cannot read is written as the `rgba()` it names.
|
|
5974
|
-
if (_S._hexAlphaAllowed) return text;
|
|
5975
|
-
const lowered = _S._transforms.lowerUnsupported
|
|
5976
|
-
? _hexAlphaFallback(text)
|
|
5977
|
-
: null;
|
|
5978
|
-
return lowered === null ? text : lowered;
|
|
5979
|
-
}
|
|
6040
|
+
case NodeType.Hash:
|
|
6041
|
+
return _printHashNode(path, minify);
|
|
5980
6042
|
default: {
|
|
5981
6043
|
// Any remaining leaf token (ident, string, url, delim, …) prints verbatim
|
|
5982
6044
|
// from its source slice.
|