html-minifier 3.5.12 → 3.5.16

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
@@ -22,19 +22,19 @@ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Pe
22
22
 
23
23
  | Site | Original size *(KB)* | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
24
24
  | ---------------------------------------------------------------------------- |:--------------------:| ------------:| --------:| ----------:| ------------------:|
25
- | [Google](https://www.google.com/) | 46 | **42** | 46 | 47 | 46 |
26
- | [HTMLMinifier](https://github.com/kangax/html-minifier) | 137 | **107** | 115 | 120 | 115 |
27
- | [Twitter](https://twitter.com/) | 141 | **103** | 133 | 155 | 133 |
28
- | [New York Times](https://www.nytimes.com/) | 234 | **162** | 183 | 179 | 167 |
29
- | [Stack Overflow](https://stackoverflow.com/) | 254 | **199** | 209 | 218 | 206 |
30
- | [BBC](https://www.bbc.co.uk/) | 271 | **220** | 264 | 267 | 252 |
25
+ | [Google](https://www.google.com/) | 50 | **45** | 49 | 51 | 49 |
26
+ | [Twitter](https://twitter.com/) | 128 | **94** | 121 | 140 | 121 |
27
+ | [HTMLMinifier](https://github.com/kangax/html-minifier) | 144 | **112** | 120 | 125 | 120 |
28
+ | [New York Times](https://www.nytimes.com/) | 212 | **145** | 164 | 159 | 148 |
29
+ | [Stack Overflow](https://stackoverflow.com/) | 249 | **194** | 203 | 212 | 201 |
31
30
  | [Bootstrap CSS](https://getbootstrap.com/docs/3.3/css/) | 272 | **260** | 269 | 229 | 269 |
32
- | [Amazon](https://www.amazon.co.uk/) | 374 | **320** | 360 | 372 | n/a |
33
- | [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 473 | **441** | 457 | 472 | 456 |
34
- | [NBC](https://www.nbc.com/) | 684 | **645** | 680 | 683 | n/a |
31
+ | [BBC](https://www.bbc.co.uk/) | 295 | **237** | 287 | 290 | 272 |
32
+ | [Amazon](https://www.amazon.co.uk/) | 391 | **336** | 377 | 389 | n/a |
33
+ | [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 474 | **442** | 458 | 474 | 458 |
34
+ | [NBC](https://www.nbc.com/) | 689 | **647** | 685 | 687 | n/a |
35
35
  | [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/) | 4799 | **4087** | 4539 | n/a | n/a |
37
- | [ES6 draft](https://tc39.github.io/ecma262/) | 6098 | **5434** | 5596 | n/a | n/a |
36
+ | [ES6 table](https://kangax.github.io/compat-table/es6/) | 5014 | **4273** | 4745 | n/a | n/a |
37
+ | [ES6 draft](https://tc39.github.io/ecma262/) | 6101 | **5437** | 5599 | n/a | n/a |
38
38
 
39
39
  ## Options Quick Reference
40
40
 
@@ -58,7 +58,7 @@ Most of the options are disabled by default.
58
58
  | `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
59
59
  | `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
60
60
  | `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points |
61
- | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `Object`, `Function(text)`) |
61
+ | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `Object`, `Function(text, type)`) |
62
62
  | `minifyJS` | Minify JavaScript in script elements and event attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
63
63
  | `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `String`, `Object`, `Function(text)`) |
64
64
  | `preserveLineBreaks` | Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break. Must be used in conjunction with `collapseWhitespace=true` | `false` |
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.12",
4
+ "version": "3.5.16",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "compress",
@@ -58,14 +58,13 @@
58
58
  "clean-css": "4.1.x",
59
59
  "commander": "2.15.x",
60
60
  "he": "1.1.x",
61
- "ncname": "1.0.x",
62
61
  "param-case": "2.1.x",
63
62
  "relateurl": "0.2.x",
64
63
  "uglify-js": "3.3.x"
65
64
  },
66
65
  "devDependencies": {
67
66
  "grunt": "1.0.x",
68
- "grunt-browserify": "5.2.x",
67
+ "grunt-browserify": "5.3.x",
69
68
  "grunt-contrib-uglify": "3.3.x",
70
69
  "gruntify-eslint": "4.0.x",
71
70
  "phantomjs-prebuilt": "2.1.x",
@@ -73,7 +72,7 @@
73
72
  },
74
73
  "benchmarkDependencies": {
75
74
  "brotli": "1.3.x",
76
- "chalk": "2.3.x",
75
+ "chalk": "2.4.x",
77
76
  "cli-table": "0.3.x",
78
77
  "lzma": "2.3.x",
79
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) {
@@ -67,9 +64,9 @@ function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
67
64
 
68
65
  var createMapFromString = utils.createMapFromString;
69
66
  // non-empty tags that will maintain whitespace around them
70
- var inlineTags = createMapFromString('a,abbr,acronym,b,bdi,bdo,big,button,cite,code,del,dfn,em,font,i,ins,kbd,label,mark,math,nobr,object,q,rt,rp,s,samp,select,small,span,strike,strong,sub,sup,svg,textarea,time,tt,u,var');
67
+ var inlineTags = createMapFromString('a,abbr,acronym,b,bdi,bdo,big,button,cite,code,del,dfn,em,font,i,ins,kbd,label,mark,math,nobr,object,q,rp,rt,rtc,ruby,s,samp,select,small,span,strike,strong,sub,sup,svg,textarea,time,tt,u,var');
71
68
  // non-empty tags that will maintain whitespace within them
72
- var inlineTextTags = createMapFromString('a,abbr,acronym,b,big,del,em,font,i,ins,kbd,mark,nobr,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var');
69
+ var inlineTextTags = createMapFromString('a,abbr,acronym,b,big,del,em,font,i,ins,kbd,mark,nobr,rp,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var');
73
70
  // self-closing tags that will maintain whitespace around them
74
71
  var selfClosingInlineTags = createMapFromString('comment,img,input,wbr');
75
72
 
@@ -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
  }
@@ -285,7 +282,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
285
282
  if (/;$/.test(attrValue) && !/&#?[0-9a-zA-Z]+;$/.test(attrValue)) {
286
283
  attrValue = attrValue.replace(/\s*;$/, ';');
287
284
  }
288
- attrValue = unwrapInlineCSS(options.minifyCSS(wrapInlineCSS(attrValue)));
285
+ attrValue = options.minifyCSS(attrValue, 'inline');
289
286
  }
290
287
  return attrValue;
291
288
  }
@@ -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') {
@@ -322,7 +319,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
322
319
  }
323
320
  else if (isMediaQuery(tag, attrs, attrName)) {
324
321
  attrValue = trimWhitespace(attrValue);
325
- return unwrapMediaQuery(options.minifyCSS(wrapMediaQuery(attrValue)));
322
+ return options.minifyCSS(attrValue, 'media');
326
323
  }
327
324
  return attrValue;
328
325
  }
@@ -395,7 +392,7 @@ var topLevelTags = createMapFromString('html,head,body');
395
392
  var compactTags = createMapFromString('html,body');
396
393
  var looseTags = createMapFromString('head,colgroup,caption');
397
394
  var trailingTags = createMapFromString('dt,thead');
398
- var htmlTags = createMapFromString('a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,element,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,isindex,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,multicol,nav,nobr,noembed,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp');
395
+ var htmlTags = createMapFromString('a,abbr,acronym,address,applet,area,article,aside,audio,b,base,basefont,bdi,bdo,bgsound,big,blink,blockquote,body,br,button,canvas,caption,center,cite,code,col,colgroup,command,content,data,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,element,em,embed,fieldset,figcaption,figure,font,footer,form,frame,frameset,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,i,iframe,image,img,input,ins,isindex,kbd,keygen,label,legend,li,link,listing,main,map,mark,marquee,menu,menuitem,meta,meter,multicol,nav,nobr,noembed,noframes,noscript,object,ol,optgroup,option,output,p,param,picture,plaintext,pre,progress,q,rb,rp,rt,rtc,ruby,s,samp,script,section,select,shadow,small,source,spacer,span,strike,strong,style,sub,summary,sup,table,tbody,td,template,textarea,tfoot,th,thead,time,title,tr,track,tt,u,ul,var,video,wbr,xmp');
399
396
 
400
397
  function canRemoveParentTag(optionalStartTag, tag) {
401
398
  switch (optionalStartTag) {
@@ -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)) {
@@ -613,100 +612,115 @@ function identity(value) {
613
612
  return value;
614
613
  }
615
614
 
616
- function processOptions(options) {
617
- ['html5', 'includeAutoGeneratedTags'].forEach(function(key) {
618
- if (!(key in options)) {
619
- options[key] = true;
620
- }
621
- });
622
-
623
- if (typeof options.log !== 'function') {
624
- options.log = identity;
625
- }
626
-
627
- if (!options.canCollapseWhitespace) {
628
- options.canCollapseWhitespace = canCollapseWhitespace;
629
- }
630
- if (!options.canTrimWhitespace) {
631
- options.canTrimWhitespace = canTrimWhitespace;
632
- }
633
-
634
- if (!('ignoreCustomComments' in options)) {
635
- options.ignoreCustomComments = [/^!/];
636
- }
637
-
638
- if (!('ignoreCustomFragments' in options)) {
639
- options.ignoreCustomFragments = [
615
+ function processOptions(values) {
616
+ var options = {
617
+ name: function(name) {
618
+ return name.toLowerCase();
619
+ },
620
+ canCollapseWhitespace: canCollapseWhitespace,
621
+ canTrimWhitespace: canTrimWhitespace,
622
+ html5: true,
623
+ ignoreCustomComments: [/^!/],
624
+ ignoreCustomFragments: [
640
625
  /<%[\s\S]*?%>/,
641
626
  /<\?[\s\S]*?\?>/
642
- ];
643
- }
644
-
645
- if (!options.minifyURLs) {
646
- options.minifyURLs = identity;
647
- }
648
- if (typeof options.minifyURLs !== 'function') {
649
- var minifyURLs = options.minifyURLs;
650
- if (typeof minifyURLs === 'string') {
651
- minifyURLs = { site: minifyURLs };
627
+ ],
628
+ includeAutoGeneratedTags: true,
629
+ log: identity,
630
+ minifyCSS: identity,
631
+ minifyJS: identity,
632
+ minifyURLs: identity
633
+ };
634
+ Object.keys(values).forEach(function(key) {
635
+ var value = values[key];
636
+ if (key === 'caseSensitive') {
637
+ if (value) {
638
+ options.name = identity;
639
+ }
652
640
  }
653
- else if (typeof minifyURLs !== 'object') {
654
- minifyURLs = {};
641
+ else if (key === 'log') {
642
+ if (typeof value === 'function') {
643
+ options.log = value;
644
+ }
655
645
  }
656
- options.minifyURLs = function(text) {
657
- try {
658
- return RelateUrl.relate(text, minifyURLs);
646
+ else if (key === 'minifyCSS' && typeof value !== 'function') {
647
+ if (!value) {
648
+ return;
659
649
  }
660
- catch (err) {
661
- options.log(err);
662
- return text;
650
+ if (typeof value !== 'object') {
651
+ value = {};
663
652
  }
664
- };
665
- }
666
-
667
- if (!options.minifyJS) {
668
- options.minifyJS = identity;
669
- }
670
- if (typeof options.minifyJS !== 'function') {
671
- var minifyJS = options.minifyJS;
672
- if (typeof minifyJS !== 'object') {
673
- minifyJS = {};
653
+ options.minifyCSS = function(text, type) {
654
+ text = text.replace(/(url\s*\(\s*)("|'|)(.*?)\2(\s*\))/ig, function(match, prefix, quote, url, suffix) {
655
+ return prefix + quote + options.minifyURLs(url) + quote + suffix;
656
+ });
657
+ try {
658
+ if (type === 'inline') {
659
+ text = wrapInlineCSS(text);
660
+ }
661
+ else if (type === 'media') {
662
+ text = wrapMediaQuery(text);
663
+ }
664
+ text = new CleanCSS(value).minify(text).styles;
665
+ if (type === 'inline') {
666
+ text = unwrapInlineCSS(text);
667
+ }
668
+ else if (type === 'media') {
669
+ text = unwrapMediaQuery(text);
670
+ }
671
+ return text;
672
+ }
673
+ catch (err) {
674
+ options.log(err);
675
+ return text;
676
+ }
677
+ };
674
678
  }
675
- (minifyJS.parse || (minifyJS.parse = {})).bare_returns = false;
676
- options.minifyJS = function(text, inline) {
677
- var start = text.match(/^\s*<!--.*/);
678
- var code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
679
- minifyJS.parse.bare_returns = inline;
680
- var result = UglifyJS.minify(code, minifyJS);
681
- if (result.error) {
682
- options.log(result.error);
683
- return text;
679
+ else if (key === 'minifyJS' && typeof value !== 'function') {
680
+ if (!value) {
681
+ return;
684
682
  }
685
- return result.code.replace(/;$/, '');
686
- };
687
- }
688
-
689
- if (!options.minifyCSS) {
690
- options.minifyCSS = identity;
691
- }
692
- if (typeof options.minifyCSS !== 'function') {
693
- var minifyCSS = options.minifyCSS;
694
- if (typeof minifyCSS !== 'object') {
695
- minifyCSS = {};
683
+ if (typeof value !== 'object') {
684
+ value = {};
685
+ }
686
+ (value.parse || (value.parse = {})).bare_returns = false;
687
+ options.minifyJS = function(text, inline) {
688
+ var start = text.match(/^\s*<!--.*/);
689
+ var code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
690
+ value.parse.bare_returns = inline;
691
+ var result = UglifyJS.minify(code, value);
692
+ if (result.error) {
693
+ options.log(result.error);
694
+ return text;
695
+ }
696
+ return result.code.replace(/;$/, '');
697
+ };
696
698
  }
697
- options.minifyCSS = function(text) {
698
- text = text.replace(/(url\s*\(\s*)("|'|)(.*?)\2(\s*\))/ig, function(match, prefix, quote, url, suffix) {
699
- return prefix + quote + options.minifyURLs(url) + quote + suffix;
700
- });
701
- try {
702
- return new CleanCSS(minifyCSS).minify(text).styles;
699
+ else if (key === 'minifyURLs' && typeof value !== 'function') {
700
+ if (!value) {
701
+ return;
703
702
  }
704
- catch (err) {
705
- options.log(err);
706
- return text;
703
+ if (typeof value === 'string') {
704
+ value = { site: value };
707
705
  }
708
- };
709
- }
706
+ else if (typeof value !== 'object') {
707
+ value = {};
708
+ }
709
+ options.minifyURLs = function(text) {
710
+ try {
711
+ return RelateUrl.relate(text, value);
712
+ }
713
+ catch (err) {
714
+ options.log(err);
715
+ return text;
716
+ }
717
+ };
718
+ }
719
+ else {
720
+ options[key] = value;
721
+ }
722
+ });
723
+ return options;
710
724
  }
711
725
 
712
726
  function uniqueId(value) {
@@ -725,7 +739,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
725
739
 
726
740
  function attrNames(attrs) {
727
741
  return attrs.map(function(attr) {
728
- return options.caseSensitive ? attr.name : attr.name.toLowerCase();
742
+ return options.name(attr.name);
729
743
  });
730
744
  }
731
745
 
@@ -749,7 +763,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
749
763
  }
750
764
  for (var i = 0, len = attrs.length; i < len; i++) {
751
765
  var attr = attrs[i];
752
- if (classChain && (options.caseSensitive ? attr.name : attr.name.toLowerCase()) === 'class') {
766
+ if (classChain && attr.value && options.name(attr.name) === 'class') {
753
767
  classChain.add(trimWhitespace(attr.value).split(/[ \t\n\f\r]+/).filter(shouldSkipUIDs));
754
768
  }
755
769
  else if (options.processScripts && attr.name.toLowerCase() === 'type') {
@@ -771,7 +785,7 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
771
785
  }
772
786
 
773
787
  var log = options.log;
774
- options.log = null;
788
+ options.log = identity;
775
789
  options.sortAttributes = false;
776
790
  options.sortClassName = false;
777
791
  scan(minify(value, options));
@@ -804,9 +818,6 @@ function createSortFns(value, options, uidIgnore, uidAttr) {
804
818
  }
805
819
 
806
820
  function minify(value, options, partialMarkup) {
807
- options = options || {};
808
- var optionsStack = [];
809
- processOptions(options);
810
821
  if (options.collapseWhitespace) {
811
822
  value = collapseWhitespace(value, options, true, true);
812
823
  }
@@ -821,7 +832,6 @@ function minify(value, options, partialMarkup) {
821
832
  stackNoCollapseWhitespace = [],
822
833
  optionalStartTag = '',
823
834
  optionalEndTag = '',
824
- t = Date.now(),
825
835
  ignoredMarkupChunks = [],
826
836
  ignoredCustomMarkupChunks = [],
827
837
  uidIgnore,
@@ -837,22 +847,25 @@ function minify(value, options, partialMarkup) {
837
847
  uidIgnore = uniqueId(value);
838
848
  var pattern = new RegExp('^' + uidIgnore + '([0-9]+)$');
839
849
  if (options.ignoreCustomComments) {
840
- options.ignoreCustomComments.push(pattern);
850
+ options.ignoreCustomComments = options.ignoreCustomComments.slice();
841
851
  }
842
852
  else {
843
- options.ignoreCustomComments = [pattern];
853
+ options.ignoreCustomComments = [];
844
854
  }
855
+ options.ignoreCustomComments.push(pattern);
845
856
  }
846
857
  var token = '<!--' + uidIgnore + ignoredMarkupChunks.length + '-->';
847
858
  ignoredMarkupChunks.push(group1);
848
859
  return token;
849
860
  });
850
861
 
851
- function escapeFragments(text) {
852
- return text.replace(uidPattern, function(match, prefix, index) {
853
- var chunks = ignoredCustomMarkupChunks[+index];
854
- return chunks[1] + uidAttr + index + chunks[2];
855
- });
862
+ function escapeFragments(fn) {
863
+ return function(text, type) {
864
+ return fn(text.replace(uidPattern, function(match, prefix, index) {
865
+ var chunks = ignoredCustomMarkupChunks[+index];
866
+ return chunks[1] + uidAttr + index + chunks[2];
867
+ }), type);
868
+ };
856
869
  }
857
870
 
858
871
  var customFragments = options.ignoreCustomFragments.map(function(re) {
@@ -865,17 +878,11 @@ function minify(value, options, partialMarkup) {
865
878
  if (!uidAttr) {
866
879
  uidAttr = uniqueId(value);
867
880
  uidPattern = new RegExp('(\\s*)' + uidAttr + '([0-9]+)(\\s*)', 'g');
868
- var minifyCSS = options.minifyCSS;
869
- if (minifyCSS) {
870
- options.minifyCSS = function(text) {
871
- return minifyCSS(escapeFragments(text));
872
- };
881
+ if (options.minifyCSS) {
882
+ options.minifyCSS = escapeFragments(options.minifyCSS);
873
883
  }
874
- var minifyJS = options.minifyJS;
875
- if (minifyJS) {
876
- options.minifyJS = function(text, inline) {
877
- return minifyJS(escapeFragments(text), inline);
878
- };
884
+ if (options.minifyJS) {
885
+ options.minifyJS = escapeFragments(options.minifyJS);
879
886
  }
880
887
  }
881
888
  var token = uidAttr + ignoredCustomMarkupChunks.length;
@@ -946,21 +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') {
952
- optionsStack.push(options);
953
- var nextOptions = {};
954
- for (var key in options) {
955
- nextOptions[key] = options[key];
956
- }
957
- nextOptions.keepClosingSlash = true;
958
- nextOptions.caseSensitive = true;
959
- options = nextOptions;
956
+ if (tag.toLowerCase() === 'svg') {
957
+ options = Object.create(options);
958
+ options.caseSensitive = true;
959
+ options.keepClosingSlash = true;
960
+ options.name = identity;
960
961
  }
961
-
962
- tag = options.caseSensitive ? tag : lowerTag;
963
-
962
+ tag = options.name(tag);
964
963
  currentTag = tag;
965
964
  charsPrevTag = tag;
966
965
  if (!inlineTextTags(tag)) {
@@ -1040,11 +1039,10 @@ function minify(value, options, partialMarkup) {
1040
1039
  }
1041
1040
  },
1042
1041
  end: function(tag, attrs, autoGenerated) {
1043
- var lowerTag = tag.toLowerCase();
1044
- if (lowerTag === 'svg') {
1045
- options = optionsStack.pop();
1042
+ if (tag.toLowerCase() === 'svg') {
1043
+ options = Object.getPrototypeOf(options);
1046
1044
  }
1047
- tag = options.caseSensitive ? tag : lowerTag;
1045
+ tag = options.name(tag);
1048
1046
 
1049
1047
  // check if current tag is in a whitespace stack
1050
1048
  if (options.collapseWhitespace) {
@@ -1269,7 +1267,6 @@ function minify(value, options, partialMarkup) {
1269
1267
  });
1270
1268
  }
1271
1269
 
1272
- options.log('minified in: ' + (Date.now() - t) + 'ms');
1273
1270
  return str;
1274
1271
  }
1275
1272
 
@@ -1277,20 +1274,29 @@ function joinResultSegments(results, options) {
1277
1274
  var str;
1278
1275
  var maxLineLength = options.maxLineLength;
1279
1276
  if (maxLineLength) {
1280
- var token;
1281
- var lines = [];
1282
- var line = '';
1283
- for (var i = 0, len = results.length; i < len; i++) {
1284
- token = results[i];
1285
- if (line.length + token.length < maxLineLength) {
1286
- line += token;
1277
+ var line = '', lines = [];
1278
+ while (results.length) {
1279
+ var len = line.length;
1280
+ var end = results[0].indexOf('\n');
1281
+ if (end < 0) {
1282
+ line += results.shift();
1287
1283
  }
1288
1284
  else {
1289
- lines.push(line.replace(/^\n/, ''));
1290
- line = token;
1285
+ line += results[0].slice(0, end);
1286
+ results[0] = results[0].slice(end + 1);
1291
1287
  }
1288
+ if (len > 0 && line.length >= maxLineLength) {
1289
+ lines.push(line.slice(0, len));
1290
+ line = line.slice(len);
1291
+ }
1292
+ else if (end >= 0) {
1293
+ lines.push(line);
1294
+ line = '';
1295
+ }
1296
+ }
1297
+ if (line) {
1298
+ lines.push(line);
1292
1299
  }
1293
- lines.push(line);
1294
1300
 
1295
1301
  str = lines.join('\n');
1296
1302
  }
@@ -1301,5 +1307,9 @@ function joinResultSegments(results, options) {
1301
1307
  }
1302
1308
 
1303
1309
  exports.minify = function(value, options) {
1304
- return minify(value, options);
1310
+ var start = Date.now();
1311
+ options = processOptions(options || {});
1312
+ var result = minify(value, options);
1313
+ options.log('minified in: ' + (Date.now() - start) + 'ms');
1314
+ return result;
1305
1315
  };
package/src/htmlparser.js CHANGED
@@ -49,7 +49,12 @@ var singleAttrIdentifier = /([^\s"'<>/=]+)/,
49
49
  ],
50
50
  // https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
51
51
  qnameCapture = (function() {
52
- var ncname = require('ncname').source.slice(1, -1);
52
+ // based on https://www.npmjs.com/package/ncname
53
+ var combiningChar = '\\u0300-\\u0345\\u0360\\u0361\\u0483-\\u0486\\u0591-\\u05A1\\u05A3-\\u05B9\\u05BB-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u064B-\\u0652\\u0670\\u06D6-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0901-\\u0903\\u093C\\u093E-\\u094D\\u0951-\\u0954\\u0962\\u0963\\u0981-\\u0983\\u09BC\\u09BE-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CD\\u09D7\\u09E2\\u09E3\\u0A02\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A70\\u0A71\\u0A81-\\u0A83\\u0ABC\\u0ABE-\\u0AC5\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0B01-\\u0B03\\u0B3C\\u0B3E-\\u0B43\\u0B47\\u0B48\\u0B4B-\\u0B4D\\u0B56\\u0B57\\u0B82\\u0B83\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0BD7\\u0C01-\\u0C03\\u0C3E-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C82\\u0C83\\u0CBE-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D43\\u0D46-\\u0D48\\u0D4A-\\u0D4D\\u0D57\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F3E\\u0F3F\\u0F71-\\u0F84\\u0F86-\\u0F8B\\u0F90-\\u0F95\\u0F97\\u0F99-\\u0FAD\\u0FB1-\\u0FB7\\u0FB9\\u20D0-\\u20DC\\u20E1\\u302A-\\u302F\\u3099\\u309A';
54
+ var digit = '0-9\\u0660-\\u0669\\u06F0-\\u06F9\\u0966-\\u096F\\u09E6-\\u09EF\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0BE7-\\u0BEF\\u0C66-\\u0C6F\\u0CE6-\\u0CEF\\u0D66-\\u0D6F\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F29';
55
+ var extender = '\\xB7\\u02D0\\u02D1\\u0387\\u0640\\u0E46\\u0EC6\\u3005\\u3031-\\u3035\\u309D\\u309E\\u30FC-\\u30FE';
56
+ var letter = 'A-Za-z\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u0131\\u0134-\\u013E\\u0141-\\u0148\\u014A-\\u017E\\u0180-\\u01C3\\u01CD-\\u01F0\\u01F4\\u01F5\\u01FA-\\u0217\\u0250-\\u02A8\\u02BB-\\u02C1\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03CE\\u03D0-\\u03D6\\u03DA\\u03DC\\u03DE\\u03E0\\u03E2-\\u03F3\\u0401-\\u040C\\u040E-\\u044F\\u0451-\\u045C\\u045E-\\u0481\\u0490-\\u04C4\\u04C7\\u04C8\\u04CB\\u04CC\\u04D0-\\u04EB\\u04EE-\\u04F5\\u04F8\\u04F9\\u0531-\\u0556\\u0559\\u0561-\\u0586\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0621-\\u063A\\u0641-\\u064A\\u0671-\\u06B7\\u06BA-\\u06BE\\u06C0-\\u06CE\\u06D0-\\u06D3\\u06D5\\u06E5\\u06E6\\u0905-\\u0939\\u093D\\u0958-\\u0961\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8B\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AE0\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B36-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB5\\u0BB7-\\u0BB9\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CDE\\u0CE0\\u0CE1\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D28\\u0D2A-\\u0D39\\u0D60\\u0D61\\u0E01-\\u0E2E\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E45\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD\\u0EAE\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0F40-\\u0F47\\u0F49-\\u0F69\\u10A0-\\u10C5\\u10D0-\\u10F6\\u1100\\u1102\\u1103\\u1105-\\u1107\\u1109\\u110B\\u110C\\u110E-\\u1112\\u113C\\u113E\\u1140\\u114C\\u114E\\u1150\\u1154\\u1155\\u1159\\u115F-\\u1161\\u1163\\u1165\\u1167\\u1169\\u116D\\u116E\\u1172\\u1173\\u1175\\u119E\\u11A8\\u11AB\\u11AE\\u11AF\\u11B7\\u11B8\\u11BA\\u11BC-\\u11C2\\u11EB\\u11F0\\u11F9\\u1E00-\\u1E9B\\u1EA0-\\u1EF9\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2126\\u212A\\u212B\\u212E\\u2180-\\u2182\\u3007\\u3021-\\u3029\\u3041-\\u3094\\u30A1-\\u30FA\\u3105-\\u312C\\u4E00-\\u9FA5\\uAC00-\\uD7A3';
57
+ var ncname = '[' + letter + '_][' + letter + digit + '\\.\\-_' + combiningChar + extender + ']*';
53
58
  return '((?:' + ncname + '\\:)?' + ncname + ')';
54
59
  })(),
55
60
  startTagOpen = new RegExp('^<' + qnameCapture),
@@ -262,12 +267,36 @@ function HTMLParser(html, handler) {
262
267
  }
263
268
  }
264
269
 
270
+ function closeIfFound(tagName) {
271
+ if (findTag(tagName) >= 0) {
272
+ parseEndTag('', tagName);
273
+ return true;
274
+ }
275
+ }
276
+
265
277
  function handleStartTag(match) {
266
278
  var tagName = match.tagName;
267
279
  var unarySlash = match.unarySlash;
268
280
 
269
- if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
270
- 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
+ }
271
300
  }
272
301
 
273
302
  if (!handler.html5 && !inline(tagName)) {
@@ -348,17 +377,23 @@ function HTMLParser(html, handler) {
348
377
  }
349
378
  }
350
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
+
351
391
  function parseEndTag(tag, tagName) {
352
392
  var pos;
353
393
 
354
394
  // Find the closest opened tag of the same type
355
395
  if (tagName) {
356
- var needle = tagName.toLowerCase();
357
- for (pos = stack.length - 1; pos >= 0; pos--) {
358
- if (stack[pos].tag.toLowerCase() === needle) {
359
- break;
360
- }
361
- }
396
+ pos = findTag(tagName);
362
397
  }
363
398
  // If no tag name is provided, clean shop
364
399
  else {