-
Notifications
You must be signed in to change notification settings - Fork 1
/
css-formatter.js
355 lines (270 loc) · 9.14 KB
/
css-formatter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/*
* CSS Minifier in Javascript
*
* @author SupunKavinda <https://twitter.com/SupunWimalasena>
* Created for CSS Creator and Minifier <https://developer.hyvor.com/tools/css-creator>
*
* Minifier is inspired by PHP Library at <https://github.com/matthiasmullie/minify>
* Unminifier is inspired by <https://github.com/mrcoles/cssunminifier/blob/master/lib/cssunminifier.js>
*/
var CSSFormatter = (function() {
// ================ Helper
// PHP's strtr
function strtr(s, p, r) {
return {
2: function () {
for (var i in p) {
s = strtr(s, i, p[i]);
}
return s;
},
3: function () {
return s.replace(RegExp(p, 'g'), r);
},
0: function () {
return;
}
}[arguments.length]();
}
// =============== Core Variables (props)
// "placeholder" => "realstring" pairs
// to encod into a weird string
// for example, strings (with ' and ") will be encoded
// to prevent them from being affected by later replacements
var encoded = {}
// to easily identify (from showdown.js)
// and to make it unique (hopefully noone would use this)
var encodeChar = '¨';
// unique index for encoding
// this should be reset in easy minify() request
var encodeIndex = 0;
// for encoding, we register regexes to use them in the future
// reason: we don't know a matched string was inside a comment
// or vice versa
// and array of [pattern, replacement] arrays
var patterns = [];
// ================ Core functions
function reset() {
encoded = {};
encodeIndex = 0;
}
// Get and set the placeholder
// Get - @return the placeholder
// Set - set the placeholder and value in the encoded vairbale
function getSetPlaceholder(val) {
var placeholder = encodeChar + (++encodeIndex) + encodeChar;
encoded[placeholder] = val;
return placeholder;
}
// ------------- Register Functions
// @param pat {regex} - pattern
// @param rep {string|callable} - replacement
function registerEncodePattern(pat, rep) {
patterns.push( [pat, rep] );
}
// DWNM (Does what name means)
function registerEncodePatterns() {
// already registered
if (patterns.length)
return;
// Strings
registerEncodePattern(/(['"])((?:\\.|[^"\\])*)\1/, function(match) {
return getSetPlaceholder(match);
});
// Comments
registerEncodePattern(/\/\*(.|[\r\n])*?\*\//, '');
// Calcs (One of the trickiest)
registerEncodePattern(/calc(\(.+?)(?=$|;|calc\()/, function(match, inside) {
var expr = '';
openedBrackets = 0;
// loop though each character
for (var i = 0, len = inside.length; i < len; i++) {
var char = inside[i];
expr += char;
if (char === '(')
openedBrackets++;
else if (char === ')' && --openedBrackets === 0)
break;
}
// we found the end
var rest = inside.replace(expr, ''),
// remove brackets
expr = expr.trim();
return getSetPlaceholder( 'calc' + expr ) + rest;
});
}
/**
* A JS translation of <https://github.com/matthiasmullie/minify> (PHP)
*/
function deployRegisteredPatterns(string) {
var last = '',
positions = new Array(patterns.length).join('0').split('').map(function(val) {
return parseInt(val) - 1; // -1
});
matches = [];
var loopThoughPatterns = patterns; // copy from global
var noMoreMatchedIndexes = []; // these patterns do not match the string any more
while (string) {
// the first match
var firstMatch = null,
firstPat = null;
// indexes of patterns that has
// no more matched in this replacement
for (var i = 0, len = loopThoughPatterns.length; i < len; i++) {
// for patterns we found that there's no more matches
if (noMoreMatchedIndexes.indexOf(i) >= 0)
continue;
var pat = loopThoughPatterns[i],
pattern = pat[0];
var match = pattern.exec(string);
// find the first match
if (match) {
if (!firstMatch || match.index < firstMatch.index) {
// make it the first match
firstMatch = match;
firstPat = pat;
}
} else {
// remove this pattern from loop though patterns
// as this didn't match
// it won't match anymore
// there's no need to check it this time
noMoreMatchedIndexes.push(i);
}
}
if (!firstMatch) {
// return string += unmatched string
last += string;
break;
}
// now replace the found one
// it's the first regex
var pattern = firstPat[0],
replacer = firstPat[1],
firstMatchIndex = firstMatch.index,
match = firstMatch[0];
// do the replacement
var replacement = string.replace(pattern, replacer);
// saving temporarily
string = string.substring(firstMatchIndex);
// the unmatched part
// we don't have any match until this now
// in the unmatched part, we can have some matches
// we will loop again to check that
var unmatched = string.substring(match.length);
// string is now unmatched
string = unmatched;
// the return string += replaced string (but not the unmatched string)
last += replacement.substring(0, replacement.length - unmatched.length);
}
return last;
}
// ------------ Decode Function
// restore everything encoded
function decodeAll(string) {
return strtr(string, encoded);
}
// ----------- Replace FUnctions
function stripWhitespaces(string) {
// whispaces in the beginning and end of lines
string = string.replace(/(^\s*|\s*$)/gm, '');
// whitespaces around meta
// from stackoverflow.com/questions/15195750/minify-compress-css-with-regex
// whitespaces around some chars selectors, !important and more
string = string.replace(/\s*([\*$~^|]*=|[{};,>~]|!important\b)\s*/g, '$1');
string = string.replace(/([\[(:>\+])\s+/g, '$1');
string = string.replace(/\s+([\]\)>\+])/g, '$1');
string = string.replace(/\s+(:)(?![^\}]*\{)/g, '$1');
string = string.replace(/:(nth-child|nth-of-type|nth-last-child|nth-last-of-type)\(\s*([+-]?)\s*(.+?)\s*([+-]?)\s*(.*?)\s*\)/g, ':$1($2$3$4$5)')
return string;
}
// removes all ;s right before }
function stripLastSemiColons(string) {
return string.replace(/;}/g, '}');
}
function shortenColors(string) {
// #ffeedd to #fed
string = string.replace(/([: ])#([0-9a-z])\2([0-9a-z])\3([0-9a-z])\4(?:([0-9a-z])\5)?(?=[; }])/ig, '$1#$2$3$4$5');
// unwanted alpha
string = string.replace(/([: ])#([0-9a-z]{6})ff?(?=[; }])/ig, '$1#$2'); // for 6 digit
string = string.replace(/([: ])#([0-9a-z]{3})f?(?=[; }])/ig, '$1#$2'); // for 3 digit
return string;
}
function shortenZeros(string) {
var before = '([:(, ])',
after = '([ ,);}])',
units = '(em|ex|%|px|cm|mm|in|pt|pc|ch|rem|vh|vw|vmin|vmax|vm)';
// 0.0px to 0
var reg = new RegExp(before + "(-?0*0(?:\\.0+)?)px" + after, 'g');
string = string.replace(reg, '$1$2$3');
// .0 => 0
var reg = new RegExp(before + "\\.0+" + units + "?" + after, 'g');
string = string.replace(reg, '$10$2$3');
// 20.100 => 20.1
var reg = new RegExp(before + "(-?[0-9]+\\.[0-9]+?)0+" + units + "?" + after, 'g');
string = string.replace(reg, '$1$2$3$4');
// 20.00 => 20
var reg = new RegExp(before + '(-?[0-9]+)\\.0+' + units + '?' + after , 'g');
string = string.replace(reg, '$1$2$3$4');
// 0.2 => .2
var reg = new RegExp(before + '(-?)0+([0-9]*\\.[0-9]+)' + units + '?' + after , 'g');
string = string.replace(reg, '$1$2$3$4$5');
// a weird -0 to 0
var reg = new RegExp(before + '-?0+' + units + '?' + after , 'g');
string = string.replace(reg, '$10$2$3');
return string;
}
function stripEmptyTags(string) {
string = string.replace(/^[^\{\};]+\{\s*\}/, '');
string = string.replace(/(\}|;)[^\{\};]+\{\s*\}/, '$1');
return string;
}
// ================ Main Functions
function minify(string) {
// reset variables
reset();
// register encoding patterns, if not
registerEncodePatterns();
// make use of encode patterns
// encodes strings, comments, calc
string = deployRegisteredPatterns(string);
// remove whitespaces
string = stripWhitespaces(string);
// remove ; before }
string = stripLastSemiColons(string);
// shorten color codes
string = shortenColors(string);
// shorten zeros by removing unwanted zeros
string = shortenZeros(string);
// {} to
string = stripEmptyTags(string);
// decode encoded things (strings, comments, calc)
string = decodeAll(string);
return string;
}
function unminify(string) {
// reset variables
reset();
// register encoding patterns, if not
registerEncodePatterns();
// make use of encode patterns
// encodes strings, comments, calc
string = deployRegisteredPatterns(string);
string = string
.split('\t').join(' ')
.replace(/\s*{\s*/g, ' {\n ')
.replace(/;\s*/g, ';\n ')
.replace(/,\s*/g, ', ')
.replace(/[ ]*}\s*/g, '}\n')
.replace(/\}\s*(.+)/g, '}\n$1')
.replace(/\n ([^:]+):\s*/g, '\n $1: ')
.replace(/([A-z0-9\)])}/g, '$1;\n}');
// decode encoded things (strings, comments, calc)
string = decodeAll(string);
return string;
}
return {
minify: minify,
unminify: unminify
};
})();