ruby-clean-css 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +3 -0
- data/EXAMPLE.md +25 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +30 -0
- data/LICENSE +19 -0
- data/README.md +106 -0
- data/lib/javascript/clean-css/.gitignore +4 -0
- data/lib/javascript/clean-css/.jshintignore +3 -0
- data/lib/javascript/clean-css/.jshintrc +14 -0
- data/lib/javascript/clean-css/.travis.yml +11 -0
- data/lib/javascript/clean-css/History.md +556 -0
- data/lib/javascript/clean-css/LICENSE +19 -0
- data/lib/javascript/clean-css/README.md +218 -0
- data/lib/javascript/clean-css/bin/cleancss +157 -0
- data/lib/javascript/clean-css/index.js +1 -0
- data/lib/javascript/clean-css/lib/clean.js +426 -0
- data/lib/javascript/clean-css/lib/colors/hsl-to-hex.js +64 -0
- data/lib/javascript/clean-css/lib/colors/long-to-short-hex.js +12 -0
- data/lib/javascript/clean-css/lib/colors/rgb-to-hex.js +14 -0
- data/lib/javascript/clean-css/lib/colors/shortener.js +174 -0
- data/lib/javascript/clean-css/lib/images/url-rebase.js +32 -0
- data/lib/javascript/clean-css/lib/images/url-rewriter.js +60 -0
- data/lib/javascript/clean-css/lib/imports/inliner.js +319 -0
- data/lib/javascript/clean-css/lib/properties/optimizer.js +276 -0
- data/lib/javascript/clean-css/lib/properties/override-compactor.js +116 -0
- data/lib/javascript/clean-css/lib/properties/processable.js +859 -0
- data/lib/javascript/clean-css/lib/properties/scanner.js +20 -0
- data/lib/javascript/clean-css/lib/properties/shorthand-compactor.js +244 -0
- data/lib/javascript/clean-css/lib/properties/token.js +184 -0
- data/lib/javascript/clean-css/lib/properties/validator.js +140 -0
- data/lib/javascript/clean-css/lib/selectors/empty-removal.js +30 -0
- data/lib/javascript/clean-css/lib/selectors/optimizer.js +341 -0
- data/lib/javascript/clean-css/lib/selectors/tokenizer.js +168 -0
- data/lib/javascript/clean-css/lib/text/comments.js +83 -0
- data/lib/javascript/clean-css/lib/text/escape-store.js +32 -0
- data/lib/javascript/clean-css/lib/text/expressions.js +73 -0
- data/lib/javascript/clean-css/lib/text/free.js +26 -0
- data/lib/javascript/clean-css/lib/text/name-quotes.js +37 -0
- data/lib/javascript/clean-css/lib/text/quote-scanner.js +91 -0
- data/lib/javascript/clean-css/lib/text/splitter.js +35 -0
- data/lib/javascript/clean-css/lib/text/urls.js +41 -0
- data/lib/javascript/clean-css/package.json +50 -0
- data/lib/javascript/clean-css/test/batch-test.js +56 -0
- data/lib/javascript/clean-css/test/bench.js +11 -0
- data/lib/javascript/clean-css/test/binary-test.js +323 -0
- data/lib/javascript/clean-css/test/data-bench/_partial.css +1 -0
- data/lib/javascript/clean-css/test/data-bench/complex.css +4 -0
- data/lib/javascript/clean-css/test/data/129-assets/assets/ui.css +2 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/css/bootstrap.css +3 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/images/glyphs.gif +0 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/css/style.css +6 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/next.gif +0 -0
- data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/prev.gif +0 -0
- data/lib/javascript/clean-css/test/data/960-min.css +125 -0
- data/lib/javascript/clean-css/test/data/960.css +602 -0
- data/lib/javascript/clean-css/test/data/big-min.css +2984 -0
- data/lib/javascript/clean-css/test/data/big.css +13794 -0
- data/lib/javascript/clean-css/test/data/blueprint-min.css +245 -0
- data/lib/javascript/clean-css/test/data/blueprint.css +556 -0
- data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts-min.css +3 -0
- data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts.css +14 -0
- data/lib/javascript/clean-css/test/data/font-awesome-ie7-min.css +392 -0
- data/lib/javascript/clean-css/test/data/font-awesome-ie7.css +1203 -0
- data/lib/javascript/clean-css/test/data/font-awesome-min.css +319 -0
- data/lib/javascript/clean-css/test/data/font-awesome.css +540 -0
- data/lib/javascript/clean-css/test/data/imports-min.css +5 -0
- data/lib/javascript/clean-css/test/data/imports.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-117-snippet-min.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-117-snippet.css +19 -0
- data/lib/javascript/clean-css/test/data/issue-159-snippet-min.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-159-snippet.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-192-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-192.css +8 -0
- data/lib/javascript/clean-css/test/data/issue-198-min.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-198.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-232-min.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-232.css +17 -0
- data/lib/javascript/clean-css/test/data/issue-241-min.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-241.css +2 -0
- data/lib/javascript/clean-css/test/data/issue-304-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-304.css +4 -0
- data/lib/javascript/clean-css/test/data/issue-305-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-305.css +3 -0
- data/lib/javascript/clean-css/test/data/issue-308-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-308.css +5 -0
- data/lib/javascript/clean-css/test/data/issue-312-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-312.css +7 -0
- data/lib/javascript/clean-css/test/data/issue-337-min.css +1 -0
- data/lib/javascript/clean-css/test/data/issue-337.css +4 -0
- data/lib/javascript/clean-css/test/data/line-breaks-in-attributes-min.css +2 -0
- data/lib/javascript/clean-css/test/data/line-breaks-in-attributes.css +8 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/base.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/base2.css +1 -0
- data/lib/javascript/clean-css/test/data/partials-absolute/extra/sub.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-relative/base.css +3 -0
- data/lib/javascript/clean-css/test/data/partials-relative/extra/included.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/comment.css +2 -0
- data/lib/javascript/clean-css/test/data/partials/extra/down.gif +0 -0
- data/lib/javascript/clean-css/test/data/partials/extra/four.css +3 -0
- data/lib/javascript/clean-css/test/data/partials/extra/three.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/five.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/four.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/one.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/three.css +1 -0
- data/lib/javascript/clean-css/test/data/partials/two.css +5 -0
- data/lib/javascript/clean-css/test/data/reset-min.css +12 -0
- data/lib/javascript/clean-css/test/data/reset.css +64 -0
- data/lib/javascript/clean-css/test/data/sample1-min.css +4 -0
- data/lib/javascript/clean-css/test/data/sample1.css +12 -0
- data/lib/javascript/clean-css/test/data/unsupported/selectors-ie7.css +20 -0
- data/lib/javascript/clean-css/test/data/unsupported/selectors-ie8.css +17 -0
- data/lib/javascript/clean-css/test/module-test.js +185 -0
- data/lib/javascript/clean-css/test/protocol-imports-test.js +409 -0
- data/lib/javascript/clean-css/test/text/splitter-test.js +20 -0
- data/lib/javascript/clean-css/test/unit-test.js +2071 -0
- data/lib/ruby-clean-css.rb +6 -0
- data/lib/ruby-clean-css/compressor.rb +142 -0
- data/lib/ruby-clean-css/exports.rb +258 -0
- data/lib/ruby-clean-css/railtie.rb +27 -0
- data/lib/ruby-clean-css/sprockets.rb +19 -0
- data/lib/ruby-clean-css/version.rb +5 -0
- data/ruby-clean-css.gemspec +30 -0
- data/test/test_compressor.rb +84 -0
- metadata +203 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module.exports = function EmptyRemoval(data) {
|
2
|
+
var stripEmpty = function(cssData) {
|
3
|
+
var tempData = [];
|
4
|
+
var nextEmpty = 0;
|
5
|
+
var cursor = 0;
|
6
|
+
|
7
|
+
for (; nextEmpty < cssData.length;) {
|
8
|
+
nextEmpty = cssData.indexOf('{}', cursor);
|
9
|
+
if (nextEmpty == -1)
|
10
|
+
break;
|
11
|
+
|
12
|
+
var startsAt = nextEmpty - 1;
|
13
|
+
while (cssData[startsAt] && cssData[startsAt] != '}' && cssData[startsAt] != '{' && cssData[startsAt] != ';')
|
14
|
+
startsAt--;
|
15
|
+
|
16
|
+
tempData.push(cssData.substring(cursor, startsAt + 1));
|
17
|
+
cursor = nextEmpty + 2;
|
18
|
+
}
|
19
|
+
|
20
|
+
return tempData.length > 0 ?
|
21
|
+
stripEmpty(tempData.join('') + cssData.substring(cursor, cssData.length)) :
|
22
|
+
cssData;
|
23
|
+
};
|
24
|
+
|
25
|
+
return {
|
26
|
+
process: function() {
|
27
|
+
return stripEmpty(data);
|
28
|
+
}
|
29
|
+
};
|
30
|
+
};
|
@@ -0,0 +1,341 @@
|
|
1
|
+
var Tokenizer = require('./tokenizer');
|
2
|
+
var PropertyOptimizer = require('../properties/optimizer');
|
3
|
+
|
4
|
+
module.exports = function Optimizer(data, context, options) {
|
5
|
+
var specialSelectors = {
|
6
|
+
'*': /\-(moz|ms|o|webkit)\-/,
|
7
|
+
'ie8': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/,
|
8
|
+
'ie7': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/
|
9
|
+
};
|
10
|
+
|
11
|
+
var minificationsMade = [];
|
12
|
+
|
13
|
+
var propertyOptimizer = new PropertyOptimizer(options.compatibility, options.aggressiveMerging);
|
14
|
+
|
15
|
+
var cleanUpSelector = function(selectors) {
|
16
|
+
if (selectors.indexOf(',') == -1)
|
17
|
+
return selectors;
|
18
|
+
|
19
|
+
var plain = [];
|
20
|
+
var cursor = 0;
|
21
|
+
var lastComma = 0;
|
22
|
+
var noBrackets = selectors.indexOf('(') == -1;
|
23
|
+
var withinBrackets = function(idx) {
|
24
|
+
if (noBrackets)
|
25
|
+
return false;
|
26
|
+
|
27
|
+
var previousOpening = selectors.lastIndexOf('(', idx);
|
28
|
+
var previousClosing = selectors.lastIndexOf(')', idx);
|
29
|
+
|
30
|
+
if (previousOpening == -1)
|
31
|
+
return false;
|
32
|
+
if (previousClosing > 0 && previousClosing < idx)
|
33
|
+
return false;
|
34
|
+
|
35
|
+
return true;
|
36
|
+
};
|
37
|
+
|
38
|
+
while (true) {
|
39
|
+
var nextComma = selectors.indexOf(',', cursor + 1);
|
40
|
+
var selector;
|
41
|
+
|
42
|
+
if (nextComma === -1) {
|
43
|
+
nextComma = selectors.length;
|
44
|
+
} else if (withinBrackets(nextComma)) {
|
45
|
+
cursor = nextComma + 1;
|
46
|
+
continue;
|
47
|
+
}
|
48
|
+
selector = selectors.substring(lastComma, nextComma);
|
49
|
+
lastComma = cursor = nextComma + 1;
|
50
|
+
|
51
|
+
if (plain.indexOf(selector) == -1)
|
52
|
+
plain.push(selector);
|
53
|
+
|
54
|
+
if (nextComma === selectors.length)
|
55
|
+
break;
|
56
|
+
}
|
57
|
+
|
58
|
+
return plain.sort().join(',');
|
59
|
+
};
|
60
|
+
|
61
|
+
var isSpecial = function(selector) {
|
62
|
+
return specialSelectors[options.compatibility || '*'].test(selector);
|
63
|
+
};
|
64
|
+
|
65
|
+
var removeDuplicates = function(tokens) {
|
66
|
+
var matched = {};
|
67
|
+
var forRemoval = [];
|
68
|
+
|
69
|
+
for (var i = 0, l = tokens.length; i < l; i++) {
|
70
|
+
var token = tokens[i];
|
71
|
+
if (typeof token == 'string' || token.block)
|
72
|
+
continue;
|
73
|
+
|
74
|
+
var id = token.body + '@' + token.selector;
|
75
|
+
var alreadyMatched = matched[id];
|
76
|
+
|
77
|
+
if (alreadyMatched) {
|
78
|
+
forRemoval.push(alreadyMatched[0]);
|
79
|
+
alreadyMatched.unshift(i);
|
80
|
+
} else {
|
81
|
+
matched[id] = [i];
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
forRemoval = forRemoval.sort(function(a, b) {
|
86
|
+
return a > b ? 1 : -1;
|
87
|
+
});
|
88
|
+
|
89
|
+
for (var j = 0, n = forRemoval.length; j < n; j++) {
|
90
|
+
tokens.splice(forRemoval[j] - j, 1);
|
91
|
+
}
|
92
|
+
|
93
|
+
minificationsMade.unshift(forRemoval.length > 0);
|
94
|
+
};
|
95
|
+
|
96
|
+
var mergeAdjacent = function(tokens) {
|
97
|
+
var forRemoval = [];
|
98
|
+
var lastToken = { selector: null, body: null };
|
99
|
+
|
100
|
+
for (var i = 0, l = tokens.length; i < l; i++) {
|
101
|
+
var token = tokens[i];
|
102
|
+
|
103
|
+
if (typeof token == 'string' || token.block)
|
104
|
+
continue;
|
105
|
+
|
106
|
+
if (token.selector == lastToken.selector) {
|
107
|
+
var joinAt = [lastToken.body.split(';').length];
|
108
|
+
lastToken.body = propertyOptimizer.process(lastToken.body + ';' + token.body, joinAt);
|
109
|
+
forRemoval.push(i);
|
110
|
+
} else if (token.body == lastToken.body && !isSpecial(token.selector) && !isSpecial(lastToken.selector)) {
|
111
|
+
lastToken.selector = cleanUpSelector(lastToken.selector + ',' + token.selector);
|
112
|
+
forRemoval.push(i);
|
113
|
+
} else {
|
114
|
+
lastToken = token;
|
115
|
+
}
|
116
|
+
}
|
117
|
+
|
118
|
+
for (var j = 0, m = forRemoval.length; j < m; j++) {
|
119
|
+
tokens.splice(forRemoval[j] - j, 1);
|
120
|
+
}
|
121
|
+
|
122
|
+
minificationsMade.unshift(forRemoval.length > 0);
|
123
|
+
};
|
124
|
+
|
125
|
+
var reduceNonAdjacent = function(tokens) {
|
126
|
+
var candidates = {};
|
127
|
+
var moreThanOnce = [];
|
128
|
+
|
129
|
+
for (var i = tokens.length - 1; i >= 0; i--) {
|
130
|
+
var token = tokens[i];
|
131
|
+
|
132
|
+
if (typeof token == 'string' || token.block)
|
133
|
+
continue;
|
134
|
+
|
135
|
+
var complexSelector = token.selector;
|
136
|
+
var selectors = complexSelector.indexOf(',') > -1 && !isSpecial(complexSelector) ?
|
137
|
+
complexSelector.split(',').concat(complexSelector) : // simplification, as :not() can have commas too
|
138
|
+
[complexSelector];
|
139
|
+
|
140
|
+
for (var j = 0, m = selectors.length; j < m; j++) {
|
141
|
+
var selector = selectors[j];
|
142
|
+
|
143
|
+
if (!candidates[selector])
|
144
|
+
candidates[selector] = [];
|
145
|
+
else
|
146
|
+
moreThanOnce.push(selector);
|
147
|
+
|
148
|
+
candidates[selector].push({
|
149
|
+
where: i,
|
150
|
+
partial: selector != complexSelector
|
151
|
+
});
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
var reducedInSimple = _reduceSimpleNonAdjacentCases(tokens, moreThanOnce, candidates);
|
156
|
+
var reducedInComplex = _reduceComplexNonAdjacentCases(tokens, candidates);
|
157
|
+
|
158
|
+
minificationsMade.unshift(reducedInSimple || reducedInComplex);
|
159
|
+
};
|
160
|
+
|
161
|
+
var _reduceSimpleNonAdjacentCases = function(tokens, matches, positions) {
|
162
|
+
var reduced = false;
|
163
|
+
|
164
|
+
for (var i = 0, l = matches.length; i < l; i++) {
|
165
|
+
var selector = matches[i];
|
166
|
+
var data = positions[selector];
|
167
|
+
|
168
|
+
if (data.length < 2)
|
169
|
+
continue;
|
170
|
+
|
171
|
+
/* jshint loopfunc: true */
|
172
|
+
_reduceSelector(tokens, selector, data, {
|
173
|
+
filterOut: function(idx, bodies) {
|
174
|
+
return data[idx].partial && bodies.length === 0;
|
175
|
+
},
|
176
|
+
callback: function(token, newBody, processedCount, tokenIdx) {
|
177
|
+
if (!data[processedCount - tokenIdx - 1].partial) {
|
178
|
+
token.body = newBody.join(';');
|
179
|
+
reduced = true;
|
180
|
+
}
|
181
|
+
}
|
182
|
+
});
|
183
|
+
}
|
184
|
+
|
185
|
+
return reduced;
|
186
|
+
};
|
187
|
+
|
188
|
+
var _reduceComplexNonAdjacentCases = function(tokens, positions) {
|
189
|
+
var reduced = false;
|
190
|
+
|
191
|
+
allSelectors:
|
192
|
+
for (var complexSelector in positions) {
|
193
|
+
if (complexSelector.indexOf(',') == -1) // simplification, as :not() can have commas too
|
194
|
+
continue;
|
195
|
+
|
196
|
+
var intoPosition = positions[complexSelector].pop().where;
|
197
|
+
var intoToken = tokens[intoPosition];
|
198
|
+
|
199
|
+
var selectors = isSpecial(complexSelector) ?
|
200
|
+
[complexSelector] :
|
201
|
+
complexSelector.split(',');
|
202
|
+
var reducedBodies = [];
|
203
|
+
|
204
|
+
for (var j = 0, m = selectors.length; j < m; j++) {
|
205
|
+
var selector = selectors[j];
|
206
|
+
var data = positions[selector];
|
207
|
+
|
208
|
+
if (data.length < 2)
|
209
|
+
continue allSelectors;
|
210
|
+
|
211
|
+
/* jshint loopfunc: true */
|
212
|
+
_reduceSelector(tokens, selector, data, {
|
213
|
+
filterOut: function(idx) {
|
214
|
+
return data[idx].where < intoPosition;
|
215
|
+
},
|
216
|
+
callback: function(token, newBody, processedCount, tokenIdx) {
|
217
|
+
if (tokenIdx === 0)
|
218
|
+
reducedBodies.push(newBody.join(';'));
|
219
|
+
}
|
220
|
+
});
|
221
|
+
|
222
|
+
if (reducedBodies[reducedBodies.length - 1] != reducedBodies[0])
|
223
|
+
continue allSelectors;
|
224
|
+
}
|
225
|
+
|
226
|
+
intoToken.body = reducedBodies[0];
|
227
|
+
reduced = true;
|
228
|
+
}
|
229
|
+
|
230
|
+
return reduced;
|
231
|
+
};
|
232
|
+
|
233
|
+
var _reduceSelector = function(tokens, selector, data, options) {
|
234
|
+
var bodies = [];
|
235
|
+
var joinsAt = [];
|
236
|
+
var splitBodies = [];
|
237
|
+
var processedTokens = [];
|
238
|
+
|
239
|
+
for (var j = data.length - 1, m = 0; j >= 0; j--) {
|
240
|
+
if (options.filterOut(j, bodies))
|
241
|
+
continue;
|
242
|
+
|
243
|
+
var where = data[j].where;
|
244
|
+
var token = tokens[where];
|
245
|
+
var body = token.body;
|
246
|
+
bodies.push(body);
|
247
|
+
splitBodies.push(body.split(';'));
|
248
|
+
processedTokens.push(where);
|
249
|
+
}
|
250
|
+
|
251
|
+
for (j = 0, m = bodies.length; j < m; j++) {
|
252
|
+
if (bodies[j].length > 0)
|
253
|
+
joinsAt.push((joinsAt[j - 1] || 0) + splitBodies[j].length);
|
254
|
+
}
|
255
|
+
|
256
|
+
var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt, true);
|
257
|
+
var optimizedProperties = optimizedBody.split(';');
|
258
|
+
|
259
|
+
var processedCount = processedTokens.length;
|
260
|
+
var propertyIdx = optimizedProperties.length - 1;
|
261
|
+
var tokenIdx = processedCount - 1;
|
262
|
+
|
263
|
+
while (tokenIdx >= 0) {
|
264
|
+
if ((tokenIdx === 0 || splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) {
|
265
|
+
propertyIdx--;
|
266
|
+
continue;
|
267
|
+
}
|
268
|
+
|
269
|
+
var newBody = optimizedProperties.splice(propertyIdx + 1);
|
270
|
+
options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
|
271
|
+
|
272
|
+
tokenIdx--;
|
273
|
+
}
|
274
|
+
};
|
275
|
+
|
276
|
+
var optimize = function(tokens) {
|
277
|
+
var noChanges = function() {
|
278
|
+
return minificationsMade.length > 4 &&
|
279
|
+
minificationsMade[0] === false &&
|
280
|
+
minificationsMade[1] === false;
|
281
|
+
};
|
282
|
+
|
283
|
+
tokens = Array.isArray(tokens) ? tokens : [tokens];
|
284
|
+
for (var i = 0, l = tokens.length; i < l; i++) {
|
285
|
+
var token = tokens[i];
|
286
|
+
|
287
|
+
if (token.selector) {
|
288
|
+
token.selector = cleanUpSelector(token.selector);
|
289
|
+
token.body = propertyOptimizer.process(token.body, false);
|
290
|
+
} else if (token.block) {
|
291
|
+
optimize(token.body);
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
// Run until 2 last operations do not yield any changes
|
296
|
+
minificationsMade = [];
|
297
|
+
while (true) {
|
298
|
+
if (noChanges())
|
299
|
+
break;
|
300
|
+
removeDuplicates(tokens);
|
301
|
+
|
302
|
+
if (noChanges())
|
303
|
+
break;
|
304
|
+
mergeAdjacent(tokens);
|
305
|
+
|
306
|
+
if (noChanges())
|
307
|
+
break;
|
308
|
+
reduceNonAdjacent(tokens);
|
309
|
+
}
|
310
|
+
};
|
311
|
+
|
312
|
+
var rebuild = function(tokens) {
|
313
|
+
var rebuilt = [];
|
314
|
+
|
315
|
+
tokens = Array.isArray(tokens) ? tokens : [tokens];
|
316
|
+
for (var i = 0, l = tokens.length; i < l; i++) {
|
317
|
+
var token = tokens[i];
|
318
|
+
|
319
|
+
if (typeof token == 'string') {
|
320
|
+
rebuilt.push(token);
|
321
|
+
continue;
|
322
|
+
}
|
323
|
+
|
324
|
+
var name = token.block || token.selector;
|
325
|
+
var body = token.block ? rebuild(token.body) : token.body;
|
326
|
+
|
327
|
+
if (body.length > 0)
|
328
|
+
rebuilt.push(name + '{' + body + '}');
|
329
|
+
}
|
330
|
+
|
331
|
+
return rebuilt.join(options.keepBreaks ? options.lineBreak : '');
|
332
|
+
};
|
333
|
+
|
334
|
+
return {
|
335
|
+
process: function() {
|
336
|
+
var tokenized = new Tokenizer(data, context).process();
|
337
|
+
optimize(tokenized);
|
338
|
+
return rebuild(tokenized);
|
339
|
+
}
|
340
|
+
};
|
341
|
+
};
|
@@ -0,0 +1,168 @@
|
|
1
|
+
module.exports = function Tokenizer(data, minifyContext) {
|
2
|
+
var chunker = new Chunker(data, 128);
|
3
|
+
var chunk = chunker.next();
|
4
|
+
var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)|\\@.+?)/;
|
5
|
+
|
6
|
+
var whatsNext = function(context) {
|
7
|
+
var cursor = context.cursor;
|
8
|
+
var mode = context.mode;
|
9
|
+
var closest;
|
10
|
+
|
11
|
+
if (chunk.length == context.cursor) {
|
12
|
+
if (chunker.isEmpty())
|
13
|
+
return null;
|
14
|
+
|
15
|
+
chunk = chunker.next();
|
16
|
+
context.cursor = 0;
|
17
|
+
}
|
18
|
+
|
19
|
+
if (mode == 'body') {
|
20
|
+
closest = chunk.indexOf('}', cursor);
|
21
|
+
return closest > -1 ?
|
22
|
+
[closest, 'bodyEnd'] :
|
23
|
+
null;
|
24
|
+
}
|
25
|
+
|
26
|
+
var nextSpecial = chunk.indexOf('@', context.cursor);
|
27
|
+
var nextEscape = mode == 'top' ? chunk.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', context.cursor) : -1;
|
28
|
+
var nextBodyStart = chunk.indexOf('{', context.cursor);
|
29
|
+
var nextBodyEnd = chunk.indexOf('}', context.cursor);
|
30
|
+
|
31
|
+
closest = nextSpecial;
|
32
|
+
if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
|
33
|
+
closest = nextEscape;
|
34
|
+
if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
|
35
|
+
closest = nextBodyStart;
|
36
|
+
if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
|
37
|
+
closest = nextBodyEnd;
|
38
|
+
|
39
|
+
if (closest == -1)
|
40
|
+
return;
|
41
|
+
if (nextEscape === closest)
|
42
|
+
return [closest, 'escape'];
|
43
|
+
if (nextBodyStart === closest)
|
44
|
+
return [closest, 'bodyStart'];
|
45
|
+
if (nextBodyEnd === closest)
|
46
|
+
return [closest, 'bodyEnd'];
|
47
|
+
if (nextSpecial === closest)
|
48
|
+
return [closest, 'special'];
|
49
|
+
};
|
50
|
+
|
51
|
+
var tokenize = function(context) {
|
52
|
+
var tokenized = [];
|
53
|
+
|
54
|
+
context = context || { cursor: 0, mode: 'top' };
|
55
|
+
|
56
|
+
while (true) {
|
57
|
+
var next = whatsNext(context);
|
58
|
+
if (!next) {
|
59
|
+
var whatsLeft = chunk.substring(context.cursor);
|
60
|
+
if (whatsLeft.length > 0) {
|
61
|
+
tokenized.push(whatsLeft);
|
62
|
+
context.cursor += whatsLeft.length;
|
63
|
+
}
|
64
|
+
break;
|
65
|
+
}
|
66
|
+
|
67
|
+
var nextSpecial = next[0];
|
68
|
+
var what = next[1];
|
69
|
+
var nextEnd;
|
70
|
+
var oldMode;
|
71
|
+
|
72
|
+
if (what == 'special') {
|
73
|
+
var firstOpenBraceAt = chunk.indexOf('{', nextSpecial);
|
74
|
+
var firstSemicolonAt = chunk.indexOf(';', nextSpecial);
|
75
|
+
var isSingle = firstSemicolonAt > -1 && (firstOpenBraceAt == -1 || firstSemicolonAt < firstOpenBraceAt);
|
76
|
+
if (isSingle) {
|
77
|
+
nextEnd = chunk.indexOf(';', nextSpecial + 1);
|
78
|
+
tokenized.push(chunk.substring(context.cursor, nextEnd + 1));
|
79
|
+
|
80
|
+
context.cursor = nextEnd + 1;
|
81
|
+
} else {
|
82
|
+
nextEnd = chunk.indexOf('{', nextSpecial + 1);
|
83
|
+
var block = chunk.substring(context.cursor, nextEnd).trim();
|
84
|
+
|
85
|
+
var isFlat = flatBlock.test(block);
|
86
|
+
oldMode = context.mode;
|
87
|
+
context.cursor = nextEnd + 1;
|
88
|
+
context.mode = isFlat ? 'body' : 'block';
|
89
|
+
var specialBody = tokenize(context);
|
90
|
+
context.mode = oldMode;
|
91
|
+
|
92
|
+
tokenized.push({ block: block, body: specialBody });
|
93
|
+
}
|
94
|
+
} else if (what == 'escape') {
|
95
|
+
nextEnd = chunk.indexOf('__', nextSpecial + 1);
|
96
|
+
var escaped = chunk.substring(context.cursor, nextEnd + 2);
|
97
|
+
tokenized.push(escaped);
|
98
|
+
|
99
|
+
context.cursor = nextEnd + 2;
|
100
|
+
} else if (what == 'bodyStart') {
|
101
|
+
var selector = chunk.substring(context.cursor, nextSpecial).trim();
|
102
|
+
|
103
|
+
oldMode = context.mode;
|
104
|
+
context.cursor = nextSpecial + 1;
|
105
|
+
context.mode = 'body';
|
106
|
+
var body = tokenize(context);
|
107
|
+
context.mode = oldMode;
|
108
|
+
|
109
|
+
tokenized.push({ selector: selector, body: body });
|
110
|
+
} else if (what == 'bodyEnd') {
|
111
|
+
// extra closing brace at the top level can be safely ignored
|
112
|
+
if (context.mode == 'top') {
|
113
|
+
var at = context.cursor;
|
114
|
+
var warning = chunk[context.cursor] == '}' ?
|
115
|
+
'Unexpected \'}\' in \'' + chunk.substring(at - 20, at + 20) + '\'. Ignoring.' :
|
116
|
+
'Unexpected content: \'' + chunk.substring(at, nextSpecial + 1) + '\'. Ignoring.';
|
117
|
+
|
118
|
+
minifyContext.warnings.push(warning);
|
119
|
+
context.cursor = nextSpecial + 1;
|
120
|
+
continue;
|
121
|
+
}
|
122
|
+
|
123
|
+
if (context.mode != 'block')
|
124
|
+
tokenized = chunk.substring(context.cursor, nextSpecial);
|
125
|
+
|
126
|
+
context.cursor = nextSpecial + 1;
|
127
|
+
|
128
|
+
break;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
return tokenized;
|
133
|
+
};
|
134
|
+
|
135
|
+
return {
|
136
|
+
process: function() {
|
137
|
+
return tokenize();
|
138
|
+
}
|
139
|
+
};
|
140
|
+
};
|
141
|
+
|
142
|
+
// Divides `data` into chunks of `chunkSize` for faster processing
|
143
|
+
var Chunker = function(data, chunkSize) {
|
144
|
+
var chunks = [];
|
145
|
+
for (var cursor = 0, dataSize = data.length; cursor < dataSize;) {
|
146
|
+
var nextCursor = cursor + chunkSize > dataSize ?
|
147
|
+
dataSize - 1 :
|
148
|
+
cursor + chunkSize;
|
149
|
+
|
150
|
+
if (data[nextCursor] != '}')
|
151
|
+
nextCursor = data.indexOf('}', nextCursor);
|
152
|
+
if (nextCursor == -1)
|
153
|
+
nextCursor = data.length - 1;
|
154
|
+
|
155
|
+
chunks.push(data.substring(cursor, nextCursor + 1));
|
156
|
+
cursor = nextCursor + 1;
|
157
|
+
}
|
158
|
+
|
159
|
+
return {
|
160
|
+
isEmpty: function() {
|
161
|
+
return chunks.length === 0;
|
162
|
+
},
|
163
|
+
|
164
|
+
next: function() {
|
165
|
+
return chunks.shift() || '';
|
166
|
+
}
|
167
|
+
};
|
168
|
+
};
|