html-minifier 0.6.9 → 0.8.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [0.7.0](https://github.com/kangax/html-minifier/compare/v0.6.9...v0.7.0)
2
+
3
+ * TODO
4
+
5
+ ## [0.6.9](https://github.com/kangax/html-minifier/compare/v0.6.8...v0.6.9)
6
+
7
+ * TODO
8
+
9
+ ## [0.6.8](https://github.com/kangax/html-minifier/compare/v0.6.7...v0.6.8)
10
+
11
+ * TODO
12
+
1
13
  ## [0.6.7](https://github.com/kangax/html-minifier/compare/v0.6.6...v0.6.7)
2
14
 
3
15
  * Preserve empty script with `src` attribute
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2014 Juriy "kangax" Zaytsev
1
+ Copyright (c) 2010-2015 Juriy "kangax" Zaytsev
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
- [![NPM version](https://badge.fury.io/js/html-minifier.svg)](http://badge.fury.io/js/html-minifier)
2
- [![Build Status](https://travis-ci.org/kangax/html-minifier.svg)](https://travis-ci.org/kangax/html-minifier)
3
- [![Dependency Status](https://david-dm.org/kangax/html-minifier.svg?theme=shields.io)](https://david-dm.org/kangax/html-minifier)
4
- [![devDependency Status](https://david-dm.org/kangax/html-minifier/dev-status.svg?theme=shields.io)](https://david-dm.org/kangax/html-minifier#info=devDependencies)
1
+ [![NPM version](https://img.shields.io/npm/v/html-minifier.svg)](https://www.npmjs.com/package/html-minifier)
2
+ [![Build Status](https://img.shields.io/travis/kangax/html-minifier.svg)](https://travis-ci.org/kangax/html-minifier)
3
+ [![Dependency Status](https://img.shields.io/david/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier)
4
+ [![devDependency Status](https://img.shields.io/david/dev/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier#info=devDependencies)
5
5
 
6
- [HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly __configurable__, __well-tested__, Javascript-based HTML minifier, with lint-like capabilities.
6
+ [HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, Javascript-based HTML minifier, with lint-like capabilities.
7
7
 
8
8
  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).
9
9
 
@@ -13,13 +13,13 @@ Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_mini
13
13
 
14
14
  How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/minifier/) — HTML Minifier from Will Peavy (1st result in [google search for "html minifier"](https://www.google.com/#q=html+minifier)) as well as htmlcompressor.com?
15
15
 
16
- | Site | Original size _(KB)_ | HTMLMinifier _(KB)_ | Will Peavy _(KB)_ | htmlcompressor.com _(KB)_ |
16
+ | Site | Original size _(KB)_ | HTMLMinifier _(KB)_ | Will Peavy _(KB)_ | htmlcompressor.com _(KB)_ |
17
17
  | --------------------------------------------------------------------------- |:-----------:| ----------------:| ------------:| ----------------:|
18
18
  | [HTMLMinifier page](https://github.com/kangax/html-minifier) | 48.8 | <b>37.3</b> | 43.3 | 41.9 |
19
19
  | [ES6 table](http://kangax.github.io/es5-compat-table/es6/) | 117.9 | <b>79.9</b> | 92 | 91.9 |
20
20
  | [MSN](http://msn.com) | 156.6 | <b>133</b> | 145 | 138.3 |
21
21
  | [Stackoverflow](http://stackoverflow.com) | 200.4 | <b>159.5</b> | 168.3 | 163.3 |
22
- | [Amazon](http://amazon.com) | 245.9 | <b>206.3</b> | 225 | 218.5 |
22
+ | [Amazon](http://amazon.com) | 245.9 | <b>206.3</b> | 225 | 218.5 |
23
23
  | [Wikipedia](http://en.wikipedia.org/wiki/President_of_the_United_States) | 401.4 | <b>380.6</b> | 396.3 | n/a |
24
24
  | [Eloquent Javascript](http://eloquentjavascript.net/print.html) | 869.5 | <b>830</b> | 872 | n/a |
25
25
 
@@ -34,10 +34,11 @@ How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/min
34
34
  | `removeCDATASectionsFromCDATA` | [Remove CDATA sections from script and style elements](http://perfectionkills.com/experimenting-with-html-minifier/#remove_cdata_sections) | `false` |
35
35
  | `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree.](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace) | `false` |
36
36
  | `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
37
- | `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` |
37
+ | `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` |
38
38
  | `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
39
39
  | `removeAttributeQuotes` | [Remove quotes around attributes when possible.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` |
40
40
  | `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` |
41
+ | `preventAttributesEscaping` | Prevents the escaping of the values of attributes. | `false` |
41
42
  | `useShortDoctype` | [Replaces the doctype with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
42
43
  | `removeEmptyAttributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes) | `false` |
43
44
  | `removeScriptTypeAttributes` | Remove `type="text/javascript"` from `script` tags. Other `type` attribute values are left intact. | `false` |
@@ -47,7 +48,7 @@ How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/min
47
48
  | `removeEmptyElements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
48
49
  | `lint` | [Toggle linting](http://perfectionkills.com/experimenting-with-html-minifier/#validate_input_through_html_lint) | `false` |
49
50
  | `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
50
- | `caseSensitive` | Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox) | `false` |
51
+ | `caseSensitive` | Treat attributes in case sensitive manner (useful for custom HTML tags.) | `false` |
51
52
  | `minifyJS` | Minify Javascript in script elements and on* attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `false`, `Object` (options)) |
52
53
  | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/GoalSmashers/clean-css)) | `false` (could be `true`, `false`, `Object` (options)) |
53
54
  | `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `Object` (options)) |
@@ -57,9 +58,37 @@ How does HTMLMinifier compare to [another solution](http://www.willpeavy.com/min
57
58
  | `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `'<div flex?="{{mode != cover}}"></div>'`) | `[ ]` |
58
59
  | `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. `<input {{#if value}}checked="checked"{{/if}}>`) | `[ ]` |
59
60
  | `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng\-class/`) | |
61
+ | `quoteCharacter` | Type of quote to use for attribute values (' or ") | " |
60
62
 
63
+ ## Special cases
61
64
 
62
- Chunks of markup can be ignored by wrapping them with `<!-- htmlmin:ignore -->`.
65
+ ### Ignoring chunks of markup.
66
+
67
+ If you have chunks of markup you would like preserved, you can wrap them `<!-- htmlmin:ignore -->`.
68
+
69
+ ### Preserving SVG tags
70
+
71
+ SVG tags are automatically recognized, and when they are minified, both case-sensitivity and closing-slashes are preserved, regardless of the minification settings used for the rest of the file.
72
+
73
+ ### Working with invalid markup
74
+
75
+ HTMLMinifier **can't work with invalid or partial chunks of markup**. This is because it parses markup into a tree struture, then modifies it (removing anything that was specified for removal, ignoring anything that was specified to be ingored, etc.), then it creates a markup out of that tree and returns it.
76
+
77
+ Input markup (e.g. `<p id="">foo`)
78
+
79
+ ↓
80
+
81
+ Internal representation of markup in a form of tree (e.g. `{tag: "p", attr: "id", children: ["foo"] }`)
82
+
83
+ ↓
84
+
85
+ Transformation of internal representation (e.g. removal of "id" attribute)
86
+
87
+ ↓
88
+
89
+ Output of resulting markup (e.g. `<p>foo</p>`)
90
+
91
+ HTMLMinifier can't know that original markup was only half of the tree; it does its best to try to parse it as a full tree and it loses information about tree being malformed or partial in the beginning. As a result, it can't create a partial/malformed tree at the time of the output.
63
92
 
64
93
  ## Installation Instructions
65
94
 
@@ -84,7 +113,7 @@ npm link .
84
113
 
85
114
  For command line usage please see `html-minifier --help`
86
115
 
87
- ## Node.js
116
+ ### Node.js
88
117
 
89
118
  ```js
90
119
  var minify = require('html-minifier').minify;
@@ -94,8 +123,8 @@ var result = minify('<p title="blah" id="moo">foo</p>', {
94
123
  result; // '<p title=blah id=moo>foo</p>'
95
124
  ```
96
125
 
97
- Running benchmarks
98
- ------------------
126
+ ## Running benchmarks
127
+
99
128
  Benchmarks for minified HTML:
100
129
  ```
101
130
  node benchmark.js
package/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The MIT License (MIT)
6
6
  *
7
- * Copyright (c) 2014 Zoltan Frombach
7
+ * Copyright (c) 2014-2015 Zoltan Frombach
8
8
  *
9
9
  * Permission is hereby granted, free of charge, to any person obtaining a copy of
10
10
  * this software and associated documentation files (the "Software"), to deal in
@@ -25,15 +25,19 @@
25
25
  *
26
26
  */
27
27
 
28
+ /* globals JSON:true */
29
+
28
30
  'use strict';
29
31
 
30
32
  var cli = require('cli');
33
+ var concat = require('concat-stream');
31
34
  var changeCase = require('change-case');
32
35
  var path = require('path');
33
36
  var fs = require('fs');
34
37
  var appName = require('./package.json').name;
35
38
  var appVersion = require('./package.json').version;
36
39
  var minify = require('./dist/htmlminifier.min.js').minify;
40
+ var HTMLLint = require('./dist/htmlminifier.min.js').HTMLLint;
37
41
  var minifyOptions = {};
38
42
  var input = null;
39
43
  var output = null;
@@ -62,9 +66,13 @@ var mainOptions = {
62
66
  collapseBooleanAttributes: [[false, 'Omit attribute values from boolean attributes']],
63
67
  removeAttributeQuotes: [[false, 'Remove quotes around attributes when possible.']],
64
68
  removeRedundantAttributes: [[false, 'Remove attributes when value matches default.']],
69
+ preventAttributesEscaping: [[false, 'Prevents the escaping of the values of attributes.']],
65
70
  useShortDoctype: [[false, 'Replaces the doctype with the short (HTML5) doctype']],
66
71
  removeEmptyAttributes: [[false, 'Remove all attributes with whitespace-only values']],
72
+ removeScriptTypeAttributes: [[false, 'Remove type="text/javascript" from script tags. Other type attribute values are left intact.']],
73
+ removeStyleLinkTypeAttributes: [[false, 'Remove type="text/css" from style and link tags. Other type attribute values are left intact.']],
67
74
  removeOptionalTags: [[false, 'Remove unrequired tags']],
75
+ removeIgnored: [[false, 'Remove all tags starting and ending with <%, %>, <?, ?>']],
68
76
  removeEmptyElements: [[false, 'Remove all elements with empty contents']],
69
77
  lint: [[false, 'Toggle linting']],
70
78
  keepClosingSlash: [[false, 'Keep the trailing slash on singleton elements']],
@@ -72,9 +80,12 @@ var mainOptions = {
72
80
  minifyJS: [[false, 'Minify Javascript in script elements and on* attributes (uses UglifyJS)']],
73
81
  minifyCSS: [[false, 'Minify CSS in style elements and style attributes (uses clean-css)']],
74
82
  minifyURLs: [[false, 'Minify URLs in various attributes (uses relateurl)']],
75
- ignoreCustomComments: [[false, 'Array of regex\'es that allow to ignore certain comments, when matched', 'string'], 'json'],
76
- processScripts: [[false, 'Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', 'string'], 'json'],
77
- maxLineLength: [[false, 'Max line length', 'number'], true]
83
+ ignoreCustomComments: [[false, 'Array of regex\'es that allow to ignore certain comments, when matched', 'string'], 'json-regex'],
84
+ processScripts: [[false, 'Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', 'string'], 'json-regex'],
85
+ maxLineLength: [[false, 'Max line length', 'number'], true],
86
+ customAttrAssign: [[false, 'Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'<div flex?="{{mode != cover}}"></div>\')', 'string'], 'json-regex'],
87
+ customAttrSurround: [[false, 'Arrays of regex\'es that allow to support custom attribute surround expressions (e.g. <input {{#if value}}checked="checked"{{/if}}>)', 'string'], 'json-regex'],
88
+ customAttrCollapse: [[false, 'Regex that specifies custom attribute to strip newlines from (e.g. /ng\-class/)', 'string'], 'string-regex']
78
89
  };
79
90
 
80
91
  var cliOptions = {
@@ -92,8 +103,94 @@ cli.parse(cliOptions);
92
103
 
93
104
  cli.main(function(args, options) {
94
105
 
106
+ function stringToRegExp(value) {
107
+ // JSON does not support regexes, so, e.g., JSON.parse() will not create
108
+ // a RegExp from the JSON value `[ "/matchString/" ]`, which is
109
+ // technically just an array containing a string that begins and end with
110
+ // a forward slash. To get a RegExp from a JSON string, it must be
111
+ // constructed explicitly in JavaScript.
112
+ //
113
+ // The likelihood of actually wanting to match text that is enclosed in
114
+ // forward slashes is probably quite rare, so if forward slashes were
115
+ // included in an argument that requires a regex, the user most likely
116
+ // thought they were part of the syntax for specifying a regex.
117
+ //
118
+ // In the unlikely case that forward slashes are indeed desired in the
119
+ // search string, the user would need to enclose the expression in a
120
+ // second set of slashes:
121
+ //
122
+ // --customAttrSrround "[\"//matchString//\"]"
123
+ //
124
+ if (value) {
125
+ var stripSlashes = /^\/(.*)\/$/.exec(value);
126
+ if (stripSlashes) {
127
+ value = stripSlashes[1];
128
+ }
129
+ return new RegExp(value);
130
+ }
131
+ }
132
+
133
+ function parseJSONOption(value, options) {
134
+ var opts = options || {};
135
+ if (value !== null) {
136
+ var jsonArray;
137
+ try {
138
+ jsonArray = JSON.parse(value);
139
+ if (opts.regexArray) {
140
+ jsonArray = jsonArray.map(function (regexString) {
141
+ return stringToRegExp(regexString);
142
+ });
143
+ }
144
+ }
145
+ catch (e) {
146
+ cli.fatal('Could not parse JSON value \'' + value + '\'');
147
+ }
148
+ if (jsonArray instanceof Array) {
149
+ return jsonArray;
150
+ }
151
+ else {
152
+ return [value];
153
+ }
154
+ }
155
+ }
156
+
157
+ function runMinify(original) {
158
+ var status = 0;
159
+ var minified = null;
160
+ try {
161
+ minified = minify(original, minifyOptions);
162
+ }
163
+ catch (e) {
164
+ status = 3;
165
+ cli.error('Minification error');
166
+ process.stderr.write(e);
167
+ }
168
+
169
+ if (minifyOptions.lint) {
170
+ minifyOptions.lint.populate();
171
+ }
172
+
173
+ if (minified !== null) {
174
+ // Write the output
175
+ try {
176
+ if (output !== null) {
177
+ fs.writeFileSync(path.resolve(output), minified);
178
+ }
179
+ else {
180
+ process.stdout.write(minified);
181
+ }
182
+ }
183
+ catch (e) {
184
+ status = 4;
185
+ cli.error('Cannot write to output');
186
+ }
187
+ }
188
+
189
+ cli.exit(status);
190
+ }
191
+
95
192
  if (options.version) {
96
- process.stderr.write(appName + ' v' + appVersion);
193
+ cli.output(appName + ' v' + appVersion);
97
194
  cli.exit(0);
98
195
  }
99
196
 
@@ -105,8 +202,7 @@ cli.main(function(args, options) {
105
202
  }
106
203
  }
107
204
  catch (e) {
108
- process.stderr.write('Error: Cannot read the specified config file');
109
- cli.exit(1);
205
+ cli.fatal('Cannot read the specified config file');
110
206
  }
111
207
  }
112
208
 
@@ -118,6 +214,12 @@ cli.main(function(args, options) {
118
214
  case 'json':
119
215
  minifyOptions[key] = parseJSONOption(value);
120
216
  break;
217
+ case 'json-regex':
218
+ minifyOptions[key] = parseJSONOption(value, { regexArray: true });
219
+ break;
220
+ case 'string-regex':
221
+ minifyOptions[key] = stringToRegExp(value);
222
+ break;
121
223
  case true:
122
224
  minifyOptions[key] = value;
123
225
  break;
@@ -127,6 +229,10 @@ cli.main(function(args, options) {
127
229
  }
128
230
  });
129
231
 
232
+ if (minifyOptions.lint === true) {
233
+ minifyOptions.lint = new HTMLLint();
234
+ }
235
+
130
236
  if (args.length) {
131
237
  input = args;
132
238
  }
@@ -135,104 +241,25 @@ cli.main(function(args, options) {
135
241
  output = options.output;
136
242
  }
137
243
 
138
- var original = '';
139
- var status = 0;
140
-
141
244
  if (input !== null) { // Minifying one or more files specified on the CMD line
142
245
 
246
+ var original = '';
247
+
143
248
  input.forEach(function(afile) {
144
249
  try {
145
250
  original += fs.readFileSync(afile, 'utf8');
146
251
  }
147
252
  catch (e) {
148
- status = 2;
149
- process.stderr.write('Error: Cannot read file ' + afile);
253
+ cli.error('Cannot read file ' + afile);
254
+ cli.exit(2);
150
255
  }
151
256
  });
152
257
 
153
- }
154
- else { // Minifying input coming from STDIN
155
-
156
- var BUFSIZE = 4096;
157
- var buf = new Buffer(BUFSIZE);
158
- var bytesRead;
159
-
160
- while (true) { // Loop as long as stdin input is available.
161
- bytesRead = 0;
162
- try {
163
- bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE);
164
- }
165
- catch (e) {
166
- if (e.code === 'EAGAIN') { // 'resource temporarily unavailable'
167
- // Happens on OS X 10.8.3 (not Windows 7!), if there's no
168
- // stdin input - typically when invoking a script without any
169
- // input (for interactive stdin input).
170
- // If you were to just continue, you'd create a tight loop.
171
- process.stderr.write('ERROR: interactive stdin input not supported');
172
- cli.exit(2);
173
- }
174
- else if (e.code === 'EOF') {
175
- // Happens on Windows 7, but not OS X 10.8.3:
176
- // simply signals the end of *piped* stdin input.
177
- break;
178
- }
179
- throw e; // unexpected exception
180
- }
181
- if (bytesRead === 0) {
182
- // No more stdin input available.
183
- // OS X 10.8.3: regardless of input method, this is how the end
184
- // of input is signaled.
185
- // Windows 7: this is how the end of input is signaled for
186
- // *interactive* stdin input.
187
- break;
188
- }
189
- original += buf.toString('utf8', 0, bytesRead);
190
- }
191
-
192
- }
193
-
194
- function parseJSONOption(value) {
195
- if (value !== null) {
196
- var jsonArray;
197
- try {
198
- jsonArray = JSON.parse(value);
199
- }
200
- catch (e) {}
201
- if (jsonArray instanceof Array) {
202
- return jsonArray;
203
- }
204
- else {
205
- return [value];
206
- }
207
- }
208
- }
258
+ runMinify(original);
209
259
 
210
- // Run minify
211
- var minified = null;
212
- try {
213
- minified = minify(original, minifyOptions);
214
- }
215
- catch (e) {
216
- status = 3;
217
- process.stderr.write('Error: Minification error');
218
260
  }
219
-
220
- if (minified !== null) {
221
- // Write the output
222
- try {
223
- if (output !== null) {
224
- fs.writeFileSync(path.resolve(output), minified);
225
- }
226
- else {
227
- process.stdout.write(minified);
228
- }
229
- }
230
- catch (e) {
231
- status = 4;
232
- process.stderr.write('Error: Cannot write to output');
233
- }
261
+ else { // Minifying input coming from STDIN
262
+ process.stdin.pipe(concat({ encoding: 'string' }, runMinify));
234
263
  }
235
264
 
236
- cli.exit(status);
237
-
238
265
  });