@thejaredwilcurt/csslop 0.0.23 → 0.0.24
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 +2 -2
- package/src/charset.js +72 -11
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.24",
|
|
6
6
|
"description": "Experimental CSS minification",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"prestart": "node ./scripts/prestart.js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@eslint/js": "^10.0.1",
|
|
30
30
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
31
31
|
"codemirror": "^6.0.2",
|
|
32
|
-
"eslint": "^10.
|
|
32
|
+
"eslint": "^10.9.0",
|
|
33
33
|
"eslint-config-tjw-base": "^5.0.0",
|
|
34
34
|
"eslint-config-tjw-import-x": "^1.0.1",
|
|
35
35
|
"eslint-config-tjw-jsdoc": "^2.0.1",
|
package/src/charset.js
CHANGED
|
@@ -117,6 +117,64 @@ function createShortestLabelLookup () {
|
|
|
117
117
|
const ENCODING_BY_LABEL = createEncodingByLabelLookup();
|
|
118
118
|
const SHORTEST_LABEL_BY_ENCODING = createShortestLabelLookup();
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* The byte sequence an engine looks for when a stylesheet declares its
|
|
122
|
+
* encoding: the lowercase `@charset` keyword, a single space, a double-quoted
|
|
123
|
+
* label, and a semicolon. Engines compare these raw bytes instead of parsing
|
|
124
|
+
* CSS syntax, so a rule written any other way (single quotes, an uppercased
|
|
125
|
+
* keyword, padded whitespace, escaped label bytes) declares nothing at all.
|
|
126
|
+
* https://drafts.csswg.org/css-syntax-3/#determine-the-fallback-encoding
|
|
127
|
+
*
|
|
128
|
+
* @type {RegExp}
|
|
129
|
+
*/
|
|
130
|
+
// Matches `@charset "label";` anywhere in raw CSS text, capturing the quoted label
|
|
131
|
+
const ENCODING_DECLARATION_PATTERN = /@charset ("[^"]*");/;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The same byte sequence, but required to make up an entire at-rule, so that
|
|
135
|
+
* text surrounding the keyword disqualifies the rule as a declaration.
|
|
136
|
+
*
|
|
137
|
+
* @type {RegExp}
|
|
138
|
+
*/
|
|
139
|
+
// Matches raw at-rule text that is exactly `@charset "label";`, capturing the quoted label
|
|
140
|
+
const ENCODING_DECLARATION_RULE_PATTERN = /^@charset ("[^"]*");$/;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reads the quoted label of an encoding declaration out of raw CSS text.
|
|
144
|
+
* Text that does not contain the declaration byte sequence declares no
|
|
145
|
+
* encoding, so an empty string is returned for it.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} text The raw CSS text to read the declaration from.
|
|
148
|
+
* @param {RegExp} pattern The byte sequence pattern to read the text with.
|
|
149
|
+
* @return {string} The declared value including its quotes, or empty string when nothing is declared.
|
|
150
|
+
*/
|
|
151
|
+
function readEncodingDeclarationValue (text, pattern) {
|
|
152
|
+
if (!text) {
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
const match = String(text).match(pattern);
|
|
156
|
+
if (!match) {
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
return match[1];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reads the value a `@charset` AST node declares, based on the raw text the
|
|
164
|
+
* rule was written with rather than its parsed value. A rule the engine never
|
|
165
|
+
* reads as an encoding declaration is inert, and reports no value.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} rule A `@charset` AST rule node.
|
|
168
|
+
* @return {string} The declared value including its quotes, or empty string when the rule is inert.
|
|
169
|
+
*/
|
|
170
|
+
function readCharsetRuleValue (rule) {
|
|
171
|
+
let ruleText = rule.rawSource;
|
|
172
|
+
if (!ruleText) {
|
|
173
|
+
ruleText = '@charset ' + rule.charset + ';';
|
|
174
|
+
}
|
|
175
|
+
return readEncodingDeclarationValue(String(ruleText).trim(), ENCODING_DECLARATION_RULE_PATTERN);
|
|
176
|
+
}
|
|
177
|
+
|
|
120
178
|
/**
|
|
121
179
|
* Reduces a raw `@charset` value to the label that gets looked up, by removing
|
|
122
180
|
* the surrounding quotes, trimming whitespace, and lowercasing it, since
|
|
@@ -188,18 +246,14 @@ function optimizeCharsetValue (charsetValue) {
|
|
|
188
246
|
/**
|
|
189
247
|
* Finds the `@charset` value that applies to a stylesheet by scanning the raw
|
|
190
248
|
* 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
|
|
249
|
+
* effect, so later ones (usually the result of concatenating files) are ignored,
|
|
250
|
+
* and only text matching the declaration byte sequence declares an encoding.
|
|
192
251
|
*
|
|
193
252
|
* @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.
|
|
253
|
+
* @return {string} The first declared `@charset` value (with quotes), or empty string when none is declared.
|
|
195
254
|
*/
|
|
196
255
|
function detectCharset (css) {
|
|
197
|
-
|
|
198
|
-
const match = css.match(/@charset\s+(["'][^"']+["'])\s*;/i);
|
|
199
|
-
if (match) {
|
|
200
|
-
return match[1];
|
|
201
|
-
}
|
|
202
|
-
return '';
|
|
256
|
+
return readEncodingDeclarationValue(css, ENCODING_DECLARATION_PATTERN);
|
|
203
257
|
}
|
|
204
258
|
|
|
205
259
|
/**
|
|
@@ -207,7 +261,9 @@ function detectCharset (css) {
|
|
|
207
261
|
* stylesheet. Only the first `@charset` is meaningful, and it is only honored
|
|
208
262
|
* when it is the very first thing in the file, so it is shortened and hoisted
|
|
209
263
|
* 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
|
|
264
|
+
* leaves the stylesheet in the default UTF-8 encoding is dropped as well, as is
|
|
265
|
+
* any `@charset` that is not written as an encoding declaration, since engines
|
|
266
|
+
* discard those without ever reading an encoding from them.
|
|
211
267
|
*
|
|
212
268
|
* @param {Array} rules The top-level AST rule nodes to filter.
|
|
213
269
|
* @return {Array} A new array of rules, with at most one `@charset` rule, placed at the start.
|
|
@@ -220,13 +276,18 @@ function filterRedundantCharsets (rules) {
|
|
|
220
276
|
if (rule.type !== 'charset') {
|
|
221
277
|
return true;
|
|
222
278
|
}
|
|
279
|
+
const declaredValue = readCharsetRuleValue(rule);
|
|
280
|
+
if (!declaredValue) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
223
283
|
if (!foundCharset) {
|
|
224
284
|
foundCharset = true;
|
|
225
|
-
const optimizedCharset = optimizeCharsetValue(
|
|
285
|
+
const optimizedCharset = optimizeCharsetValue(declaredValue);
|
|
226
286
|
if (optimizedCharset) {
|
|
227
287
|
hoistedCharset = {
|
|
228
288
|
...rule,
|
|
229
|
-
charset: optimizedCharset
|
|
289
|
+
charset: optimizedCharset,
|
|
290
|
+
rawSource: '@charset ' + optimizedCharset + ';'
|
|
230
291
|
};
|
|
231
292
|
}
|
|
232
293
|
}
|