html-minifier 3.5.15 → 3.5.19

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 CHANGED
@@ -3,14 +3,12 @@
3
3
  [![NPM version](https://img.shields.io/npm/v/html-minifier.svg)](https://www.npmjs.com/package/html-minifier)
4
4
  [![Build Status](https://img.shields.io/travis/kangax/html-minifier.svg)](https://travis-ci.org/kangax/html-minifier)
5
5
  [![Dependency Status](https://img.shields.io/david/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier)
6
- [![devDependency Status](https://img.shields.io/david/dev/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier?type=dev)
7
- [![Gitter](https://img.shields.io/gitter/room/kangax/html-minifier.svg)](https://gitter.im/kangax/html-minifier)
8
6
 
9
- [HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, JavaScript-based HTML minifier.
7
+ [HTMLMinifier](https://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, JavaScript-based HTML minifier.
10
8
 
11
9
  See [corresponding blog post](http://perfectionkills.com/experimenting-with-html-minifier/) for all the gory details of [how it works](http://perfectionkills.com/experimenting-with-html-minifier/#how_it_works), [description of each option](http://perfectionkills.com/experimenting-with-html-minifier/#options), [testing results](http://perfectionkills.com/experimenting-with-html-minifier/#field_testing) and [conclusions](http://perfectionkills.com/experimenting-with-html-minifier/#cost_and_benefits).
12
10
 
13
- [Test suite is available online](http://kangax.github.io/html-minifier/tests/).
11
+ [Test suite is available online](https://kangax.github.io/html-minifier/tests/).
14
12
 
15
13
  Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_minifier), and for Node.js, [Grunt plugin](https://github.com/gruntjs/grunt-contrib-htmlmin), [Gulp module](https://github.com/jonschlinkert/gulp-htmlmin), [Koa middleware wrapper](https://github.com/koajs/html-minifier) and [Express middleware wrapper](https://github.com/melonmanchan/express-minify-html).
16
14
 
@@ -22,19 +20,19 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe
22
20
 
23
21
  | Site | Original size *(KB)* | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
24
22
  | ---------------------------------------------------------------------------- |:--------------------:| ------------:| --------:| ----------:| ------------------:|
25
- | [Google](https://www.google.com/) | 45 | **42** | 45 | 47 | 45 |
26
- | [Twitter](https://twitter.com/) | 122 | **89** | 115 | 134 | 115 |
27
- | [HTMLMinifier](https://github.com/kangax/html-minifier) | 142 | **110** | 119 | 123 | 118 |
28
- | [New York Times](https://www.nytimes.com/) | 221 | **152** | 172 | 168 | 156 |
29
- | [Stack Overflow](https://stackoverflow.com/) | 247 | **193** | 202 | 210 | 200 |
30
- | [Bootstrap CSS](https://getbootstrap.com/docs/3.3/css/) | 272 | **260** | 269 | 229 | 269 |
31
- | [BBC](https://www.bbc.co.uk/) | 283 | **228** | 275 | 278 | 262 |
32
- | [Amazon](https://www.amazon.co.uk/) | 370 | **315** | 356 | 367 | n/a |
33
- | [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 474 | **442** | 458 | 473 | 457 |
34
- | [NBC](https://www.nbc.com/) | 666 | **627** | 663 | 665 | n/a |
23
+ | [Google](https://www.google.com/) | 46 | **42** | 46 | 48 | 46 |
24
+ | [Twitter](https://twitter.com/) | 143 | **107** | 136 | 157 | 136 |
25
+ | [HTMLMinifier](https://github.com/kangax/html-minifier) | 152 | **119** | 127 | 132 | 127 |
26
+ | [New York Times](https://www.nytimes.com/) | 223 | **152** | 173 | 168 | 156 |
27
+ | [Stack Overflow](https://stackoverflow.com/) | 258 | **201** | 211 | 220 | 208 |
28
+ | [Bootstrap CSS](https://getbootstrap.com/docs/3.3/css/) | 271 | **260** | 269 | 229 | 269 |
29
+ | [BBC](https://www.bbc.co.uk/) | 299 | **242** | 290 | 293 | 283 |
30
+ | [Amazon](https://www.amazon.co.uk/) | 398 | **342** | 385 | 397 | n/a |
31
+ | [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 485 | **452** | 470 | 487 | 469 |
32
+ | [NBC](https://www.nbc.com/) | 679 | **639** | 675 | 678 | n/a |
35
33
  | [Eloquent Javascript](https://eloquentjavascript.net/1st_edition/print.html) | 870 | **815** | 840 | 864 | n/a |
36
- | [ES6 table](https://kangax.github.io/compat-table/es6/) | 4916 | **4189** | 4652 | n/a | n/a |
37
- | [ES6 draft](https://tc39.github.io/ecma262/) | 6099 | **5435** | 5597 | n/a | n/a |
34
+ | [ES6 table](https://kangax.github.io/compat-table/es6/) | 5109 | **4353** | 4834 | n/a | n/a |
35
+ | [ES6 draft](https://tc39.github.io/ecma262/) | 6063 | **5443** | 5607 | n/a | n/a |
38
36
 
39
37
  ## Options Quick Reference
40
38
 
package/cli.js CHANGED
@@ -140,7 +140,7 @@ var mainOptionKeys = Object.keys(mainOptions);
140
140
  mainOptionKeys.forEach(function(key) {
141
141
  var option = mainOptions[key];
142
142
  if (Array.isArray(option)) {
143
- key = '--' + paramCase(key);
143
+ key = key === 'minifyURLs' ? '--minify-urls' : '--' + paramCase(key);
144
144
  key += option[1] === parseJSON ? ' [value]' : ' <value>';
145
145
  program.option(key, option[0], option[1]);
146
146
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "html-minifier",
3
3
  "description": "Highly configurable, well-tested, JavaScript-based HTML minifier.",
4
- "version": "3.5.15",
4
+ "version": "3.5.19",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "compress",
@@ -23,11 +23,11 @@
23
23
  "uglifier",
24
24
  "uglify"
25
25
  ],
26
- "homepage": "http://kangax.github.io/html-minifier/",
26
+ "homepage": "https://kangax.github.io/html-minifier/",
27
27
  "author": "Juriy \"kangax\" Zaytsev",
28
28
  "maintainers": [
29
29
  "Alex Lam <alexlamsl@gmail.com>",
30
- "Juriy Zaytsev <kangax@gmail.com> (http://perfectionkills.com)"
30
+ "Juriy Zaytsev <kangax@gmail.com> (http://perfectionkills.com/)"
31
31
  ],
32
32
  "contributors": [
33
33
  "Gilmore Davidson (https://github.com/gilmoreorless)",
@@ -56,11 +56,11 @@
56
56
  "dependencies": {
57
57
  "camel-case": "3.0.x",
58
58
  "clean-css": "4.1.x",
59
- "commander": "2.15.x",
59
+ "commander": "2.16.x",
60
60
  "he": "1.1.x",
61
61
  "param-case": "2.1.x",
62
62
  "relateurl": "0.2.x",
63
- "uglify-js": "3.3.x"
63
+ "uglify-js": "3.4.x"
64
64
  },
65
65
  "devDependencies": {
66
66
  "grunt": "1.0.x",
@@ -72,7 +72,7 @@
72
72
  },
73
73
  "benchmarkDependencies": {
74
74
  "brotli": "1.3.x",
75
- "chalk": "2.3.x",
75
+ "chalk": "2.4.x",
76
76
  "cli-table": "0.3.x",
77
77
  "lzma": "2.3.x",
78
78
  "minimize": "2.2.x",
@@ -9,10 +9,7 @@ var UglifyJS = require('uglify-js');
9
9
  var utils = require('./utils');
10
10
 
11
11
  function trimWhitespace(str) {
12
- if (typeof str !== 'string') {
13
- return str;
14
- }
15
- return str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
12
+ return str && str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
16
13
  }
17
14
 
18
15
  function collapseWhitespaceAll(str) {
@@ -112,7 +109,7 @@ function isEventAttribute(attrName, options) {
112
109
  }
113
110
 
114
111
  function canRemoveAttributeQuotes(value) {
115
- // http://mathiasbynens.be/notes/unquoted-attribute-values
112
+ // https://mathiasbynens.be/notes/unquoted-attribute-values
116
113
  return /^[^ \t\n\f\r"'`=<>]+$/.test(value);
117
114
  }
118
115
 
@@ -258,7 +255,7 @@ function isSrcset(attrName, tag) {
258
255
  }
259
256
 
260
257
  function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
261
- if (attrValue && isEventAttribute(attrName, options)) {
258
+ if (isEventAttribute(attrName, options)) {
262
259
  attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
263
260
  return options.minifyJS(attrValue, true);
264
261
  }
@@ -314,7 +311,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
314
311
  return (+numString).toString();
315
312
  });
316
313
  }
317
- else if (attrValue && options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
314
+ else if (options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
318
315
  attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
319
316
  }
320
317
  else if (tag === 'script' && attrName === 'type') {
@@ -377,7 +374,7 @@ function processScript(text, options, currentAttrs) {
377
374
  // Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
378
375
  // with the following deviations:
379
376
  // - retain <body> if followed by <noscript>
380
- // - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow http://www.w3.org/TR/html5/syntax.html#optional-tags
377
+ // - </rb>, </rt>, </rtc>, </rp> & </tfoot> follow https://www.w3.org/TR/html5/syntax.html#optional-tags
381
378
  // - retain all tags which are adjacent to non-standard HTML tags
382
379
  var optionalStartTags = createMapFromString('html,head,body,colgroup,tbody');
383
380
  var optionalEndTags = createMapFromString('html,head,body,li,dt,dd,p,rb,rt,rtc,rp,optgroup,option,colgroup,caption,thead,tbody,tfoot,tr,td,th');
@@ -522,7 +519,7 @@ function canTrimWhitespace(tag) {
522
519
  }
523
520
 
524
521
  function normalizeAttr(attr, attrs, tag, options) {
525
- var attrName = options.caseSensitive ? attr.name : attr.name.toLowerCase(),
522
+ var attrName = options.name(attr.name),
526
523
  attrValue = attr.value;
527
524
 
528
525
  if (options.decodeEntities && attrValue) {
@@ -538,7 +535,9 @@ function normalizeAttr(attr, attrs, tag, options) {
538
535
  return;
539
536
  }
540
537
 
541
- attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
538
+ if (attrValue) {
539
+ attrValue = cleanAttributeValue(tag, attrName, attrValue, options, attrs);
540
+ }
542
541
 
543
542
  if (options.removeEmptyAttributes &&
544
543
  canDeleteEmptyAttribute(tag, attrName, attrValue, options)) {
@@ -615,6 +614,9 @@ function identity(value) {
615
614
 
616
615
  function processOptions(values) {
617
616
  var options = {
617
+ name: function(name) {
618
+ return name.toLowerCase();
619
+ },
618
620
  canCollapseWhitespace: canCollapseWhitespace,
619
621
  canTrimWhitespace: canTrimWhitespace,
620
622
  html5: true,
@@ -631,7 +633,12 @@ function processOptions(values) {
631
633
  };
632
634
  Object.keys(values).forEach(function(key) {
633
635
  var value = values[key];
634
- if (key === 'log') {
636
+ if (key === 'caseSensitive') {
637
+ if (value) {
638
+ options.name = identity;
639
+ }
640
+ }
641
+ else if (key === 'log') {
635
642
  if (typeof value === 'function') {
636
643
  options.log = value;
637
644
  }
@@ -732,7 +739,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
732
739
 
733
740
  function attrNames(attrs) {
734
741
  return attrs.map(function(attr) {
735
- return options.caseSensitive ? attr.name : attr.name.toLowerCase();
742
+ return options.name(attr.name);
736
743
  });
737
744
  }
738
745
 
@@ -756,7 +763,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
756
763
  }
757
764
  for (var i = 0, len = attrs.length; i < len; i++) {
758
765
  var attr = attrs[i];
759
- if (classChain && (options.caseSensitive ? attr.name : attr.name.toLowerCase()) === 'class') {
766
+ if (classChain && attr.value && options.name(attr.name) === 'class') {
760
767
  classChain.add(trimWhitespace(attr.value).split(/[ \t\n\f\r]+/).filter(shouldSkipUIDs));
761
768
  }
762
769
  else if (options.processScripts && attr.name.toLowerCase() === 'type') {
@@ -946,16 +953,13 @@ function minify(value, options, partialMarkup) {
946
953
  html5: options.html5,
947
954
 
948
955
  start: function(tag, attrs, unary, unarySlash, autoGenerated) {
949
- var lowerTag = tag.toLowerCase();
950
-
951
- if (lowerTag === 'svg') {
956
+ if (tag.toLowerCase() === 'svg') {
952
957
  options = Object.create(options);
953
- options.keepClosingSlash = true;
954
958
  options.caseSensitive = true;
959
+ options.keepClosingSlash = true;
960
+ options.name = identity;
955
961
  }
956
-
957
- tag = options.caseSensitive ? tag : lowerTag;
958
-
962
+ tag = options.name(tag);
959
963
  currentTag = tag;
960
964
  charsPrevTag = tag;
961
965
  if (!inlineTextTags(tag)) {
@@ -1035,11 +1039,10 @@ function minify(value, options, partialMarkup) {
1035
1039
  }
1036
1040
  },
1037
1041
  end: function(tag, attrs, autoGenerated) {
1038
- var lowerTag = tag.toLowerCase();
1039
- if (lowerTag === 'svg') {
1042
+ if (tag.toLowerCase() === 'svg') {
1040
1043
  options = Object.getPrototypeOf(options);
1041
1044
  }
1042
- tag = options.caseSensitive ? tag : lowerTag;
1045
+ tag = options.name(tag);
1043
1046
 
1044
1047
  // check if current tag is in a whitespace stack
1045
1048
  if (options.collapseWhitespace) {
@@ -1217,7 +1220,7 @@ function minify(value, options, partialMarkup) {
1217
1220
  buffer.push(text);
1218
1221
  },
1219
1222
  doctype: function(doctype) {
1220
- buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespaceAll(doctype));
1223
+ buffer.push(options.useShortDoctype ? '<!doctype html>' : collapseWhitespaceAll(doctype));
1221
1224
  },
1222
1225
  customAttrAssign: options.customAttrAssign,
1223
1226
  customAttrSurround: options.customAttrSurround
@@ -1238,10 +1241,8 @@ function minify(value, options, partialMarkup) {
1238
1241
  squashTrailingWhitespace('br');
1239
1242
  }
1240
1243
 
1241
- var str = joinResultSegments(buffer, options);
1242
-
1243
- if (uidPattern) {
1244
- str = str.replace(uidPattern, function(match, prefix, index, suffix) {
1244
+ return joinResultSegments(buffer, options, uidPattern ? function(str) {
1245
+ return str.replace(uidPattern, function(match, prefix, index, suffix) {
1245
1246
  var chunk = ignoredCustomMarkupChunks[+index][0];
1246
1247
  if (options.collapseWhitespace) {
1247
1248
  if (prefix !== '\t') {
@@ -1257,39 +1258,44 @@ function minify(value, options, partialMarkup) {
1257
1258
  }
1258
1259
  return chunk;
1259
1260
  });
1260
- }
1261
- if (uidIgnore) {
1262
- str = str.replace(new RegExp('<!--' + uidIgnore + '([0-9]+)-->', 'g'), function(match, index) {
1261
+ } : identity, uidIgnore ? function(str) {
1262
+ return str.replace(new RegExp('<!--' + uidIgnore + '([0-9]+)-->', 'g'), function(match, index) {
1263
1263
  return ignoredMarkupChunks[+index];
1264
1264
  });
1265
- }
1266
-
1267
- return str;
1265
+ } : identity);
1268
1266
  }
1269
1267
 
1270
- function joinResultSegments(results, options) {
1268
+ function joinResultSegments(results, options, restoreCustom, restoreIgnore) {
1271
1269
  var str;
1272
1270
  var maxLineLength = options.maxLineLength;
1273
1271
  if (maxLineLength) {
1274
- var token;
1275
- var lines = [];
1276
- var line = '';
1277
- for (var i = 0, len = results.length; i < len; i++) {
1278
- token = results[i];
1279
- if (line.length + token.length < maxLineLength) {
1280
- line += token;
1272
+ var line = '', lines = [];
1273
+ while (results.length) {
1274
+ var len = line.length;
1275
+ var end = results[0].indexOf('\n');
1276
+ if (end < 0) {
1277
+ line += restoreIgnore(restoreCustom(results.shift()));
1281
1278
  }
1282
1279
  else {
1283
- lines.push(line.replace(/^\n/, ''));
1284
- line = token;
1280
+ line += restoreIgnore(restoreCustom(results[0].slice(0, end)));
1281
+ results[0] = results[0].slice(end + 1);
1282
+ }
1283
+ if (len > 0 && line.length > maxLineLength) {
1284
+ lines.push(line.slice(0, len));
1285
+ line = line.slice(len);
1286
+ }
1287
+ else if (end >= 0) {
1288
+ lines.push(line);
1289
+ line = '';
1285
1290
  }
1286
1291
  }
1287
- lines.push(line);
1288
-
1292
+ if (line) {
1293
+ lines.push(line);
1294
+ }
1289
1295
  str = lines.join('\n');
1290
1296
  }
1291
1297
  else {
1292
- str = results.join('');
1298
+ str = restoreIgnore(restoreCustom(results.join('')));
1293
1299
  }
1294
1300
  return options.collapseWhitespace ? collapseWhitespace(str, options, true, true) : str;
1295
1301
  }
package/src/htmlparser.js CHANGED
@@ -60,7 +60,7 @@ var singleAttrIdentifier = /([^\s"'<>/=]+)/,
60
60
  startTagOpen = new RegExp('^<' + qnameCapture),
61
61
  startTagClose = /^\s*(\/?)>/,
62
62
  endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>'),
63
- doctype = /^<!DOCTYPE [^>]+>/i;
63
+ doctype = /^<!DOCTYPE\s[^>]+>/i;
64
64
 
65
65
  var IS_REGEX_CAPTURING_BROKEN = false;
66
66
  'x'.replace(/x(.)?/g, function(m, g) {
@@ -140,7 +140,7 @@ function HTMLParser(html, handler) {
140
140
  }
141
141
  }
142
142
 
143
- // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
143
+ // https://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
144
144
  if (/^<!\[/.test(html)) {
145
145
  var conditionalEnd = html.indexOf(']>');
146
146
 
@@ -267,12 +267,36 @@ function HTMLParser(html, handler) {
267
267
  }
268
268
  }
269
269
 
270
+ function closeIfFound(tagName) {
271
+ if (findTag(tagName) >= 0) {
272
+ parseEndTag('', tagName);
273
+ return true;
274
+ }
275
+ }
276
+
270
277
  function handleStartTag(match) {
271
278
  var tagName = match.tagName;
272
279
  var unarySlash = match.unarySlash;
273
280
 
274
- if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
275
- parseEndTag('', lastTag);
281
+ if (handler.html5) {
282
+ if (lastTag === 'p' && nonPhrasing(tagName)) {
283
+ parseEndTag('', lastTag);
284
+ }
285
+ else if (tagName === 'tbody') {
286
+ closeIfFound('thead');
287
+ }
288
+ else if (tagName === 'tfoot') {
289
+ if (!closeIfFound('tbody')) {
290
+ closeIfFound('thead');
291
+ }
292
+ }
293
+ if (tagName === 'col' && findTag('colgroup') < 0) {
294
+ lastTag = 'colgroup';
295
+ stack.push({ tag: lastTag, attrs: [] });
296
+ if (handler.start) {
297
+ handler.start(lastTag, [], false, '');
298
+ }
299
+ }
276
300
  }
277
301
 
278
302
  if (!handler.html5 && !inline(tagName)) {
@@ -353,17 +377,23 @@ function HTMLParser(html, handler) {
353
377
  }
354
378
  }
355
379
 
380
+ function findTag(tagName) {
381
+ var pos;
382
+ var needle = tagName.toLowerCase();
383
+ for (pos = stack.length - 1; pos >= 0; pos--) {
384
+ if (stack[pos].tag.toLowerCase() === needle) {
385
+ break;
386
+ }
387
+ }
388
+ return pos;
389
+ }
390
+
356
391
  function parseEndTag(tag, tagName) {
357
392
  var pos;
358
393
 
359
394
  // Find the closest opened tag of the same type
360
395
  if (tagName) {
361
- var needle = tagName.toLowerCase();
362
- for (pos = stack.length - 1; pos >= 0; pos--) {
363
- if (stack[pos].tag.toLowerCase() === needle) {
364
- break;
365
- }
366
- }
396
+ pos = findTag(tagName);
367
397
  }
368
398
  // If no tag name is provided, clean shop
369
399
  else {