@thejaredwilcurt/csslop 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/charset.js +246 -0
- package/src/context.js +7 -8
- package/src/index.js +4 -17
- package/src/position-try.js +0 -32
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@thejaredwilcurt/csslop",
|
|
3
3
|
"main": "index.js",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.23",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/prestart.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"real": "node ./tests/realworld.test.js",
|
|
17
17
|
"lint": "eslint *.js scripts src tests --fix",
|
|
18
18
|
"fix": "npm run lint",
|
|
19
|
-
"bump": "npx --yes -- @jsdevtools/version-bump-prompt && npm i"
|
|
19
|
+
"bump": "npx --yes -- @jsdevtools/version-bump-prompt patch && npm i"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@csstools/css-calc": "^3.3.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@codemirror/autocomplete": "^6.20.3",
|
|
27
27
|
"@codemirror/lang-css": "^6.3.1",
|
|
28
|
-
"@codemirror/view": "^6.43.
|
|
28
|
+
"@codemirror/view": "^6.43.9",
|
|
29
29
|
"@eslint/js": "^10.0.1",
|
|
30
30
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
31
31
|
"codemirror": "^6.0.2",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
35
35
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
|
36
36
|
"eslint-plugin-import-x": "^4.17.0",
|
|
37
|
-
"eslint-plugin-jsdoc": "^64.1
|
|
37
|
+
"eslint-plugin-jsdoc": "^64.2.1",
|
|
38
38
|
"fflate": "^0.8.3",
|
|
39
39
|
"globals": "^17.11.0",
|
|
40
40
|
"pretty-ms": "^9.3.0",
|
|
41
|
-
"real-world-css-libraries": "^1.0.
|
|
42
|
-
"vite": "^8.2.
|
|
41
|
+
"real-world-css-libraries": "^1.0.8",
|
|
42
|
+
"vite": "^8.2.2"
|
|
43
43
|
},
|
|
44
44
|
"author": "The Jared Wilcurt",
|
|
45
45
|
"homepage": "https://github.com/TheJaredWilcurt/csslop#readme",
|
package/src/charset.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Resolves `@charset` labels against the Encoding Standard so charset rules can be deduplicated, hoisted, shortened, or removed.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The default encoding of a stylesheet, used when no `@charset` is declared.
|
|
7
|
+
*
|
|
8
|
+
* @type {string}
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_ENCODING = 'UTF-8';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Encodings that a stylesheet can never actually be in when labelled by a
|
|
14
|
+
* `@charset` rule. The rule itself is written in ASCII bytes, so a UTF-16
|
|
15
|
+
* label falls back to UTF-8 when determining the fallback encoding.
|
|
16
|
+
* https://drafts.csswg.org/css-syntax-3/#determine-the-fallback-encoding
|
|
17
|
+
*
|
|
18
|
+
* @type {Set<string>}
|
|
19
|
+
*/
|
|
20
|
+
const UTF_16_ENCODINGS = new Set(['UTF-16BE', 'UTF-16LE']);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Every encoding name of the Encoding Standard mapped to all of its labels.
|
|
24
|
+
* A `@charset` label is looked up here (ASCII case-insensitively, after
|
|
25
|
+
* trimming whitespace) to find the encoding a browser will use, and any label
|
|
26
|
+
* of that encoding can be swapped in without changing how the file is decoded.
|
|
27
|
+
* https://encoding.spec.whatwg.org/#names-and-labels
|
|
28
|
+
*
|
|
29
|
+
* @type {object}
|
|
30
|
+
*/
|
|
31
|
+
const ENCODING_LABELS = {
|
|
32
|
+
// The encoding
|
|
33
|
+
'UTF-8': ['unicode-1-1-utf-8', 'unicode11utf8', 'unicode20utf8', 'utf-8', 'utf8', 'x-unicode20utf8'],
|
|
34
|
+
// Legacy single-byte encodings
|
|
35
|
+
IBM866: ['866', 'cp866', 'csibm866', 'ibm866'],
|
|
36
|
+
'ISO-8859-2': ['csisolatin2', 'iso-8859-2', 'iso-ir-101', 'iso8859-2', 'iso88592', 'iso_8859-2', 'iso_8859-2:1987', 'l2', 'latin2'],
|
|
37
|
+
'ISO-8859-3': ['csisolatin3', 'iso-8859-3', 'iso-ir-109', 'iso8859-3', 'iso88593', 'iso_8859-3', 'iso_8859-3:1988', 'l3', 'latin3'],
|
|
38
|
+
'ISO-8859-4': ['csisolatin4', 'iso-8859-4', 'iso-ir-110', 'iso8859-4', 'iso88594', 'iso_8859-4', 'iso_8859-4:1988', 'l4', 'latin4'],
|
|
39
|
+
'ISO-8859-5': ['csisolatincyrillic', 'cyrillic', 'iso-8859-5', 'iso-ir-144', 'iso8859-5', 'iso88595', 'iso_8859-5', 'iso_8859-5:1988'],
|
|
40
|
+
'ISO-8859-6': ['arabic', 'asmo-708', 'csiso88596e', 'csiso88596i', 'csisolatinarabic', 'ecma-114', 'iso-8859-6', 'iso-8859-6-e', 'iso-8859-6-i', 'iso-ir-127', 'iso8859-6', 'iso88596', 'iso_8859-6', 'iso_8859-6:1987'],
|
|
41
|
+
'ISO-8859-7': ['csisolatingreek', 'ecma-118', 'elot_928', 'greek', 'greek8', 'iso-8859-7', 'iso-ir-126', 'iso8859-7', 'iso88597', 'iso_8859-7', 'iso_8859-7:1987', 'sun_eu_greek'],
|
|
42
|
+
'ISO-8859-8': ['csiso88598e', 'csisolatinhebrew', 'hebrew', 'iso-8859-8', 'iso-8859-8-e', 'iso-ir-138', 'iso8859-8', 'iso88598', 'iso_8859-8', 'iso_8859-8:1988', 'visual'],
|
|
43
|
+
'ISO-8859-8-I': ['csiso88598i', 'iso-8859-8-i', 'logical'],
|
|
44
|
+
'ISO-8859-10': ['csisolatin6', 'iso-8859-10', 'iso-ir-157', 'iso8859-10', 'iso885910', 'l6', 'latin6'],
|
|
45
|
+
'ISO-8859-13': ['iso-8859-13', 'iso8859-13', 'iso885913'],
|
|
46
|
+
'ISO-8859-14': ['iso-8859-14', 'iso8859-14', 'iso885914'],
|
|
47
|
+
'ISO-8859-15': ['csisolatin9', 'iso-8859-15', 'iso8859-15', 'iso885915', 'iso_8859-15', 'l9'],
|
|
48
|
+
'ISO-8859-16': ['iso-8859-16'],
|
|
49
|
+
'KOI8-R': ['cskoi8r', 'koi', 'koi8', 'koi8-r', 'koi8_r'],
|
|
50
|
+
'KOI8-U': ['koi8-ru', 'koi8-u'],
|
|
51
|
+
macintosh: ['csmacintosh', 'mac', 'macintosh', 'x-mac-roman'],
|
|
52
|
+
'windows-874': ['dos-874', 'iso-8859-11', 'iso8859-11', 'iso885911', 'tis-620', 'windows-874'],
|
|
53
|
+
'windows-1250': ['cp1250', 'windows-1250', 'x-cp1250'],
|
|
54
|
+
'windows-1251': ['cp1251', 'windows-1251', 'x-cp1251'],
|
|
55
|
+
'windows-1252': ['ansi_x3.4-1968', 'ascii', 'cp1252', 'cp819', 'csisolatin1', 'ibm819', 'iso-8859-1', 'iso-ir-100', 'iso8859-1', 'iso88591', 'iso_8859-1', 'iso_8859-1:1987', 'l1', 'latin1', 'us-ascii', 'windows-1252', 'x-cp1252'],
|
|
56
|
+
'windows-1253': ['cp1253', 'windows-1253', 'x-cp1253'],
|
|
57
|
+
'windows-1254': ['cp1254', 'csisolatin5', 'iso-8859-9', 'iso-ir-148', 'iso8859-9', 'iso88599', 'iso_8859-9', 'iso_8859-9:1989', 'l5', 'latin5', 'windows-1254', 'x-cp1254'],
|
|
58
|
+
'windows-1255': ['cp1255', 'windows-1255', 'x-cp1255'],
|
|
59
|
+
'windows-1256': ['cp1256', 'windows-1256', 'x-cp1256'],
|
|
60
|
+
'windows-1257': ['cp1257', 'windows-1257', 'x-cp1257'],
|
|
61
|
+
'windows-1258': ['cp1258', 'windows-1258', 'x-cp1258'],
|
|
62
|
+
'x-mac-cyrillic': ['x-mac-cyrillic', 'x-mac-ukrainian'],
|
|
63
|
+
// Legacy multi-byte Chinese (simplified) encodings
|
|
64
|
+
GBK: ['chinese', 'csgb2312', 'csiso58gb231280', 'gb2312', 'gb_2312', 'gb_2312-80', 'gbk', 'iso-ir-58', 'x-gbk'],
|
|
65
|
+
gb18030: ['gb18030'],
|
|
66
|
+
// Legacy multi-byte Chinese (traditional) encodings
|
|
67
|
+
Big5: ['big5', 'big5-hkscs', 'cn-big5', 'csbig5', 'x-x-big5'],
|
|
68
|
+
// Legacy multi-byte Japanese encodings
|
|
69
|
+
'EUC-JP': ['cseucpkdfmtjapanese', 'euc-jp', 'x-euc-jp'],
|
|
70
|
+
'ISO-2022-JP': ['csiso2022jp', 'iso-2022-jp'],
|
|
71
|
+
Shift_JIS: ['csshiftjis', 'ms932', 'ms_kanji', 'shift-jis', 'shift_jis', 'sjis', 'windows-31j', 'x-sjis'],
|
|
72
|
+
// Legacy multi-byte Korean encodings
|
|
73
|
+
'EUC-KR': ['cseuckr', 'csksc56011987', 'euc-kr', 'iso-ir-149', 'korean', 'ks_c_5601-1987', 'ks_c_5601-1989', 'ksc5601', 'ksc_5601', 'windows-949'],
|
|
74
|
+
// Legacy miscellaneous encodings
|
|
75
|
+
replacement: ['csiso2022kr', 'hz-gb-2312', 'iso-2022-cn', 'iso-2022-cn-ext', 'iso-2022-kr', 'replacement'],
|
|
76
|
+
'UTF-16BE': ['unicodefffe', 'utf-16be'],
|
|
77
|
+
'UTF-16LE': ['csunicode', 'iso-10646-ucs-2', 'ucs-2', 'unicode', 'unicodefeff', 'utf-16', 'utf-16le'],
|
|
78
|
+
'x-user-defined': ['x-user-defined']
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Builds the lookup of every Encoding Standard label to the encoding it names.
|
|
83
|
+
*
|
|
84
|
+
* @return {Map} Map of lowercase label to encoding name.
|
|
85
|
+
*/
|
|
86
|
+
function createEncodingByLabelLookup () {
|
|
87
|
+
const encodingByLabel = new Map();
|
|
88
|
+
for (const encodingName in ENCODING_LABELS) {
|
|
89
|
+
for (const label of ENCODING_LABELS[encodingName]) {
|
|
90
|
+
encodingByLabel.set(label, encodingName);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return encodingByLabel;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds the lookup of every encoding to its fewest-bytes label, so a
|
|
98
|
+
* `@charset` rule can be rewritten with a shorter but equivalent label.
|
|
99
|
+
* Labels of the same length are ordered alphabetically to keep output stable.
|
|
100
|
+
*
|
|
101
|
+
* @return {Map} Map of encoding name to its shortest label.
|
|
102
|
+
*/
|
|
103
|
+
function createShortestLabelLookup () {
|
|
104
|
+
const shortestLabels = new Map();
|
|
105
|
+
for (const encodingName in ENCODING_LABELS) {
|
|
106
|
+
const labelsByLength = [...ENCODING_LABELS[encodingName]].sort((labelA, labelB) => {
|
|
107
|
+
if (labelA.length !== labelB.length) {
|
|
108
|
+
return labelA.length - labelB.length;
|
|
109
|
+
}
|
|
110
|
+
return labelA.localeCompare(labelB);
|
|
111
|
+
});
|
|
112
|
+
shortestLabels.set(encodingName, labelsByLength[0]);
|
|
113
|
+
}
|
|
114
|
+
return shortestLabels;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const ENCODING_BY_LABEL = createEncodingByLabelLookup();
|
|
118
|
+
const SHORTEST_LABEL_BY_ENCODING = createShortestLabelLookup();
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Reduces a raw `@charset` value to the label that gets looked up, by removing
|
|
122
|
+
* the surrounding quotes, trimming whitespace, and lowercasing it, since
|
|
123
|
+
* Encoding Standard labels are matched ASCII case-insensitively.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} charsetValue The `@charset` value, with or without surrounding quotes.
|
|
126
|
+
* @return {string} The normalized label, or empty string when there is no value.
|
|
127
|
+
*/
|
|
128
|
+
function normalizeCharsetLabel (charsetValue) {
|
|
129
|
+
if (!charsetValue) {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
// Remove one layer of matching single or double quotes wrapping the label
|
|
133
|
+
const unquoted = String(charsetValue).trim().replace(/^(["'])([\s\S]*)\1$/, '$2');
|
|
134
|
+
return unquoted.trim().toLowerCase();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Resolves a `@charset` value to the encoding a browser will actually decode
|
|
139
|
+
* the stylesheet with. Labels that are not in the Encoding Standard are
|
|
140
|
+
* ignored, and UTF-16 labels fall back to UTF-8, so both leave the stylesheet
|
|
141
|
+
* in the default encoding.
|
|
142
|
+
*
|
|
143
|
+
* @param {string} charsetValue The `@charset` value, with or without surrounding quotes.
|
|
144
|
+
* @return {string} The effective encoding name.
|
|
145
|
+
*/
|
|
146
|
+
function resolveEffectiveEncoding (charsetValue) {
|
|
147
|
+
const encodingName = ENCODING_BY_LABEL.get(normalizeCharsetLabel(charsetValue));
|
|
148
|
+
if (!encodingName || UTF_16_ENCODINGS.has(encodingName)) {
|
|
149
|
+
return DEFAULT_ENCODING;
|
|
150
|
+
}
|
|
151
|
+
return encodingName;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Determines whether a `@charset` value leaves the stylesheet in an encoding
|
|
156
|
+
* that can represent every unicode character, meaning unicode escapes may be
|
|
157
|
+
* safely replaced with the literal characters they resolve to.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} charsetValue The `@charset` value, with or without surrounding quotes.
|
|
160
|
+
* @return {boolean} True when the effective encoding supports all unicode characters.
|
|
161
|
+
*/
|
|
162
|
+
function isUnicodeCompatibleCharset (charsetValue) {
|
|
163
|
+
return resolveEffectiveEncoding(charsetValue) === DEFAULT_ENCODING;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Rewrites a `@charset` value as the shortest quoted label for the same
|
|
168
|
+
* encoding, or as an empty string when the rule has no effect at all and can
|
|
169
|
+
* be deleted, because the stylesheet is left in the default UTF-8 encoding.
|
|
170
|
+
* The declared label is kept when no shorter label exists for its encoding.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} charsetValue The `@charset` value, with or without surrounding quotes.
|
|
173
|
+
* @return {string} The shortest equivalent quoted value, or empty string when the rule is removable.
|
|
174
|
+
*/
|
|
175
|
+
function optimizeCharsetValue (charsetValue) {
|
|
176
|
+
const encodingName = resolveEffectiveEncoding(charsetValue);
|
|
177
|
+
if (encodingName === DEFAULT_ENCODING) {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
const declaredLabel = normalizeCharsetLabel(charsetValue);
|
|
181
|
+
const shortestLabel = SHORTEST_LABEL_BY_ENCODING.get(encodingName);
|
|
182
|
+
if (shortestLabel.length < declaredLabel.length) {
|
|
183
|
+
return '"' + shortestLabel + '"';
|
|
184
|
+
}
|
|
185
|
+
return '"' + declaredLabel + '"';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Finds the `@charset` value that applies to a stylesheet by scanning the raw
|
|
190
|
+
* CSS text before it is parsed. Only the first `@charset` of a document has any
|
|
191
|
+
* effect, so later ones (usually the result of concatenating files) are ignored.
|
|
192
|
+
*
|
|
193
|
+
* @param {string} css The raw CSS string to scan.
|
|
194
|
+
* @return {string} The first `@charset` value (with quotes), or empty string when none is declared.
|
|
195
|
+
*/
|
|
196
|
+
function detectCharset (css) {
|
|
197
|
+
// Match @charset followed by a quoted value and semicolon
|
|
198
|
+
const match = css.match(/@charset\s+(["'][^"']+["'])\s*;/i);
|
|
199
|
+
if (match) {
|
|
200
|
+
return match[1];
|
|
201
|
+
}
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Applies the browser's `@charset` handling to the top-level rules of a
|
|
207
|
+
* stylesheet. Only the first `@charset` is meaningful, and it is only honored
|
|
208
|
+
* when it is the very first thing in the file, so it is shortened and hoisted
|
|
209
|
+
* to the front while every later `@charset` is dropped. A first `@charset` that
|
|
210
|
+
* leaves the stylesheet in the default UTF-8 encoding is dropped as well.
|
|
211
|
+
*
|
|
212
|
+
* @param {Array} rules The top-level AST rule nodes to filter.
|
|
213
|
+
* @return {Array} A new array of rules, with at most one `@charset` rule, placed at the start.
|
|
214
|
+
*/
|
|
215
|
+
function filterRedundantCharsets (rules) {
|
|
216
|
+
let hoistedCharset = null;
|
|
217
|
+
let foundCharset = false;
|
|
218
|
+
|
|
219
|
+
const filtered = rules.filter((rule) => {
|
|
220
|
+
if (rule.type !== 'charset') {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
if (!foundCharset) {
|
|
224
|
+
foundCharset = true;
|
|
225
|
+
const optimizedCharset = optimizeCharsetValue(rule.charset);
|
|
226
|
+
if (optimizedCharset) {
|
|
227
|
+
hoistedCharset = {
|
|
228
|
+
...rule,
|
|
229
|
+
charset: optimizedCharset
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (hoistedCharset) {
|
|
237
|
+
return [hoistedCharset, ...filtered];
|
|
238
|
+
}
|
|
239
|
+
return filtered;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export {
|
|
243
|
+
detectCharset,
|
|
244
|
+
filterRedundantCharsets,
|
|
245
|
+
isUnicodeCompatibleCharset
|
|
246
|
+
};
|
package/src/context.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* @file Manages the shared minification context for tracking registered custom properties and their syntax.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { isUnicodeCompatibleCharset } from './charset.js';
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Creates a fresh minification context used to track `@property`-registered custom properties and their declared syntax types across the entire stylesheet.
|
|
7
9
|
*
|
|
@@ -22,18 +24,15 @@ function createMinifyContext () {
|
|
|
22
24
|
let activeCharset = '';
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
|
-
* Returns true when the active charset
|
|
26
|
-
* (UTF-8, UTF-16
|
|
27
|
-
*
|
|
27
|
+
* Returns true when the active charset leaves the stylesheet in a
|
|
28
|
+
* unicode-compatible encoding (UTF-8, a UTF-16 label that falls back to UTF-8,
|
|
29
|
+
* an unrecognized label, or the default when no `@charset` is declared),
|
|
30
|
+
* meaning CSS unicode escapes can safely be resolved to literal characters.
|
|
28
31
|
*
|
|
29
32
|
* @return {boolean} True if the active charset supports unicode characters.
|
|
30
33
|
*/
|
|
31
34
|
function isUnicodeCharset () {
|
|
32
|
-
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
const normalized = activeCharset.toLowerCase().replace(/["']/g, '');
|
|
36
|
-
return normalized === 'utf-8' || normalized.startsWith('utf-16');
|
|
35
|
+
return isUnicodeCompatibleCharset(activeCharset);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { parse } from '@node-projects/css-parser';
|
|
6
6
|
|
|
7
|
+
import {
|
|
8
|
+
detectCharset,
|
|
9
|
+
filterRedundantCharsets
|
|
10
|
+
} from './charset.js';
|
|
7
11
|
import {
|
|
8
12
|
clearActiveCharset,
|
|
9
13
|
createMinifyContext,
|
|
@@ -13,7 +17,6 @@ import {
|
|
|
13
17
|
analyzePositionTryRules,
|
|
14
18
|
cleanPositionTryRules,
|
|
15
19
|
collectRuleMetadata,
|
|
16
|
-
filterRedundantCharsets,
|
|
17
20
|
filterUnusedPositionTry
|
|
18
21
|
} from './position-try.js';
|
|
19
22
|
import {
|
|
@@ -172,22 +175,6 @@ function mergeAdjacentRulesWithIdenticalBodies (ruleStrings) {
|
|
|
172
175
|
return result;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
|
-
/**
|
|
176
|
-
* Extracts the first `@charset` value from raw CSS text before parsing.
|
|
177
|
-
* Scans for `@charset` followed by a quoted string and semicolon.
|
|
178
|
-
*
|
|
179
|
-
* @param {string} css The raw CSS string to scan.
|
|
180
|
-
* @return {string} The first charset value (with quotes), or empty string if none found.
|
|
181
|
-
*/
|
|
182
|
-
function detectCharset (css) {
|
|
183
|
-
// Match @charset followed by a quoted value and semicolon
|
|
184
|
-
const match = css.match(/@charset\s+(["'][^"']+["'])\s*;/i);
|
|
185
|
-
if (match) {
|
|
186
|
-
return match[1];
|
|
187
|
-
}
|
|
188
|
-
return '';
|
|
189
|
-
}
|
|
190
|
-
|
|
191
178
|
/**
|
|
192
179
|
* Parses, optimizes, and minifies a CSS string by applying rule merging, declaration deduplication, value compression, and dead-code elimination.
|
|
193
180
|
*
|
package/src/position-try.js
CHANGED
|
@@ -163,41 +163,9 @@ function filterUnusedPositionTry (rules, positionTryRules, positionTryUsage) {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
/**
|
|
167
|
-
* Removes duplicate and redundant UTF-8 `@charset` rules, keeping only the first
|
|
168
|
-
* non-UTF-8 charset declaration and moving it to the top of the document.
|
|
169
|
-
* Per the CSS specification, `@charset` must be the very first thing in a stylesheet.
|
|
170
|
-
*
|
|
171
|
-
* @param {Array} rules The top-level AST rule nodes to filter.
|
|
172
|
-
* @return {Array} A new array of rules with the first non-UTF-8 `@charset` at the start and all others removed.
|
|
173
|
-
*/
|
|
174
|
-
function filterRedundantCharsets (rules) {
|
|
175
|
-
let keptCharset = null;
|
|
176
|
-
|
|
177
|
-
const filtered = rules.filter((rule) => {
|
|
178
|
-
if (rule.type !== 'charset') {
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
if (!keptCharset) {
|
|
182
|
-
// Strip surrounding quotes from the charset value for comparison
|
|
183
|
-
const normalizedCharset = rule.charset?.toLowerCase().replace(/["']/g, '');
|
|
184
|
-
if (normalizedCharset !== 'utf-8') {
|
|
185
|
-
keptCharset = rule;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
if (keptCharset) {
|
|
192
|
-
return [keptCharset, ...filtered];
|
|
193
|
-
}
|
|
194
|
-
return filtered;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
166
|
export {
|
|
198
167
|
analyzePositionTryRules,
|
|
199
168
|
cleanPositionTryRules,
|
|
200
169
|
collectRuleMetadata,
|
|
201
|
-
filterRedundantCharsets,
|
|
202
170
|
filterUnusedPositionTry
|
|
203
171
|
};
|