single-file-core 1.0.0

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.
@@ -0,0 +1,789 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Author: Gildas Lormeau
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ // derived from https://github.com/fmarcia/UglifyCSS
26
+
27
+ /**
28
+ * UglifyCSS
29
+ * Port of YUI CSS Compressor to NodeJS
30
+ * Author: Franck Marcia - https://github.com/fmarcia
31
+ * MIT licenced
32
+ */
33
+
34
+ /**
35
+ * cssmin.js
36
+ * Author: Stoyan Stefanov - http://phpied.com/
37
+ * This is a JavaScript port of the CSS minification tool
38
+ * distributed with YUICompressor, itself a port
39
+ * of the cssmin utility by Isaac Schlueter - http://foohack.com/
40
+ * Permission is hereby granted to use the JavaScript version under the same
41
+ * conditions as the YUICompressor (original YUICompressor note below).
42
+ */
43
+
44
+ /**
45
+ * YUI Compressor
46
+ * http://developer.yahoo.com/yui/compressor/
47
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
48
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
49
+ * The copyrights embodied in the content of this file are licensed
50
+ * by Yahoo! Inc. under the BSD (revised) open source license.
51
+ */
52
+
53
+ /**
54
+ * @type {string} - placeholder prefix
55
+ */
56
+
57
+ const ___PRESERVED_TOKEN_ = "___PRESERVED_TOKEN_";
58
+
59
+ /**
60
+ * @typedef {object} options - UglifyCSS options
61
+ * @property {number} [maxLineLen=0] - Maximum line length of uglified CSS
62
+ * @property {boolean} [expandVars=false] - Expand variables
63
+ * @property {boolean} [uglyComments=false] - Removes newlines within preserved comments
64
+ * @property {boolean} [cuteComments=false] - Preserves newlines within and around preserved comments
65
+ * @property {boolean} [debug=false] - Prints full error stack on error
66
+ * @property {string} [output=''] - Output file name
67
+ */
68
+
69
+ /**
70
+ * @type {options} - UglifyCSS options
71
+ */
72
+
73
+ const defaultOptions = {
74
+ maxLineLen: 0,
75
+ expandVars: false,
76
+ uglyComments: false,
77
+ cuteComments: false,
78
+ debug: false,
79
+ output: ""
80
+ };
81
+
82
+ const REGEXP_DATA_URI = /url\(\s*(["']?)data:/g;
83
+ const REGEXP_WHITE_SPACES = /\s+/g;
84
+ const REGEXP_NEW_LINE = /\n/g;
85
+
86
+ /**
87
+ * extractDataUrls replaces all data urls with tokens before we start
88
+ * compressing, to avoid performance issues running some of the subsequent
89
+ * regexes against large strings chunks.
90
+ *
91
+ * @param {string} css - CSS content
92
+ * @param {string[]} preservedTokens - Global array of tokens to preserve
93
+ *
94
+ * @return {string} Processed CSS
95
+ */
96
+
97
+ function extractDataUrls(css, preservedTokens) {
98
+
99
+ // Leave data urls alone to increase parse performance.
100
+ const pattern = REGEXP_DATA_URI;
101
+ const maxIndex = css.length - 1;
102
+ const sb = [];
103
+
104
+ let appendIndex = 0, match;
105
+
106
+ // Since we need to account for non-base64 data urls, we need to handle
107
+ // ' and ) being part of the data string. Hence switching to indexOf,
108
+ // to determine whether or not we have matching string terminators and
109
+ // handling sb appends directly, instead of using matcher.append* methods.
110
+
111
+ while ((match = pattern.exec(css)) !== null) {
112
+
113
+ const startIndex = match.index + 4; // 'url('.length()
114
+ let terminator = match[1]; // ', " or empty (not quoted)
115
+
116
+ if (terminator.length === 0) {
117
+ terminator = ")";
118
+ }
119
+
120
+ let foundTerminator = false, endIndex = pattern.lastIndex - 1;
121
+
122
+ while (foundTerminator === false && endIndex + 1 <= maxIndex && endIndex != -1) {
123
+ endIndex = css.indexOf(terminator, endIndex + 1);
124
+
125
+ // endIndex == 0 doesn't really apply here
126
+ if ((endIndex > 0) && (css.charAt(endIndex - 1) !== "\\")) {
127
+ foundTerminator = true;
128
+ if (")" != terminator) {
129
+ endIndex = css.indexOf(")", endIndex);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Enough searching, start moving stuff over to the buffer
135
+ sb.push(css.substring(appendIndex, match.index));
136
+
137
+ if (foundTerminator) {
138
+
139
+ let token = css.substring(startIndex, endIndex);
140
+ const parts = token.split(",");
141
+ if (parts.length > 1 && parts[0].slice(-7) == ";base64") {
142
+ token = token.replace(REGEXP_WHITE_SPACES, "");
143
+ } else {
144
+ token = token.replace(REGEXP_NEW_LINE, " ");
145
+ token = token.replace(REGEXP_WHITE_SPACES, " ");
146
+ token = token.replace(REGEXP_PRESERVE_HSLA1, "");
147
+ }
148
+
149
+ preservedTokens.push(token);
150
+
151
+ const preserver = "url(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___)";
152
+ sb.push(preserver);
153
+
154
+ appendIndex = endIndex + 1;
155
+ } else {
156
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
157
+ sb.push(css.substring(match.index, pattern.lastIndex));
158
+ appendIndex = pattern.lastIndex;
159
+ }
160
+ }
161
+
162
+ sb.push(css.substring(appendIndex));
163
+
164
+ return sb.join("");
165
+ }
166
+
167
+ const REGEXP_HEX_COLORS = /(=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi;
168
+
169
+ /**
170
+ * compressHexColors compresses hex color values of the form #AABBCC to #ABC.
171
+ *
172
+ * DOES NOT compress CSS ID selectors which match the above pattern (which would
173
+ * break things), like #AddressForm { ... }
174
+ *
175
+ * DOES NOT compress IE filters, which have hex color values (which would break
176
+ * things), like chroma(color='#FFFFFF');
177
+ *
178
+ * DOES NOT compress invalid hex values, like background-color: #aabbccdd
179
+ *
180
+ * @param {string} css - CSS content
181
+ *
182
+ * @return {string} Processed CSS
183
+ */
184
+
185
+ function compressHexColors(css) {
186
+
187
+ // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
188
+
189
+ const pattern = REGEXP_HEX_COLORS;
190
+ const sb = [];
191
+
192
+ let index = 0, match;
193
+
194
+ while ((match = pattern.exec(css)) !== null) {
195
+
196
+ sb.push(css.substring(index, match.index));
197
+
198
+ const isFilter = match[1];
199
+
200
+ if (isFilter) {
201
+ // Restore, maintain case, otherwise filter will break
202
+ sb.push(match[1] + "#" + (match[2] + match[3] + match[4] + match[5] + match[6] + match[7]));
203
+ } else {
204
+ if (match[2].toLowerCase() == match[3].toLowerCase() &&
205
+ match[4].toLowerCase() == match[5].toLowerCase() &&
206
+ match[6].toLowerCase() == match[7].toLowerCase()) {
207
+
208
+ // Compress.
209
+ sb.push("#" + (match[3] + match[5] + match[7]).toLowerCase());
210
+ } else {
211
+ // Non compressible color, restore but lower case.
212
+ sb.push("#" + (match[2] + match[3] + match[4] + match[5] + match[6] + match[7]).toLowerCase());
213
+ }
214
+ }
215
+
216
+ index = pattern.lastIndex = pattern.lastIndex - match[8].length;
217
+ }
218
+
219
+ sb.push(css.substring(index));
220
+
221
+ return sb.join("");
222
+ }
223
+
224
+ const REGEXP_KEYFRAMES = /@[a-z0-9-_]*keyframes\s+[a-z0-9-_]+\s*{/gi;
225
+ const REGEXP_WHITE_SPACE = /(^\s|\s$)/g;
226
+
227
+ /** keyframes preserves 0 followed by unit in keyframes steps
228
+ *
229
+ * @param {string} content - CSS content
230
+ * @param {string[]} preservedTokens - Global array of tokens to preserve
231
+ *
232
+ * @return {string} Processed CSS
233
+ */
234
+
235
+ function keyframes(content, preservedTokens) {
236
+
237
+ const pattern = REGEXP_KEYFRAMES;
238
+
239
+ let index = 0, buffer;
240
+
241
+ const preserve = (part, i) => {
242
+ part = part.replace(REGEXP_WHITE_SPACE, "");
243
+ if (part.charAt(0) === "0") {
244
+ preservedTokens.push(part);
245
+ buffer[i] = ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
246
+ }
247
+ };
248
+
249
+ while (true) { // eslint-disable-line no-constant-condition
250
+
251
+ let level = 0;
252
+ buffer = "";
253
+
254
+ let startIndex = content.slice(index).search(pattern);
255
+ if (startIndex < 0) {
256
+ break;
257
+ }
258
+
259
+ index += startIndex;
260
+ startIndex = index;
261
+
262
+ const len = content.length;
263
+ const buffers = [];
264
+
265
+ for (; index < len; ++index) {
266
+
267
+ const ch = content.charAt(index);
268
+
269
+ if (ch === "{") {
270
+
271
+ if (level === 0) {
272
+ buffers.push(buffer.replace(REGEXP_WHITE_SPACE, ""));
273
+
274
+ } else if (level === 1) {
275
+
276
+ buffer = buffer.split(",");
277
+
278
+ buffer.forEach(preserve);
279
+
280
+ buffers.push(buffer.join(",").replace(REGEXP_WHITE_SPACE, ""));
281
+ }
282
+
283
+ buffer = "";
284
+ level += 1;
285
+
286
+ } else if (ch === "}") {
287
+
288
+ if (level === 2) {
289
+ buffers.push("{" + buffer.replace(REGEXP_WHITE_SPACE, "") + "}");
290
+ buffer = "";
291
+
292
+ } else if (level === 1) {
293
+ content = content.slice(0, startIndex) +
294
+ buffers.shift() + "{" +
295
+ buffers.join("") +
296
+ content.slice(index);
297
+ break;
298
+ }
299
+
300
+ level -= 1;
301
+ }
302
+
303
+ if (level < 0) {
304
+ break;
305
+
306
+ } else if (ch !== "{" && ch !== "}") {
307
+ buffer += ch;
308
+ }
309
+ }
310
+ }
311
+
312
+ return content;
313
+ }
314
+
315
+ /**
316
+ * collectComments collects all comment blocks and return new content with comment placeholders
317
+ *
318
+ * @param {string} content - CSS content
319
+ * @param {string[]} comments - Global array of extracted comments
320
+ *
321
+ * @return {string} Processed CSS
322
+ */
323
+
324
+ function collectComments(content, comments) {
325
+
326
+ const table = [];
327
+
328
+ let from = 0, end;
329
+
330
+ while (true) { // eslint-disable-line no-constant-condition
331
+
332
+ const start = content.indexOf("/*", from);
333
+
334
+ if (start > -1) {
335
+
336
+ end = content.indexOf("*/", start + 2);
337
+
338
+ if (end > -1) {
339
+ comments.push(content.slice(start + 2, end));
340
+ table.push(content.slice(from, start));
341
+ table.push("/*___PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___*/");
342
+ from = end + 2;
343
+
344
+ } else {
345
+ // unterminated comment
346
+ end = -2;
347
+ break;
348
+ }
349
+
350
+ } else {
351
+ break;
352
+ }
353
+ }
354
+
355
+ table.push(content.slice(end + 2));
356
+
357
+ return table.join("");
358
+ }
359
+
360
+ /**
361
+ * processString uglifies a CSS string
362
+ *
363
+ * @param {string} content - CSS string
364
+ * @param {options} options - UglifyCSS options
365
+ *
366
+ * @return {string} Uglified result
367
+ */
368
+
369
+ // const REGEXP_EMPTY_RULES = /[^};{/]+\{\}/g;
370
+ const REGEXP_PRESERVE_STRING = /"([^\\"]|\\.|\\)*"/g;
371
+ const REGEXP_PRESERVE_STRING2 = /'([^\\']|\\.|\\)*'/g;
372
+ const REGEXP_MINIFY_ALPHA = /progid:DXImageTransform.Microsoft.Alpha\(Opacity=/gi;
373
+ const REGEXP_PRESERVE_TOKEN1 = /\r\n/g;
374
+ const REGEXP_PRESERVE_TOKEN2 = /[\r\n]/g;
375
+ const REGEXP_VARIABLES = /@variables\s*\{\s*([^}]+)\s*\}/g;
376
+ const REGEXP_VARIABLE = /\s*([a-z0-9-]+)\s*:\s*([^;}]+)\s*/gi;
377
+ const REGEXP_VARIABLE_VALUE = /var\s*\(\s*([^)]+)\s*\)/g;
378
+ const REGEXP_PRESERVE_CALC = /calc\(([^;}]*)\)/g;
379
+ const REGEXP_TRIM = /(^\s*|\s*$)/g;
380
+ const REGEXP_PRESERVE_CALC2 = /\( /g;
381
+ const REGEXP_PRESERVE_CALC3 = / \)/g;
382
+ const REGEXP_PRESERVE_MATRIX = /\s*filter:\s*progid:DXImageTransform.Microsoft.Matrix\(([^)]+)\);/g;
383
+ const REGEXP_REMOVE_SPACES = /(^|\})(([^{:])+:)+([^{]*{)/g;
384
+ const REGEXP_REMOVE_SPACES2 = /\s+([!{;:>+()\],])/g;
385
+ const REGEXP_REMOVE_SPACES2_BIS = /([^\\])\s+([}])/g;
386
+ const REGEXP_RESTORE_SPACE_IMPORTANT = /!important/g;
387
+ const REGEXP_PSEUDOCLASSCOLON = /___PSEUDOCLASSCOLON___/g;
388
+ const REGEXP_COLUMN = /:/g;
389
+ const REGEXP_PRESERVE_ZERO_UNIT = /\s*(animation|animation-delay|animation-duration|transition|transition-delay|transition-duration):\s*([^;}]+)/gi;
390
+ const REGEXP_PRESERVE_ZERO_UNIT1 = /(^|\D)0?\.?0(m?s)/gi;
391
+ const REGEXP_PRESERVE_FLEX = /\s*(flex|flex-basis):\s*([^;}]+)/gi;
392
+ const REGEXP_SPACES = /\s+/;
393
+ const REGEXP_PRESERVE_HSLA = /(hsla?)\(([^)]+)\)/g;
394
+ const REGEXP_PRESERVE_HSLA1 = /(^\s+|\s+$)/g;
395
+ const REGEXP_RETAIN_SPACE_IE6 = /:first-(line|letter)(\{|,)/gi;
396
+ const REGEXP_CHARSET = /^(.*)(@charset)( "[^"]*";)/gi;
397
+ const REGEXP_REMOVE_SECOND_CHARSET = /^((\s*)(@charset)( [^;]+;\s*))+/gi;
398
+ const REGEXP_LOWERCASE_DIRECTIVES = /@(font-face|import|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?keyframe|media|page|namespace)/gi;
399
+ const REGEXP_LOWERCASE_PSEUDO_ELEMENTS = /:(active|after|before|checked|disabled|empty|enabled|first-(?:child|of-type)|focus|hover|last-(?:child|of-type)|link|only-(?:child|of-type)|root|:selection|target|visited)/gi;
400
+ const REGEXP_CHARSET2 = /^(.*)(@charset "[^"]*";)/g;
401
+ const REGEXP_CHARSET3 = /^(\s*@charset [^;]+;\s*)+/g;
402
+ const REGEXP_LOWERCASE_FUNCTIONS = /:(lang|not|nth-child|nth-last-child|nth-last-of-type|nth-of-type|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?any)\(/gi;
403
+ const REGEXP_LOWERCASE_FUNCTIONS2 = /([:,( ]\s*)(attr|color-stop|from|rgba|to|url|(?:-(?:atsc|khtml|moz|ms|o|wap|webkit)-)?(?:calc|max|min|(?:repeating-)?(?:linear|radial)-gradient)|-webkit-gradient)/gi;
404
+ const REGEXP_NEWLINE1 = /\s*\/\*/g;
405
+ const REGEXP_NEWLINE2 = /\*\/\s*/g;
406
+ const REGEXP_RESTORE_SPACE1 = /\band\(/gi;
407
+ const REGEXP_RESTORE_SPACE2 = /([^:])not\(/gi;
408
+ const REGEXP_RESTORE_SPACE3 = /\bor\(/gi;
409
+ const REGEXP_REMOVE_SPACES3 = /([!{}:;>+([,])\s+/g;
410
+ const REGEXP_REMOVE_SEMI_COLUMNS = /;+\}/g;
411
+ // const REGEXP_REPLACE_ZERO = /(^|[^.0-9\\])(?:0?\.)?0(?:ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|ms|k?Hz|dpi|dpcm|dppx|%)(?![a-z0-9])/gi;
412
+ const REGEXP_REPLACE_ZERO_DOT = /([0-9])\.0(ex|ch|r?em|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|g?rad|turn|m?s|k?Hz|dpi|dpcm|dppx|%| |;)/gi;
413
+ const REGEXP_REPLACE_4_ZEROS = /:0 0 0 0(;|\})/g;
414
+ const REGEXP_REPLACE_3_ZEROS = /:0 0 0(;|\})/g;
415
+ // const REGEXP_REPLACE_2_ZEROS = /:0 0(;|\})/g;
416
+ const REGEXP_REPLACE_1_ZERO = /(transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin|box-shadow):0(;|\})/gi;
417
+ const REGEXP_REPLACE_ZERO_DOT_DECIMAL = /(:|\s)0+\.(\d+)/g;
418
+ const REGEXP_REPLACE_RGB = /rgb\s*\(\s*([0-9,\s]+)\s*\)/gi;
419
+ const REGEXP_REPLACE_BORDER_ZERO = /(border|border-top|border-right|border-bottom|border-left|outline|background):none(;|\})/gi;
420
+ const REGEXP_REPLACE_IE_OPACITY = /progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi;
421
+ const REGEXP_REPLACE_QUERY_FRACTION = /\(([-A-Za-z]+):([0-9]+)\/([0-9]+)\)/g;
422
+ const REGEXP_QUERY_FRACTION = /___QUERY_FRACTION___/g;
423
+ const REGEXP_REPLACE_SEMI_COLUMNS = /;;+/g;
424
+ const REGEXP_REPLACE_HASH_COLOR = /(:|\s)(#f00)(;|})/g;
425
+ const REGEXP_PRESERVED_NEWLINE = /___PRESERVED_NEWLINE___/g;
426
+ const REGEXP_REPLACE_HASH_COLOR_SHORT1 = /(:|\s)(#000080)(;|})/g;
427
+ const REGEXP_REPLACE_HASH_COLOR_SHORT2 = /(:|\s)(#808080)(;|})/g;
428
+ const REGEXP_REPLACE_HASH_COLOR_SHORT3 = /(:|\s)(#808000)(;|})/g;
429
+ const REGEXP_REPLACE_HASH_COLOR_SHORT4 = /(:|\s)(#800080)(;|})/g;
430
+ const REGEXP_REPLACE_HASH_COLOR_SHORT5 = /(:|\s)(#c0c0c0)(;|})/g;
431
+ const REGEXP_REPLACE_HASH_COLOR_SHORT6 = /(:|\s)(#008080)(;|})/g;
432
+ const REGEXP_REPLACE_HASH_COLOR_SHORT7 = /(:|\s)(#ffa500)(;|})/g;
433
+ const REGEXP_REPLACE_HASH_COLOR_SHORT8 = /(:|\s)(#800000)(;|})/g;
434
+
435
+ function processString(content = "", options = defaultOptions) {
436
+
437
+ const comments = [];
438
+ const preservedTokens = [];
439
+
440
+ let pattern;
441
+
442
+ const originalContent = content;
443
+ content = extractDataUrls(content, preservedTokens);
444
+ content = collectComments(content, comments);
445
+
446
+ // preserve strings so their content doesn't get accidentally minified
447
+ preserveString(REGEXP_PRESERVE_STRING);
448
+ preserveString(REGEXP_PRESERVE_STRING2);
449
+
450
+ function preserveString(pattern) {
451
+ content = content.replace(pattern, token => {
452
+ const quote = token.substring(0, 1);
453
+ token = token.slice(1, -1);
454
+ // maybe the string contains a comment-like substring or more? put'em back then
455
+ if (token.indexOf("___PRESERVE_CANDIDATE_COMMENT_") >= 0) {
456
+ for (let i = 0, len = comments.length; i < len; i += 1) {
457
+ token = token.replace("___PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]);
458
+ }
459
+ }
460
+ // minify alpha opacity in filter strings
461
+ token = token.replace(REGEXP_MINIFY_ALPHA, "alpha(opacity=");
462
+ preservedTokens.push(token);
463
+ return quote + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___" + quote;
464
+ });
465
+ }
466
+
467
+ // strings are safe, now wrestle the comments
468
+ for (let i = 0, len = comments.length; i < len; i += 1) {
469
+
470
+ const token = comments[i];
471
+ const placeholder = "___PRESERVE_CANDIDATE_COMMENT_" + i + "___";
472
+
473
+ // ! in the first position of the comment means preserve
474
+ // so push to the preserved tokens keeping the !
475
+ if (token.charAt(0) === "!") {
476
+ if (options.cuteComments) {
477
+ preservedTokens.push(token.substring(1).replace(REGEXP_PRESERVE_TOKEN1, "\n"));
478
+ } else if (options.uglyComments) {
479
+ preservedTokens.push(token.substring(1).replace(REGEXP_PRESERVE_TOKEN2, ""));
480
+ } else {
481
+ preservedTokens.push(token);
482
+ }
483
+ content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
484
+ continue;
485
+ }
486
+
487
+ // \ in the last position looks like hack for Mac/IE5
488
+ // shorten that to /*\*/ and the next one to /**/
489
+ if (token.charAt(token.length - 1) === "\\") {
490
+ preservedTokens.push("\\");
491
+ content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
492
+ i = i + 1; // attn: advancing the loop
493
+ preservedTokens.push("");
494
+ content = content.replace(
495
+ "___PRESERVE_CANDIDATE_COMMENT_" + i + "___",
496
+ ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___"
497
+ );
498
+ continue;
499
+ }
500
+
501
+ // keep empty comments after child selectors (IE7 hack)
502
+ // e.g. html >/**/ body
503
+ if (token.length === 0) {
504
+ const startIndex = content.indexOf(placeholder);
505
+ if (startIndex > 2) {
506
+ if (content.charAt(startIndex - 3) === ">") {
507
+ preservedTokens.push("");
508
+ content = content.replace(placeholder, ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
509
+ }
510
+ }
511
+ }
512
+
513
+ // in all other cases kill the comment
514
+ content = content.replace(`/*${placeholder}*/`, "");
515
+ }
516
+
517
+ // parse simple @variables blocks and remove them
518
+ if (options.expandVars) {
519
+ const vars = {};
520
+ pattern = REGEXP_VARIABLES;
521
+ content = content.replace(pattern, (_, f1) => {
522
+ pattern = REGEXP_VARIABLE;
523
+ f1.replace(pattern, (_, f1, f2) => {
524
+ if (f1 && f2) {
525
+ vars[f1] = f2;
526
+ }
527
+ return "";
528
+ });
529
+ return "";
530
+ });
531
+
532
+ // replace var(x) with the value of x
533
+ pattern = REGEXP_VARIABLE_VALUE;
534
+ content = content.replace(pattern, (_, f1) => {
535
+ return vars[f1] || "none";
536
+ });
537
+ }
538
+
539
+ // normalize all whitespace strings to single spaces. Easier to work with that way.
540
+ content = content.replace(REGEXP_WHITE_SPACES, " ");
541
+
542
+ // preserve formulas in calc() before removing spaces
543
+ pattern = REGEXP_PRESERVE_CALC;
544
+ content = content.replace(pattern, (_, f1) => {
545
+ preservedTokens.push(
546
+ "calc(" +
547
+ f1.replace(REGEXP_TRIM, "")
548
+ .replace(REGEXP_PRESERVE_CALC2, "(")
549
+ .replace(REGEXP_PRESERVE_CALC3, ")") +
550
+ ")"
551
+ );
552
+ return ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
553
+ });
554
+
555
+ // preserve matrix
556
+ pattern = REGEXP_PRESERVE_MATRIX;
557
+ content = content.replace(pattern, (_, f1) => {
558
+ preservedTokens.push(f1);
559
+ return "filter:progid:DXImageTransform.Microsoft.Matrix(" + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___);";
560
+ });
561
+
562
+ // remove the spaces before the things that should not have spaces before them.
563
+ // but, be careful not to turn 'p :link {...}' into 'p:link{...}'
564
+ // swap out any pseudo-class colons with the token, and then swap back.
565
+ pattern = REGEXP_REMOVE_SPACES;
566
+ content = content.replace(pattern, token => token.replace(REGEXP_COLUMN, "___PSEUDOCLASSCOLON___"));
567
+
568
+ // remove spaces before the things that should not have spaces before them.
569
+ content = content.replace(REGEXP_REMOVE_SPACES2, "$1");
570
+ content = content.replace(REGEXP_REMOVE_SPACES2_BIS, "$1$2");
571
+
572
+ // restore spaces for !important
573
+ content = content.replace(REGEXP_RESTORE_SPACE_IMPORTANT, " !important");
574
+
575
+ // bring back the colon
576
+ content = content.replace(REGEXP_PSEUDOCLASSCOLON, ":");
577
+
578
+ // preserve 0 followed by a time unit for properties using time units
579
+ pattern = REGEXP_PRESERVE_ZERO_UNIT;
580
+ content = content.replace(pattern, (_, f1, f2) => {
581
+
582
+ f2 = f2.replace(REGEXP_PRESERVE_ZERO_UNIT1, (_, g1, g2) => {
583
+ preservedTokens.push("0" + g2);
584
+ return g1 + ___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___";
585
+ });
586
+
587
+ return f1 + ":" + f2;
588
+ });
589
+
590
+ // preserve unit for flex-basis within flex and flex-basis (ie10 bug)
591
+ pattern = REGEXP_PRESERVE_FLEX;
592
+ content = content.replace(pattern, (_, f1, f2) => {
593
+ let f2b = f2.split(REGEXP_SPACES);
594
+ preservedTokens.push(f2b.pop());
595
+ f2b.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
596
+ f2b = f2b.join(" ");
597
+ return `${f1}:${f2b}`;
598
+ });
599
+
600
+ // preserve 0% in hsl and hsla color definitions
601
+ content = content.replace(REGEXP_PRESERVE_HSLA, (_, f1, f2) => {
602
+ const f0 = [];
603
+ f2.split(",").forEach(part => {
604
+ part = part.replace(REGEXP_PRESERVE_HSLA1, "");
605
+ if (part === "0%") {
606
+ preservedTokens.push("0%");
607
+ f0.push(___PRESERVED_TOKEN_ + (preservedTokens.length - 1) + "___");
608
+ } else {
609
+ f0.push(part);
610
+ }
611
+ });
612
+ return f1 + "(" + f0.join(",") + ")";
613
+ });
614
+
615
+ // preserve 0 followed by unit in keyframes steps (WIP)
616
+ content = keyframes(content, preservedTokens);
617
+
618
+ // retain space for special IE6 cases
619
+ content = content.replace(REGEXP_RETAIN_SPACE_IE6, (_, f1, f2) => ":first-" + f1.toLowerCase() + " " + f2);
620
+
621
+ // newlines before and after the end of a preserved comment
622
+ if (options.cuteComments) {
623
+ content = content.replace(REGEXP_NEWLINE1, "___PRESERVED_NEWLINE___/*");
624
+ content = content.replace(REGEXP_NEWLINE2, "*/___PRESERVED_NEWLINE___");
625
+ // no space after the end of a preserved comment
626
+ } else {
627
+ content = content.replace(REGEXP_NEWLINE2, "*/");
628
+ }
629
+
630
+ // If there are multiple @charset directives, push them to the top of the file.
631
+ pattern = REGEXP_CHARSET;
632
+ content = content.replace(pattern, (_, f1, f2, f3) => f2.toLowerCase() + f3 + f1);
633
+
634
+ // When all @charset are at the top, remove the second and after (as they are completely ignored).
635
+ pattern = REGEXP_REMOVE_SECOND_CHARSET;
636
+ content = content.replace(pattern, (_, __, f2, f3, f4) => f2 + f3.toLowerCase() + f4);
637
+
638
+ // lowercase some popular @directives (@charset is done right above)
639
+ pattern = REGEXP_LOWERCASE_DIRECTIVES;
640
+ content = content.replace(pattern, (_, f1) => "@" + f1.toLowerCase());
641
+
642
+ // lowercase some more common pseudo-elements
643
+ pattern = REGEXP_LOWERCASE_PSEUDO_ELEMENTS;
644
+ content = content.replace(pattern, (_, f1) => ":" + f1.toLowerCase());
645
+
646
+ // if there is a @charset, then only allow one, and push to the top of the file.
647
+ content = content.replace(REGEXP_CHARSET2, "$2$1");
648
+ content = content.replace(REGEXP_CHARSET3, "$1");
649
+
650
+ // lowercase some more common functions
651
+ pattern = REGEXP_LOWERCASE_FUNCTIONS;
652
+ content = content.replace(pattern, (_, f1) => ":" + f1.toLowerCase() + "(");
653
+
654
+ // lower case some common function that can be values
655
+ // NOTE: rgb() isn't useful as we replace with #hex later, as well as and() is already done for us right after this
656
+ pattern = REGEXP_LOWERCASE_FUNCTIONS2;
657
+ content = content.replace(pattern, (_, f1, f2) => f1 + f2.toLowerCase());
658
+
659
+ // put the space back in some cases, to support stuff like
660
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
661
+ content = content.replace(REGEXP_RESTORE_SPACE1, "and (");
662
+ content = content.replace(REGEXP_RESTORE_SPACE2, "$1not (");
663
+ content = content.replace(REGEXP_RESTORE_SPACE3, "or (");
664
+
665
+ // remove the spaces after the things that should not have spaces after them.
666
+ content = content.replace(REGEXP_REMOVE_SPACES3, "$1");
667
+
668
+ // remove unnecessary semicolons
669
+ content = content.replace(REGEXP_REMOVE_SEMI_COLUMNS, "}");
670
+
671
+ // replace 0(px,em,%) with 0.
672
+ // content = content.replace(REGEXP_REPLACE_ZERO, "$10");
673
+
674
+ // Replace x.0(px,em,%) with x(px,em,%).
675
+ content = content.replace(REGEXP_REPLACE_ZERO_DOT, "$1$2");
676
+
677
+ // replace 0 0 0 0; with 0.
678
+ content = content.replace(REGEXP_REPLACE_4_ZEROS, ":0$1");
679
+ content = content.replace(REGEXP_REPLACE_3_ZEROS, ":0$1");
680
+ // content = content.replace(REGEXP_REPLACE_2_ZEROS, ":0$1");
681
+
682
+ // replace background-position:0; with background-position:0 0;
683
+ // same for transform-origin and box-shadow
684
+ pattern = REGEXP_REPLACE_1_ZERO;
685
+ content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ":0 0" + f2);
686
+
687
+ // replace 0.6 to .6, but only when preceded by : or a white-space
688
+ content = content.replace(REGEXP_REPLACE_ZERO_DOT_DECIMAL, "$1.$2");
689
+
690
+ // shorten colors from rgb(51,102,153) to #336699
691
+ // this makes it more likely that it'll get further compressed in the next step.
692
+ pattern = REGEXP_REPLACE_RGB;
693
+ content = content.replace(pattern, (_, f1) => {
694
+ const rgbcolors = f1.split(",");
695
+ let hexcolor = "#";
696
+ for (let i = 0; i < rgbcolors.length; i += 1) {
697
+ let val = parseInt(rgbcolors[i], 10);
698
+ if (val < 16) {
699
+ hexcolor += "0";
700
+ }
701
+ if (val > 255) {
702
+ val = 255;
703
+ }
704
+ hexcolor += val.toString(16);
705
+ }
706
+ return hexcolor;
707
+ });
708
+
709
+ // Shorten colors from #AABBCC to #ABC.
710
+ content = compressHexColors(content);
711
+
712
+ // Replace #f00 -> red
713
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR, "$1red$3");
714
+
715
+ // Replace other short color keywords
716
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT1, "$1navy$3");
717
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT2, "$1gray$3");
718
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT3, "$1olive$3");
719
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT4, "$1purple$3");
720
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT5, "$1silver$3");
721
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT6, "$1teal$3");
722
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT7, "$1orange$3");
723
+ content = content.replace(REGEXP_REPLACE_HASH_COLOR_SHORT8, "$1maroon$3");
724
+
725
+ // border: none -> border:0
726
+ pattern = REGEXP_REPLACE_BORDER_ZERO;
727
+ content = content.replace(pattern, (_, f1, f2) => f1.toLowerCase() + ":0" + f2);
728
+
729
+ // shorter opacity IE filter
730
+ content = content.replace(REGEXP_REPLACE_IE_OPACITY, "alpha(opacity=");
731
+
732
+ // Find a fraction that is used for Opera's -o-device-pixel-ratio query
733
+ // Add token to add the '\' back in later
734
+ content = content.replace(REGEXP_REPLACE_QUERY_FRACTION, "($1:$2___QUERY_FRACTION___$3)");
735
+
736
+ // remove empty rules.
737
+ // content = content.replace(REGEXP_EMPTY_RULES, "");
738
+
739
+ // Add '\' back to fix Opera -o-device-pixel-ratio query
740
+ content = content.replace(REGEXP_QUERY_FRACTION, "/");
741
+
742
+ // some source control tools don't like it when files containing lines longer
743
+ // than, say 8000 characters, are checked in. The linebreak option is used in
744
+ // that case to split long lines after a specific column.
745
+ if (options.maxLineLen > 0) {
746
+ const lines = [];
747
+ let line = [];
748
+ for (let i = 0, len = content.length; i < len; i += 1) {
749
+ const ch = content.charAt(i);
750
+ line.push(ch);
751
+ if (ch === "}" && line.length > options.maxLineLen) {
752
+ lines.push(line.join(""));
753
+ line = [];
754
+ }
755
+ }
756
+ if (line.length) {
757
+ lines.push(line.join(""));
758
+ }
759
+
760
+ content = lines.join("\n");
761
+ }
762
+
763
+ // replace multiple semi-colons in a row by a single one
764
+ // see SF bug #1980989
765
+ content = content.replace(REGEXP_REPLACE_SEMI_COLUMNS, ";");
766
+
767
+ // trim the final string (for any leading or trailing white spaces)
768
+ content = content.replace(REGEXP_TRIM, "");
769
+
770
+ if (preservedTokens.length > 1000) {
771
+ return originalContent;
772
+ }
773
+
774
+ // restore preserved tokens
775
+ for (let i = preservedTokens.length - 1; i >= 0; i--) {
776
+ content = content.replace(___PRESERVED_TOKEN_ + i + "___", preservedTokens[i], "g");
777
+ }
778
+
779
+ // restore preserved newlines
780
+ content = content.replace(REGEXP_PRESERVED_NEWLINE, "\n");
781
+
782
+ // return
783
+ return content;
784
+ }
785
+
786
+ export {
787
+ defaultOptions,
788
+ processString
789
+ };