html-minifier 0.7.2 → 1.1.1
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/README.md +38 -15
- package/cli.js +147 -23
- package/dist/htmlminifier.js +59 -55
- package/dist/htmlminifier.min.js +4 -4
- package/package.json +18 -21
- package/src/htmllint.js +168 -0
- package/src/htmlminifier.js +838 -0
- package/src/htmlparser.js +560 -0
|
@@ -0,0 +1,838 @@
|
|
|
1
|
+
/* global CleanCSS */
|
|
2
|
+
|
|
3
|
+
(function(global) {
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
var log, HTMLParser;
|
|
7
|
+
if (global.console && global.console.log) {
|
|
8
|
+
log = function(message) {
|
|
9
|
+
// "preserving" `this`
|
|
10
|
+
global.console.log(message);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
log = function() {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (global.HTMLParser) {
|
|
18
|
+
HTMLParser = global.HTMLParser;
|
|
19
|
+
}
|
|
20
|
+
else if (typeof require === 'function') {
|
|
21
|
+
HTMLParser = require('./htmlparser').HTMLParser;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var trimWhitespace = function(str) {
|
|
25
|
+
if (typeof str !== 'string') {
|
|
26
|
+
return str;
|
|
27
|
+
}
|
|
28
|
+
return str.replace(/^\s+/, '').replace(/\s+$/, '');
|
|
29
|
+
};
|
|
30
|
+
if (String.prototype.trim) {
|
|
31
|
+
trimWhitespace = function(str) {
|
|
32
|
+
if (typeof str !== 'string') {
|
|
33
|
+
return str;
|
|
34
|
+
}
|
|
35
|
+
return str.trim();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function collapseWhitespace(str) {
|
|
40
|
+
return str ? str.replace(/[\t\n\r ]+/g, ' ') : str;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
|
|
44
|
+
|
|
45
|
+
// array of non-empty element tags that will maintain a single space outside of them
|
|
46
|
+
var tags = [
|
|
47
|
+
'a', 'abbr', 'acronym', 'b', 'bdi', 'bdo', 'big', 'button', 'cite',
|
|
48
|
+
'code', 'del', 'dfn', 'em', 'font', 'i', 'ins', 'kbd', 'mark', 'q',
|
|
49
|
+
'rt', 'rp', 's', 'samp', 'small', 'span', 'strike', 'strong',
|
|
50
|
+
'sub', 'sup', 'svg', 'time', 'tt', 'u', 'var'
|
|
51
|
+
],
|
|
52
|
+
lineBreakBefore = /^[\t ]*[\n\r]+[\t\n\r ]*/,
|
|
53
|
+
lineBreakAfter = /[\t\n\r ]*[\n\r]+[\t ]*$/,
|
|
54
|
+
preserveBefore = lineBreakBefore.test(str) ? '\n' : ' ',
|
|
55
|
+
preserveAfter = lineBreakAfter.test(str) ? '\n' : ' ',
|
|
56
|
+
lineBreakStamp = 'htmlmincollapsedlinebreak';
|
|
57
|
+
|
|
58
|
+
if (prevTag && prevTag !== 'img' && prevTag !== 'input' && (prevTag.substr(0, 1) !== '/'
|
|
59
|
+
|| (prevTag.substr(0, 1) === '/' && (options.collapseInlineTagWhitespace || tags.indexOf(prevTag.substr(1)) === -1)))) {
|
|
60
|
+
str = str.replace(/^\s+/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveBefore : '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (nextTag && nextTag !== 'img' && nextTag !== 'input' && (nextTag.substr(0, 1) === '/'
|
|
64
|
+
|| (nextTag.substr(0, 1) !== '/' && (options.collapseInlineTagWhitespace || tags.indexOf(nextTag) === -1)))) {
|
|
65
|
+
str = str.replace(/\s+$/, options.conservativeCollapse ? ' ' : options.preserveLineBreaks ? preserveAfter : '');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (prevTag && nextTag) {
|
|
69
|
+
|
|
70
|
+
if (options.preserveLineBreaks) {
|
|
71
|
+
str = str
|
|
72
|
+
.replace(lineBreakBefore, lineBreakStamp)
|
|
73
|
+
.replace(lineBreakAfter, lineBreakStamp);
|
|
74
|
+
}
|
|
75
|
+
// strip non space whitespace then compress spaces to one
|
|
76
|
+
return str
|
|
77
|
+
.replace(/[\t\n\r]+/g, ' ').replace(/[ ]+/g, ' ')
|
|
78
|
+
.replace(new RegExp(lineBreakStamp, 'g'), '\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return str;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isConditionalComment(text) {
|
|
85
|
+
return ((/\[if[^\]]+\]/).test(text) || (/\s*((?:<!)?\[endif\])$/).test(text));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isIgnoredComment(text, options) {
|
|
89
|
+
if ((/^!/).test(text) || /\s*htmlmin:temp/.test(text)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (options.ignoreCustomComments) {
|
|
94
|
+
for (var i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
|
|
95
|
+
if (options.ignoreCustomComments[i].test(text)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isEventAttribute(attrName) {
|
|
105
|
+
return (/^on[a-z]+/).test(attrName);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function canRemoveAttributeQuotes(value, isLast) {
|
|
109
|
+
// http://mathiasbynens.be/notes/unquoted-attribute-values
|
|
110
|
+
return (/^[^\x20\t\n\f\r"'`=<>]+$/).test(value) &&
|
|
111
|
+
// make sure trailing slash is not interpreted as HTML self-closing tag
|
|
112
|
+
!(isLast && (/\/$/).test(value));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function attributesInclude(attributes, attribute) {
|
|
116
|
+
for (var i = attributes.length; i--;) {
|
|
117
|
+
if (attributes[i].name.toLowerCase() === attribute) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isAttributeRedundant(tag, attrName, attrValue, attrs) {
|
|
125
|
+
attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
(tag === 'script' &&
|
|
129
|
+
attrName === 'language' &&
|
|
130
|
+
attrValue === 'javascript') ||
|
|
131
|
+
|
|
132
|
+
(tag === 'form' &&
|
|
133
|
+
attrName === 'method' &&
|
|
134
|
+
attrValue === 'get') ||
|
|
135
|
+
|
|
136
|
+
(tag === 'input' &&
|
|
137
|
+
attrName === 'type' &&
|
|
138
|
+
attrValue === 'text') ||
|
|
139
|
+
|
|
140
|
+
(tag === 'script' &&
|
|
141
|
+
attrName === 'charset' &&
|
|
142
|
+
!attributesInclude(attrs, 'src')) ||
|
|
143
|
+
|
|
144
|
+
(tag === 'a' &&
|
|
145
|
+
attrName === 'name' &&
|
|
146
|
+
attributesInclude(attrs, 'id')) ||
|
|
147
|
+
|
|
148
|
+
(tag === 'area' &&
|
|
149
|
+
attrName === 'shape' &&
|
|
150
|
+
attrValue === 'rect')
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function isScriptTypeAttribute(tag, attrName, attrValue) {
|
|
155
|
+
return (
|
|
156
|
+
tag === 'script' &&
|
|
157
|
+
attrName === 'type' &&
|
|
158
|
+
trimWhitespace(attrValue.toLowerCase()) === 'text/javascript'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// https://mathiasbynens.be/demo/javascript-mime-type
|
|
163
|
+
// https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
|
|
164
|
+
var executableScriptsMimetypes = {
|
|
165
|
+
'text/javascript': 1,
|
|
166
|
+
'text/ecmascript': 1,
|
|
167
|
+
'text/jscript': 1,
|
|
168
|
+
'application/javascript': 1,
|
|
169
|
+
'application/x-javascript': 1,
|
|
170
|
+
'application/ecmascript': 1
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
function isExecutableScript(tag, attrs) {
|
|
174
|
+
if (tag !== 'script') {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
178
|
+
var attrName = attrs[i].name.toLowerCase();
|
|
179
|
+
if (attrName === 'type') {
|
|
180
|
+
return attrs[i].value === '' ||
|
|
181
|
+
executableScriptsMimetypes[attrs[i].value] === 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isStyleLinkTypeAttribute(tag, attrName, attrValue) {
|
|
188
|
+
return (
|
|
189
|
+
(tag === 'style' || tag === 'link') &&
|
|
190
|
+
attrName === 'type' &&
|
|
191
|
+
trimWhitespace(attrValue.toLowerCase()) === 'text/css'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
var enumeratedAttributeValues = {
|
|
196
|
+
draggable: ['true', 'false'] // defaults to 'auto'
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
function isBooleanAttribute(attrName, attrValue) {
|
|
200
|
+
var isSimpleBoolean = (/^(?:allowfullscreen|async|autofocus|autoplay|checked|compact|controls|declare|default|defaultchecked|defaultmuted|defaultselected|defer|disabled|enabled|formnovalidate|hidden|indeterminate|inert|ismap|itemscope|loop|multiple|muted|nohref|noresize|noshade|novalidate|nowrap|open|pauseonexit|readonly|required|reversed|scoped|seamless|selected|sortable|spellcheck|truespeed|typemustmatch|visible)$/i).test(attrName);
|
|
201
|
+
if (isSimpleBoolean) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
var attrValueEnumeration = enumeratedAttributeValues[attrName.toLowerCase()];
|
|
206
|
+
if (!attrValueEnumeration) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
return (-1 === attrValueEnumeration.indexOf(attrValue.toLowerCase()));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function isUriTypeAttribute(attrName, tag) {
|
|
215
|
+
return (
|
|
216
|
+
((/^(?:a|area|link|base)$/).test(tag) && attrName === 'href') ||
|
|
217
|
+
(tag === 'img' && (/^(?:src|longdesc|usemap)$/).test(attrName)) ||
|
|
218
|
+
(tag === 'object' && (/^(?:classid|codebase|data|usemap)$/).test(attrName)) ||
|
|
219
|
+
(tag === 'q' && attrName === 'cite') ||
|
|
220
|
+
(tag === 'blockquote' && attrName === 'cite') ||
|
|
221
|
+
((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
|
|
222
|
+
(tag === 'form' && attrName === 'action') ||
|
|
223
|
+
(tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
|
|
224
|
+
(tag === 'head' && attrName === 'profile') ||
|
|
225
|
+
(tag === 'script' && (attrName === 'src' || attrName === 'for'))
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isNumberTypeAttribute(attrName, tag) {
|
|
230
|
+
return (
|
|
231
|
+
((/^(?:a|area|object|button)$/).test(tag) && attrName === 'tabindex') ||
|
|
232
|
+
(tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
|
|
233
|
+
(tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
|
|
234
|
+
(tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
|
|
235
|
+
(tag === 'colgroup' && attrName === 'span') ||
|
|
236
|
+
(tag === 'col' && attrName === 'span') ||
|
|
237
|
+
((tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
|
|
242
|
+
if (attrValue && isEventAttribute(attrName)) {
|
|
243
|
+
attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '').replace(/\s*;$/, '');
|
|
244
|
+
if (options.minifyJS) {
|
|
245
|
+
var wrappedCode = '(function(){' + attrValue + '})()';
|
|
246
|
+
var minified = minifyJS(wrappedCode, options.minifyJS);
|
|
247
|
+
return minified.slice(12, minified.length - 4).replace(/"/g, '"');
|
|
248
|
+
}
|
|
249
|
+
return attrValue;
|
|
250
|
+
}
|
|
251
|
+
else if (attrName === 'class') {
|
|
252
|
+
return collapseWhitespace(trimWhitespace(attrValue));
|
|
253
|
+
}
|
|
254
|
+
else if (isUriTypeAttribute(attrName, tag)) {
|
|
255
|
+
attrValue = trimWhitespace(attrValue);
|
|
256
|
+
if (options.minifyURLs) {
|
|
257
|
+
return minifyURLs(attrValue, options.minifyURLs);
|
|
258
|
+
}
|
|
259
|
+
return attrValue;
|
|
260
|
+
}
|
|
261
|
+
else if (isNumberTypeAttribute(attrName, tag)) {
|
|
262
|
+
return trimWhitespace(attrValue);
|
|
263
|
+
}
|
|
264
|
+
else if (attrName === 'style') {
|
|
265
|
+
attrValue = trimWhitespace(attrValue);
|
|
266
|
+
if (attrValue) {
|
|
267
|
+
attrValue = attrValue.replace(/\s*;\s*$/, '');
|
|
268
|
+
}
|
|
269
|
+
if (options.minifyCSS) {
|
|
270
|
+
return minifyCSS(attrValue, options.minifyCSS, true);
|
|
271
|
+
}
|
|
272
|
+
return attrValue;
|
|
273
|
+
}
|
|
274
|
+
else if (isMetaViewport(tag, attrs) && attrName === 'content') {
|
|
275
|
+
attrValue = attrValue.replace(/1\.0/g, '1').replace(/\s+/g, '');
|
|
276
|
+
}
|
|
277
|
+
else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
|
|
278
|
+
attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
|
|
279
|
+
}
|
|
280
|
+
return attrValue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isMetaViewport(tag, attrs) {
|
|
284
|
+
if (tag !== 'meta') {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
288
|
+
if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Wrap CSS declarations for CleanCSS > 3.x
|
|
295
|
+
// See https://github.com/jakubpawlowicz/clean-css/issues/418
|
|
296
|
+
function wrapCSS(text) {
|
|
297
|
+
return '*{' + text + '}';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function unwrapCSS(text) {
|
|
301
|
+
var matches = text.match(/^\*\{([\s\S]*)\}$/m);
|
|
302
|
+
if (matches && matches[1]) {
|
|
303
|
+
return matches[1];
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return text;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function cleanConditionalComment(comment) {
|
|
311
|
+
return comment
|
|
312
|
+
.replace(/^(\[[^\]]+\]>)\s*/, '$1')
|
|
313
|
+
.replace(/\s*(<!\[endif\])$/, '$1');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function removeCDATASections(text) {
|
|
317
|
+
return text
|
|
318
|
+
// "/* <![CDATA[ */" or "// <![CDATA["
|
|
319
|
+
.replace(/^(?:\s*\/\*\s*<!\[CDATA\[\s*\*\/|\s*\/\/\s*<!\[CDATA\[.*)/, '')
|
|
320
|
+
// "/* ]]> */" or "// ]]>"
|
|
321
|
+
.replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/, '');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function processScript(text, options, currentAttrs) {
|
|
325
|
+
for (var i = 0, len = currentAttrs.length; i < len; i++) {
|
|
326
|
+
if (currentAttrs[i].name.toLowerCase() === 'type' &&
|
|
327
|
+
options.processScripts.indexOf(currentAttrs[i].value) > -1) {
|
|
328
|
+
return minify(text, options);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return text;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
var reStartDelimiter = {
|
|
335
|
+
// account for js + html comments (e.g.: //<!--)
|
|
336
|
+
script: /^\s*(?:\/\/)?\s*<!--.*\n?/,
|
|
337
|
+
style: /^\s*<!--\s*/
|
|
338
|
+
};
|
|
339
|
+
var reEndDelimiter = {
|
|
340
|
+
script: /\s*(?:\/\/)?\s*-->\s*$/,
|
|
341
|
+
style: /\s*-->\s*$/
|
|
342
|
+
};
|
|
343
|
+
function removeComments(text, tag) {
|
|
344
|
+
return text.replace(reStartDelimiter[tag], '').replace(reEndDelimiter[tag], '');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function isOptionalTag(tag) {
|
|
348
|
+
return (/^(?:html|t?body|t?head|tfoot|tr|td|th|dt|dd|option|colgroup|source|track)$/).test(tag);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
var reEmptyAttribute = new RegExp(
|
|
352
|
+
'^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
|
|
353
|
+
'?:down|up|over|move|out)|key(?:press|down|up)))$');
|
|
354
|
+
|
|
355
|
+
function canDeleteEmptyAttribute(tag, attrName, attrValue) {
|
|
356
|
+
var isValueEmpty = !attrValue || (/^\s*$/).test(attrValue);
|
|
357
|
+
if (isValueEmpty) {
|
|
358
|
+
return (
|
|
359
|
+
(tag === 'input' && attrName === 'value') ||
|
|
360
|
+
reEmptyAttribute.test(attrName));
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function canRemoveElement(tag, attrs) {
|
|
366
|
+
if (tag === 'textarea') {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (tag === 'script') {
|
|
371
|
+
for (var i = attrs.length - 1; i >= 0; i--) {
|
|
372
|
+
if (attrs[i].name === 'src') {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function canCollapseWhitespace(tag) {
|
|
382
|
+
return !(/^(?:script|style|pre|textarea)$/.test(tag));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function canTrimWhitespace(tag) {
|
|
386
|
+
return !(/^(?:pre|textarea)$/.test(tag));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function normalizeAttribute(attr, attrs, tag, unarySlash, index, options, isLast) {
|
|
390
|
+
|
|
391
|
+
var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
|
|
392
|
+
attrValue = options.preventAttributesEscaping ? attr.value : attr.escaped,
|
|
393
|
+
attrQuote = options.preventAttributesEscaping ? attr.quote : (options.quoteCharacter === '\'' ? '\'' : '"'),
|
|
394
|
+
attrFragment,
|
|
395
|
+
emittedAttrValue,
|
|
396
|
+
isTerminalOfUnarySlash = unarySlash && index === attrs.length - 1;
|
|
397
|
+
|
|
398
|
+
if ((options.removeRedundantAttributes &&
|
|
399
|
+
isAttributeRedundant(tag, attrName, attrValue, attrs))
|
|
400
|
+
||
|
|
401
|
+
(options.removeScriptTypeAttributes &&
|
|
402
|
+
isScriptTypeAttribute(tag, attrName, attrValue))
|
|
403
|
+
||
|
|
404
|
+
(options.removeStyleLinkTypeAttributes &&
|
|
405
|
+
isStyleLinkTypeAttribute(tag, attrName, attrValue))) {
|
|
406
|
+
return '';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
|
|
410
|
+
|
|
411
|
+
if (attrValue !== undefined && !options.removeAttributeQuotes ||
|
|
412
|
+
!canRemoveAttributeQuotes(attrValue, isLast) || isTerminalOfUnarySlash) {
|
|
413
|
+
emittedAttrValue = attrQuote + attrValue + attrQuote;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
emittedAttrValue = attrValue;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (options.removeEmptyAttributes &&
|
|
420
|
+
canDeleteEmptyAttribute(tag, attrName, attrValue)) {
|
|
421
|
+
return '';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (attrValue === undefined || (options.collapseBooleanAttributes &&
|
|
425
|
+
isBooleanAttribute(attrName, attrValue))) {
|
|
426
|
+
attrFragment = attrName;
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
attrFragment = attrName + attr.customAssign + emittedAttrValue;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return (' ' + attr.customOpen + attrFragment + attr.customClose);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function setDefaultTesters(options) {
|
|
436
|
+
|
|
437
|
+
var defaultTesters = ['canCollapseWhitespace', 'canTrimWhitespace'];
|
|
438
|
+
|
|
439
|
+
for (var i = 0, len = defaultTesters.length; i < len; i++) {
|
|
440
|
+
if (!options[defaultTesters[i]]) {
|
|
441
|
+
options[defaultTesters[i]] = function() {
|
|
442
|
+
return false;
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function minifyURLs(text, options) {
|
|
449
|
+
if (typeof options !== 'object') {
|
|
450
|
+
options = { };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
// try to get global reference first
|
|
455
|
+
var __RelateUrl = global.RelateUrl;
|
|
456
|
+
|
|
457
|
+
if (typeof __RelateUrl === 'undefined' && typeof require === 'function') {
|
|
458
|
+
__RelateUrl = require('relateurl');
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// noop
|
|
462
|
+
if (!__RelateUrl) {
|
|
463
|
+
return text;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (__RelateUrl.relate) {
|
|
467
|
+
return __RelateUrl.relate(text, options);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
return text;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
log(err);
|
|
475
|
+
}
|
|
476
|
+
return text;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function minifyJS(text, options) {
|
|
480
|
+
if (typeof options !== 'object') {
|
|
481
|
+
options = { };
|
|
482
|
+
}
|
|
483
|
+
options.fromString = true;
|
|
484
|
+
var outputOptions = options.output || {};
|
|
485
|
+
outputOptions.inline_script = true;
|
|
486
|
+
options.output = outputOptions;
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
// try to get global reference first
|
|
490
|
+
var __UglifyJS = global.UglifyJS;
|
|
491
|
+
|
|
492
|
+
if (typeof __UglifyJS === 'undefined' && typeof require === 'function') {
|
|
493
|
+
__UglifyJS = require('uglify-js');
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// noop
|
|
497
|
+
if (!__UglifyJS) {
|
|
498
|
+
return text;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (__UglifyJS.minify) {
|
|
502
|
+
return __UglifyJS.minify(text, options).code;
|
|
503
|
+
}
|
|
504
|
+
else if (__UglifyJS.parse) {
|
|
505
|
+
|
|
506
|
+
var ast = __UglifyJS.parse(text);
|
|
507
|
+
ast.figure_out_scope();
|
|
508
|
+
|
|
509
|
+
var compressor = __UglifyJS.Compressor();
|
|
510
|
+
var compressedAst = ast.transform(compressor);
|
|
511
|
+
|
|
512
|
+
compressedAst.figure_out_scope();
|
|
513
|
+
compressedAst.compute_char_frequency();
|
|
514
|
+
|
|
515
|
+
if (options.mangle !== false) {
|
|
516
|
+
compressedAst.mangle_names();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
var stream = __UglifyJS.OutputStream(options.output);
|
|
520
|
+
compressedAst.print(stream);
|
|
521
|
+
|
|
522
|
+
return stream.toString();
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
return text;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
log(err);
|
|
530
|
+
}
|
|
531
|
+
return text;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function minifyCSS(text, options, inline) {
|
|
535
|
+
if (typeof options !== 'object') {
|
|
536
|
+
options = { };
|
|
537
|
+
}
|
|
538
|
+
if (typeof options.advanced === 'undefined') {
|
|
539
|
+
options.advanced = false;
|
|
540
|
+
}
|
|
541
|
+
try {
|
|
542
|
+
var cleanCSS;
|
|
543
|
+
|
|
544
|
+
if (typeof CleanCSS !== 'undefined') {
|
|
545
|
+
cleanCSS = new CleanCSS(options);
|
|
546
|
+
}
|
|
547
|
+
else if (typeof require === 'function') {
|
|
548
|
+
var CleanCSSModule = require('clean-css');
|
|
549
|
+
cleanCSS = new CleanCSSModule(options);
|
|
550
|
+
}
|
|
551
|
+
if (inline) {
|
|
552
|
+
return unwrapCSS(cleanCSS.minify(wrapCSS(text)).styles);
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
return cleanCSS.minify(text).styles;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
log(err);
|
|
560
|
+
}
|
|
561
|
+
return text;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function minify(value, options) {
|
|
565
|
+
|
|
566
|
+
options = options || {};
|
|
567
|
+
var optionsStack = [];
|
|
568
|
+
|
|
569
|
+
value = trimWhitespace(value);
|
|
570
|
+
setDefaultTesters(options);
|
|
571
|
+
|
|
572
|
+
var results = [ ],
|
|
573
|
+
buffer = [ ],
|
|
574
|
+
currentChars = '',
|
|
575
|
+
currentTag = '',
|
|
576
|
+
currentAttrs = [],
|
|
577
|
+
stackNoTrimWhitespace = [],
|
|
578
|
+
stackNoCollapseWhitespace = [],
|
|
579
|
+
lint = options.lint,
|
|
580
|
+
t = new Date(),
|
|
581
|
+
ignoredMarkupChunks = [ ],
|
|
582
|
+
ignoredCustomMarkupChunks = [ ],
|
|
583
|
+
reIgnore = /<!-- htmlmin:ignore -->([\s\S]*?)<!-- htmlmin:ignore -->/g,
|
|
584
|
+
uidAttr = ' htmlmin' + (Math.random() + '').slice(2) + ' ',
|
|
585
|
+
reCustomIgnore,
|
|
586
|
+
customFragments;
|
|
587
|
+
|
|
588
|
+
if (options.ignoreCustomFragments) {
|
|
589
|
+
|
|
590
|
+
customFragments = options.ignoreCustomFragments.map(function(re) {
|
|
591
|
+
return re.source;
|
|
592
|
+
});
|
|
593
|
+
reCustomIgnore = new RegExp('(?:' + customFragments.join('|') + ')', 'g');
|
|
594
|
+
|
|
595
|
+
// temporarily replace custom ignored fragments with unique attributes
|
|
596
|
+
value = value.replace(reCustomIgnore, function(match) {
|
|
597
|
+
ignoredCustomMarkupChunks.push(match);
|
|
598
|
+
return uidAttr;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// temporarily replace ignored chunks with comments,
|
|
603
|
+
// so that we don't have to worry what's there.
|
|
604
|
+
// for all we care there might be
|
|
605
|
+
// completely-horribly-broken-alien-non-html-emoj-cthulhu-filled content
|
|
606
|
+
value = value.replace(reIgnore, function(match, group1) {
|
|
607
|
+
ignoredMarkupChunks.push(group1);
|
|
608
|
+
return '<!-- htmlmin:temp -->';
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
function _canCollapseWhitespace(tag, attrs) {
|
|
612
|
+
return canCollapseWhitespace(tag) || options.canCollapseWhitespace(tag, attrs);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function _canTrimWhitespace(tag, attrs) {
|
|
616
|
+
return canTrimWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
new HTMLParser(value, {
|
|
620
|
+
html5: typeof options.html5 !== 'undefined' ? options.html5 : true,
|
|
621
|
+
|
|
622
|
+
start: function(tag, attrs, unary, unarySlash) {
|
|
623
|
+
|
|
624
|
+
var lowerTag = tag.toLowerCase();
|
|
625
|
+
|
|
626
|
+
if (lowerTag === 'svg') {
|
|
627
|
+
optionsStack.push(options);
|
|
628
|
+
var nextOptions = {};
|
|
629
|
+
for (var key in options) {
|
|
630
|
+
nextOptions[key] = options[key];
|
|
631
|
+
}
|
|
632
|
+
nextOptions.keepClosingSlash = true;
|
|
633
|
+
nextOptions.caseSensitive = true;
|
|
634
|
+
options = nextOptions;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
tag = options.caseSensitive ? tag : lowerTag;
|
|
638
|
+
|
|
639
|
+
currentTag = tag;
|
|
640
|
+
currentChars = '';
|
|
641
|
+
currentAttrs = attrs;
|
|
642
|
+
|
|
643
|
+
// set whitespace flags for nested tags (eg. <code> within a <pre>)
|
|
644
|
+
if (options.collapseWhitespace) {
|
|
645
|
+
if (!_canTrimWhitespace(tag, attrs)) {
|
|
646
|
+
stackNoTrimWhitespace.push(tag);
|
|
647
|
+
}
|
|
648
|
+
if (!_canCollapseWhitespace(tag, attrs)) {
|
|
649
|
+
stackNoCollapseWhitespace.push(tag);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
var openTag = '<' + tag;
|
|
654
|
+
var closeTag = ((unarySlash && options.keepClosingSlash) ? '/' : '') + '>';
|
|
655
|
+
if (attrs.length === 0) {
|
|
656
|
+
openTag += closeTag;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
buffer.push(openTag);
|
|
660
|
+
|
|
661
|
+
if (lint) {
|
|
662
|
+
lint.testElement(tag);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
var token, isLast;
|
|
666
|
+
for (var i = 0, len = attrs.length; i < len; i++) {
|
|
667
|
+
isLast = i === len - 1;
|
|
668
|
+
if (lint) {
|
|
669
|
+
lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].escaped);
|
|
670
|
+
}
|
|
671
|
+
token = normalizeAttribute(attrs[i], attrs, tag, unarySlash, i, options, isLast);
|
|
672
|
+
if (isLast) {
|
|
673
|
+
token += closeTag;
|
|
674
|
+
}
|
|
675
|
+
buffer.push(token);
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
end: function(tag, attrs) {
|
|
679
|
+
|
|
680
|
+
var lowerTag = tag.toLowerCase();
|
|
681
|
+
if (lowerTag === 'svg') {
|
|
682
|
+
options = optionsStack.pop();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// check if current tag is in a whitespace stack
|
|
686
|
+
if (options.collapseWhitespace) {
|
|
687
|
+
if (stackNoTrimWhitespace.length &&
|
|
688
|
+
tag === stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
|
|
689
|
+
stackNoTrimWhitespace.pop();
|
|
690
|
+
}
|
|
691
|
+
if (stackNoCollapseWhitespace.length &&
|
|
692
|
+
tag === stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
|
|
693
|
+
stackNoCollapseWhitespace.pop();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
var isElementEmpty = currentChars === '' && tag === currentTag;
|
|
698
|
+
if ((options.removeEmptyElements && isElementEmpty && canRemoveElement(tag, attrs))) {
|
|
699
|
+
// remove last "element" from buffer, return
|
|
700
|
+
for (var i = buffer.length - 1; i >= 0; i--) {
|
|
701
|
+
if (/^<[^\/!]/.test(buffer[i])) {
|
|
702
|
+
buffer.splice(i);
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
else if (options.removeOptionalTags && isOptionalTag(tag)) {
|
|
709
|
+
// noop, leave start tag in buffer
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
// push end tag to buffer
|
|
714
|
+
buffer.push('</' + (options.caseSensitive ? tag : lowerTag) + '>');
|
|
715
|
+
results.push.apply(results, buffer);
|
|
716
|
+
}
|
|
717
|
+
// flush buffer
|
|
718
|
+
buffer.length = 0;
|
|
719
|
+
currentChars = '';
|
|
720
|
+
},
|
|
721
|
+
chars: function(text, prevTag, nextTag) {
|
|
722
|
+
prevTag = prevTag === '' ? 'comment' : prevTag;
|
|
723
|
+
nextTag = nextTag === '' ? 'comment' : nextTag;
|
|
724
|
+
|
|
725
|
+
if (currentTag === 'script' || currentTag === 'style') {
|
|
726
|
+
if (options.removeCommentsFromCDATA) {
|
|
727
|
+
text = removeComments(text, currentTag);
|
|
728
|
+
}
|
|
729
|
+
if (options.removeCDATASectionsFromCDATA) {
|
|
730
|
+
text = removeCDATASections(text);
|
|
731
|
+
}
|
|
732
|
+
if (options.processScripts) {
|
|
733
|
+
text = processScript(text, options, currentAttrs);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (options.minifyJS && isExecutableScript(currentTag, currentAttrs)) {
|
|
737
|
+
text = minifyJS(text, options.minifyJS);
|
|
738
|
+
}
|
|
739
|
+
if (currentTag === 'style' && options.minifyCSS) {
|
|
740
|
+
text = minifyCSS(text, options.minifyCSS);
|
|
741
|
+
}
|
|
742
|
+
if (options.collapseWhitespace) {
|
|
743
|
+
if (!stackNoTrimWhitespace.length) {
|
|
744
|
+
text = ((prevTag && prevTag !== 'comment') || (nextTag && nextTag !== 'comment')) ?
|
|
745
|
+
collapseWhitespaceSmart(text, prevTag, nextTag, options)
|
|
746
|
+
: trimWhitespace(text);
|
|
747
|
+
}
|
|
748
|
+
if (!stackNoCollapseWhitespace.length) {
|
|
749
|
+
text = !(prevTag && nextTag || nextTag === 'html') ? collapseWhitespace(text) : text;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
currentChars = text;
|
|
753
|
+
if (lint) {
|
|
754
|
+
lint.testChars(text);
|
|
755
|
+
}
|
|
756
|
+
buffer.push(text);
|
|
757
|
+
},
|
|
758
|
+
comment: function(text, nonStandard) {
|
|
759
|
+
|
|
760
|
+
var prefix = nonStandard ? '<!' : '<!--';
|
|
761
|
+
var suffix = nonStandard ? '>' : '-->';
|
|
762
|
+
|
|
763
|
+
if (options.removeComments) {
|
|
764
|
+
if (isConditionalComment(text)) {
|
|
765
|
+
text = prefix + cleanConditionalComment(text) + suffix;
|
|
766
|
+
}
|
|
767
|
+
else if (isIgnoredComment(text, options)) {
|
|
768
|
+
text = '<!--' + text + '-->';
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
text = '';
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
text = prefix + text + suffix;
|
|
776
|
+
}
|
|
777
|
+
buffer.push(text);
|
|
778
|
+
},
|
|
779
|
+
doctype: function(doctype) {
|
|
780
|
+
buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespace(doctype));
|
|
781
|
+
},
|
|
782
|
+
customAttrAssign: options.customAttrAssign,
|
|
783
|
+
customAttrSurround: options.customAttrSurround
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
results.push.apply(results, buffer);
|
|
787
|
+
var str = joinResultSegments(results, options);
|
|
788
|
+
|
|
789
|
+
var customIgnoreRe = new RegExp('\\s*' + trimWhitespace(uidAttr) + '\\s*', 'g');
|
|
790
|
+
|
|
791
|
+
str = str.replace(customIgnoreRe, function() {
|
|
792
|
+
return ignoredCustomMarkupChunks.shift();
|
|
793
|
+
});
|
|
794
|
+
str = str.replace(/<!-- htmlmin:temp -->/g, function() {
|
|
795
|
+
return ignoredMarkupChunks.shift();
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
log('minified in: ' + (new Date() - t) + 'ms');
|
|
799
|
+
return str;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function joinResultSegments(results, options) {
|
|
803
|
+
var str;
|
|
804
|
+
var maxLineLength = options.maxLineLength;
|
|
805
|
+
if (maxLineLength) {
|
|
806
|
+
var token;
|
|
807
|
+
var lines = [];
|
|
808
|
+
var line = '';
|
|
809
|
+
for (var i = 0, len = results.length; i < len; i++) {
|
|
810
|
+
token = results[i];
|
|
811
|
+
if (line.length + token.length < maxLineLength) {
|
|
812
|
+
line += token;
|
|
813
|
+
}
|
|
814
|
+
else {
|
|
815
|
+
lines.push(line.replace(/^\n/, ''));
|
|
816
|
+
line = token;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
lines.push(line);
|
|
820
|
+
|
|
821
|
+
str = lines.join('\n');
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
str = results.join('');
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return str;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// for CommonJS enviroments, export everything
|
|
831
|
+
if (typeof exports !== 'undefined') {
|
|
832
|
+
exports.minify = minify;
|
|
833
|
+
}
|
|
834
|
+
else {
|
|
835
|
+
global.minify = minify;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
}(typeof exports === 'undefined' ? this : exports));
|