html-minifier 1.1.1 → 1.4.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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010-2015 Juriy "kangax" Zaytsev
1
+ Copyright (c) 2010-2016 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,12 @@
1
+ # HTMLMinifier
2
+
1
3
  [![NPM version](https://img.shields.io/npm/v/html-minifier.svg)](https://www.npmjs.com/package/html-minifier)
2
4
  [![Build Status](https://img.shields.io/travis/kangax/html-minifier.svg)](https://travis-ci.org/kangax/html-minifier)
3
5
  [![Dependency Status](https://img.shields.io/david/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier)
4
6
  [![devDependency Status](https://img.shields.io/david/dev/kangax/html-minifier.svg)](https://david-dm.org/kangax/html-minifier#info=devDependencies)
7
+ [![Gitter](https://img.shields.io/gitter/room/kangax/html-minifier.svg)](https://gitter.im/kangax/html-minifier)
5
8
 
6
- [HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, Javascript-based HTML minifier, with lint-like capabilities.
9
+ [HTMLMinifier](http://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, JavaScript-based HTML minifier, with lint-like capabilities.
7
10
 
8
11
  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
12
 
@@ -13,58 +16,64 @@ Also see corresponding [Ruby wrapper](https://github.com/stereobooster/html_mini
13
16
 
14
17
  ## Minification comparison
15
18
 
16
- How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Peavy](http://www.willpeavy.com/minifier/) (1st result in [google search for "html minifier"](https://www.google.com/#q=html+minifier)) as well as [htmlcompressor.com](http://htmlcompressor.com) and [minimize](https://github.com/Swaagie/minimize)?
17
-
18
- | Site | Original size _(KB)_ | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
19
- | --------------------------------------------------------------------------- |:-----------:| ----------------:| ----------------:| ------------:| ----------------:|
20
- | [HTMLMinifier page](https://github.com/kangax/html-minifier) | 48.8 | <b>37.3</b> | 41.8 | 43.3 | 41.9 |
21
- | [ES6 table](http://kangax.github.io/es5-compat-table/es6/) | 117.9 | <b>79.9</b> | 94.1 | 92 | 91.9 |
22
- | [MSN](http://msn.com) | 156.6 | <b>133</b> | 137.7 | 145 | 138.3 |
23
- | [Stackoverflow](http://stackoverflow.com) | 200.4 | <b>159.5</b> | 165.1 | 168.3 | 163.3 |
24
- | [Amazon](http://amazon.com) | 245.9 | <b>206.3</b> | 234.1 | 225 | 218.5 |
25
- | [Wikipedia](http://en.wikipedia.org/wiki/President_of_the_United_States) | 401.4 | <b>380.6</b> | 386.6 | 396.3 | n/a |
26
- | [Eloquent Javascript](http://eloquentjavascript.net/print.html) | 869.5 | <b>830</b> | 838 | 872 | n/a |
27
-
28
-
19
+ How does HTMLMinifier compare to other solutions — [HTML Minifier from Will Peavy](http://www.willpeavy.com/minifier/) (1st result in [Google search for "html minifier"](https://www.google.com/#q=html+minifier)) as well as [htmlcompressor.com](http://htmlcompressor.com) and [minimize](https://github.com/Swaagie/minimize)?
20
+
21
+ | Site | Original size *(KB)* | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
22
+ | --------------------------------------------------------------------------- |:--------------------:| ------------:| --------:| ----------:| ------------------:|
23
+ | [HTMLMinifier page](https://github.com/kangax/html-minifier) | 49 | **37** | 42 | 44 | 43 |
24
+ | [NBC](http://www.nbc.com/) | 91 | **74** | 84 | 85 | 85 |
25
+ | [ES6 table](http://kangax.github.io/es5-compat-table/es6/) | 118 | **80** | 93 | 93 | 94 |
26
+ | [New York Times](http://www.nytimes.com/) | 131 | **101** | 122 | 125 | 120 |
27
+ | [Google](https://www.google.com/) | 133 | **128** | 132 | 135 | 131 |
28
+ | [MSN](http://www.msn.com/) | 157 | **130** | 138 | 145 | 138 |
29
+ | [Stackoverflow](http://stackoverflow.com/) | 200 | **159** | 165 | 173 | 166 |
30
+ | [Amazon](http://www.amazon.com/) | 246 | **203** | 234 | 230 | 219 |
31
+ | [Bootstrap CSS](http://getbootstrap.com/css/) | 277 | **268** | 274 | 232 | 277 |
32
+ | [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 401 | **367** | 388 | 400 | n/a |
33
+ | [Eloquent Javascript](http://eloquentjavascript.net/1st_edition/print.html) | 870 | **827** | 840 | 864 | n/a |
34
+ | [ES6 draft](https://tc39.github.io/ecma262/) | 3678 | **2991** | 3079 | 3183 | n/a |
29
35
 
30
36
  ## Options Quick Reference
31
37
 
32
38
  | Option | Description | Default |
33
39
  |--------------------------------|-----------------|---------|
34
40
  | `removeComments` | [Strip HTML comments](http://perfectionkills.com/experimenting-with-html-minifier/#remove_comments) | `false` |
35
- | `removeCommentsFromCDATA` | [Strip HTML comments from scripts and styles](http://perfectionkills.com/experimenting-with-html-minifier/#remove_comments_from_scripts_and_styles) | `false` |
36
- | `removeCDATASectionsFromCDATA` | [Remove CDATA sections from script and style elements](http://perfectionkills.com/experimenting-with-html-minifier/#remove_cdata_sections) | `false` |
37
41
  | `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree.](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace) | `false` |
38
42
  | `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
43
+ | `collapseInlineTagWhitespace` | Don't leave any spaces between `display:inline;` elements when collapsing. Must be used in conjunction with `collapseWhitespace=true` | `false` |
39
44
  | `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` |
40
45
  | `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
46
+ | `removeTagWhitespace` | Remove space between attributes whenever possible. **Note that this will result in invalid HTML!** | `false` |
41
47
  | `removeAttributeQuotes` | [Remove quotes around attributes when possible.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` |
42
48
  | `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` |
43
49
  | `preventAttributesEscaping` | Prevents the escaping of the values of attributes. | `false` |
44
- | `useShortDoctype` | [Replaces the doctype with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
50
+ | `useShortDoctype` | [Replaces the `doctype` with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
45
51
  | `removeEmptyAttributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes) | `false` |
46
52
  | `removeScriptTypeAttributes` | Remove `type="text/javascript"` from `script` tags. Other `type` attribute values are left intact. | `false` |
47
53
  | `removeStyleLinkTypeAttributes`| Remove `type="text/css"` from `style` and `link` tags. Other `type` attribute values are left intact. | `false` |
48
- | `removeOptionalTags` | [Remove unrequired tags](http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
54
+ | `removeOptionalTags` | [Remove optional tags](http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
49
55
  | `removeEmptyElements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
50
56
  | `lint` | [Toggle linting](http://perfectionkills.com/experimenting-with-html-minifier/#validate_input_through_html_lint) | `false` |
51
57
  | `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
52
58
  | `caseSensitive` | Treat attributes in case sensitive manner (useful for custom HTML tags.) | `false` |
53
- | `minifyJS` | Minify Javascript in script elements and on* attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `false`, `Object` (options)) |
54
- | `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)) |
59
+ | `minifyJS` | Minify JavaScript in script elements and event attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `false`, `Object` (options)) |
60
+ | `minifyCSS` | Minify CSS in style elements and style attributes (uses [clean-css](https://github.com/jakubpawlowicz/clean-css)) | `false` (could be `true`, `false`, `Object` (options)) |
55
61
  | `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `Object` (options)) |
62
+ | `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
56
63
  | `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ ]` |
57
- | `ignoreCustomFragments` | Array of regex'es that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ ]` |
64
+ | `ignoreCustomFragments` | Array of regex'es that allow to ignore certain fragments, when matched (e.g. `<?php ... ?>`, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
58
65
  | `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. `text/ng-template`, `text/x-handlebars-template`, etc.) | `[ ]` |
66
+ | `processConditionalComments` | Process contents of conditional comments through minifier | `false` |
59
67
  | `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points. |
68
+ | `customEventAttributes` | Arrays of regex'es that allow to support custom event attributes for `minifyJS` (e.g. `ng-click`) | `[ /^on[a-z]{3,}$/ ]` |
60
69
  | `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `'<div flex?="{{mode != cover}}"></div>'`) | `[ ]` |
61
70
  | `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. `<input {{#if value}}checked="checked"{{/if}}>`) | `[ ]` |
62
71
  | `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng\-class/`) | |
63
- | `quoteCharacter` | Type of quote to use for attribute values (' or ") | " |
72
+ | `quoteCharacter` | Type of quote to use for attribute values (' or ") | |
64
73
 
65
74
  ## Special cases
66
75
 
67
- ### Ignoring chunks of markup.
76
+ ### Ignoring chunks of markup
68
77
 
69
78
  If you have chunks of markup you would like preserved, you can wrap them `<!-- htmlmin:ignore -->`.
70
79
 
@@ -74,17 +83,17 @@ SVG tags are automatically recognized, and when they are minified, both case-sen
74
83
 
75
84
  ### Working with invalid markup
76
85
 
77
- HTMLMinifier **can't work with invalid or partial chunks of markup**. This is because it parses markup into a tree structure, 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.
86
+ HTMLMinifier **can't work with invalid or partial chunks of markup**. This is because it parses markup into a tree structure, then modifies it (removing anything that was specified for removal, ignoring anything that was specified to be ignored, etc.), then it creates a markup out of that tree and returns it.
78
87
 
79
88
  Input markup (e.g. `<p id="">foo`)
80
89
 
81
90
  ↓
82
91
 
83
- Internal representation of markup in a form of tree (e.g. `{tag: "p", attr: "id", children: ["foo"] }`)
92
+ Internal representation of markup in a form of tree (e.g. `{ tag: "p", attr: "id", children: ["foo"] }`)
84
93
 
85
94
  ↓
86
95
 
87
- Transformation of internal representation (e.g. removal of "id" attribute)
96
+ Transformation of internal representation (e.g. removal of `id` attribute)
88
97
 
89
98
  ↓
90
99
 
@@ -95,17 +104,20 @@ HTMLMinifier can't know that original markup was only half of the tree; it does
95
104
  ## Installation Instructions
96
105
 
97
106
  From NPM for use as a command line app:
98
- ```bash
107
+
108
+ ```shell
99
109
  npm install html-minifier -g
100
110
  ```
101
111
 
102
112
  From NPM for programmatic use:
103
- ```bash
113
+
114
+ ```shell
104
115
  npm install html-minifier
105
116
  ```
106
117
 
107
118
  From Git:
108
- ```bash
119
+
120
+ ```shell
109
121
  git clone git://github.com/kangax/html-minifier.git
110
122
  cd html-minifier
111
123
  npm link .
@@ -128,6 +140,7 @@ result; // '<p title=blah id=moo>foo</p>'
128
140
  ## Running benchmarks
129
141
 
130
142
  Benchmarks for minified HTML:
131
- ```
143
+
144
+ ```shell
132
145
  node benchmark.js
133
146
  ```
package/cli.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The MIT License (MIT)
6
6
  *
7
- * Copyright (c) 2014-2015 Zoltan Frombach
7
+ * Copyright (c) 2014-2016 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,8 +25,6 @@
25
25
  *
26
26
  */
27
27
 
28
- /* globals JSON:true */
29
-
30
28
  'use strict';
31
29
 
32
30
  var cli = require('cli');
@@ -36,19 +34,18 @@ var path = require('path');
36
34
  var fs = require('fs');
37
35
  var appName = require('./package.json').name;
38
36
  var appVersion = require('./package.json').version;
39
- var minify = require('./dist/htmlminifier.min.js').minify;
40
- var HTMLLint = require('./dist/htmlminifier.min.js').HTMLLint;
37
+ var minify = require('.').minify;
38
+ var HTMLLint = require('./src/htmllint').HTMLLint;
41
39
  var minifyOptions = {};
42
- var input = null;
43
- var output = null;
40
+ var output;
44
41
 
45
42
  cli.width = 100;
46
43
  cli.option_width = 40;
47
44
  cli.setApp(appName, appVersion);
48
45
 
49
46
  var usage = appName + ' [OPTIONS] [FILE(s)]\n\n';
50
- usage += ' If no input file(s) specified then STDIN will be used for input.\n';
51
- usage += ' If more than one input file specified those will be concatenated and minified together.\n\n';
47
+ usage += ' If no input files are specified then STDIN will be used for input.\n';
48
+ usage += ' If more than one input file is specified then those will be concatenated and minified together.\n\n';
52
49
  usage += ' When you specify a config file with the --config-file option (see sample-cli-config-file.conf for format)\n';
53
50
  usage += ' you can still override some of its contents by providing individual command line options, too.\n\n';
54
51
  usage += ' When you want to provide an array of strings for --ignore-custom-comments or --process-scripts options\n';
@@ -57,35 +54,40 @@ usage += ' on the command line you must escape those such as --ignore-custom-
57
54
  cli.setUsage(usage);
58
55
 
59
56
  var mainOptions = {
60
- removeComments: [[false, 'Strip HTML comments']],
61
- removeCommentsFromCDATA: [[false, 'Strip HTML comments from scripts and styles']],
62
- removeCDATASectionsFromCDATA: [[false, 'Remove CDATA sections from script and style elements']],
57
+ caseSensitive: [[false, 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)']],
58
+ collapseBooleanAttributes: [[false, 'Omit attribute values from boolean attributes']],
59
+ collapseInlineTagWhitespace: [[false, 'Collapse white space around inline tag']],
63
60
  collapseWhitespace: [[false, 'Collapse white space that contributes to text nodes in a document tree.']],
64
61
  conservativeCollapse: [[false, 'Always collapse to 1 space (never remove it entirely)']],
62
+ customAttrAssign: [[false, 'Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'<div flex?="{{mode != cover}}"></div>\')', 'string'], 'json-regex'],
63
+ customAttrCollapse: [[false, 'Regex that specifies custom attribute to strip newlines from (e.g. /ng\-class/)', 'string'], 'string-regex'],
64
+ 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'],
65
+ customEventAttributes: [[false, 'Arrays of regex\'es that allow to support custom event attributes for minifyJS (e.g. ng-click)', 'string'], 'json-regex'],
66
+ html5: [[false, 'Parse input according to HTML5 specifications']],
67
+ ignoreCustomComments: [[false, 'Array of regex\'es that allow to ignore certain comments, when matched', 'string'], 'json-regex'],
68
+ ignoreCustomFragments: [[false, 'Array of regex\'es that allow to ignore certain fragments, when matched (e.g. <?php ... ?>, {{ ... }})', 'string'], 'json-regex'],
69
+ includeAutoGeneratedTags: [[false, 'Insert tags generated by HTML parser'], true],
70
+ keepClosingSlash: [[false, 'Keep the trailing slash on singleton elements']],
71
+ lint: [[false, 'Toggle linting']],
72
+ maxLineLength: [[false, 'Max line length', 'number'], true],
73
+ minifyCSS: [[false, 'Minify CSS in style elements and style attributes (uses clean-css)']],
74
+ minifyJS: [[false, 'Minify Javascript in script elements and on* attributes (uses UglifyJS)']],
75
+ minifyURLs: [[false, 'Minify URLs in various attributes (uses relateurl)', 'string'], 'site-url'],
65
76
  preserveLineBreaks: [[false, 'Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break.']],
66
- collapseInlineTagWhitespace: [[false, 'Collapse white space around inline tag']],
67
- collapseBooleanAttributes: [[false, 'Omit attribute values from boolean attributes']],
68
- removeAttributeQuotes: [[false, 'Remove quotes around attributes when possible.']],
69
- removeRedundantAttributes: [[false, 'Remove attributes when value matches default.']],
70
77
  preventAttributesEscaping: [[false, 'Prevents the escaping of the values of attributes.']],
71
- useShortDoctype: [[false, 'Replaces the doctype with the short (HTML5) doctype']],
78
+ processConditionalComments: [[false, 'Process contents of conditional comments through minifier']],
79
+ 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'],
80
+ quoteCharacter: [[false, 'Type of quote to use for attribute values (\' or ")', 'string'], true],
81
+ removeAttributeQuotes: [[false, 'Remove quotes around attributes when possible.']],
82
+ removeComments: [[false, 'Strip HTML comments']],
72
83
  removeEmptyAttributes: [[false, 'Remove all attributes with whitespace-only values']],
84
+ removeEmptyElements: [[false, 'Remove all elements with empty contents']],
85
+ removeOptionalTags: [[false, 'Remove unrequired tags']],
86
+ removeRedundantAttributes: [[false, 'Remove attributes when value matches default.']],
73
87
  removeScriptTypeAttributes: [[false, 'Remove type="text/javascript" from script tags. Other type attribute values are left intact.']],
74
88
  removeStyleLinkTypeAttributes: [[false, 'Remove type="text/css" from style and link tags. Other type attribute values are left intact.']],
75
- removeOptionalTags: [[false, 'Remove unrequired tags']],
76
- removeEmptyElements: [[false, 'Remove all elements with empty contents']],
77
- lint: [[false, 'Toggle linting']],
78
- keepClosingSlash: [[false, 'Keep the trailing slash on singleton elements']],
79
- caseSensitive: [[false, 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)']],
80
- minifyJS: [[false, 'Minify Javascript in script elements and on* attributes (uses UglifyJS)']],
81
- minifyCSS: [[false, 'Minify CSS in style elements and style attributes (uses clean-css)']],
82
- minifyURLs: [[false, 'Minify URLs in various attributes (uses relateurl)']],
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']
89
+ removeTagWhitespace: [[false, 'Remove space between attributes whenever possible.']],
90
+ useShortDoctype: [[false, 'Replaces the doctype with the short (HTML5) doctype']]
89
91
  };
90
92
 
91
93
  var cliOptions = {
@@ -132,27 +134,19 @@ cli.main(function(args, options) {
132
134
  }
133
135
  }
134
136
 
135
- function parseJSONOption(value, options) {
136
- var opts = options || {};
137
+ function parseJSONOption(value, regexArray) {
137
138
  if (value !== null) {
138
139
  var jsonArray;
139
140
  try {
140
141
  jsonArray = JSON.parse(value);
141
- if (opts.regexArray) {
142
- jsonArray = jsonArray.map(function (regexString) {
143
- return stringToRegExp(regexString);
144
- });
142
+ if (regexArray) {
143
+ jsonArray = jsonArray.map(stringToRegExp);
145
144
  }
146
145
  }
147
146
  catch (e) {
148
147
  cli.fatal('Could not parse JSON value \'' + value + '\'');
149
148
  }
150
- if (jsonArray instanceof Array) {
151
- return jsonArray;
152
- }
153
- else {
154
- return [value];
155
- }
149
+ return Array.isArray(jsonArray) ? jsonArray : [value];
156
150
  }
157
151
  }
158
152
 
@@ -165,7 +159,7 @@ cli.main(function(args, options) {
165
159
  catch (e) {
166
160
  status = 3;
167
161
  cli.error('Minification error');
168
- process.stderr.write(e);
162
+ process.stderr.write((e.stack || e).toString());
169
163
  }
170
164
 
171
165
  if (minifyOptions.lint) {
@@ -174,22 +168,21 @@ cli.main(function(args, options) {
174
168
 
175
169
  if (minified !== null) {
176
170
  // Write the output
177
- try {
178
- if (output !== null) {
171
+ if (output) {
172
+ try {
179
173
  fs.writeFileSync(path.resolve(output), minified);
180
174
  }
181
- else {
182
- process.stdout.write(minified);
175
+ catch (e) {
176
+ status = 4;
177
+ cli.error('Cannot write to ' + output);
183
178
  }
184
179
  }
185
- catch (e) {
186
- status = 4;
187
- console.log(output);
188
- cli.error('Cannot write to output');
180
+ else {
181
+ process.stdout.write(minified);
189
182
  }
190
183
  }
191
184
 
192
- cli.exit(status);
185
+ return status;
193
186
  }
194
187
 
195
188
  function createDirectory(path) {
@@ -197,31 +190,34 @@ cli.main(function(args, options) {
197
190
  fs.mkdirSync(path);
198
191
  }
199
192
  catch (e) {
200
- if (e.code === 'EEXIST') {
201
- return;
193
+ if (e.code !== 'EEXIST') {
194
+ cli.fatal('Can not create directory ' + path);
195
+ return 3;
202
196
  }
203
- cli.fatal('Can not create directory ' + path);
204
- cli.exit(3);
205
197
  }
198
+ return 0;
206
199
  }
207
200
 
208
201
  function processDirectory(inputDir, outputDir) {
209
- createDirectory(outputDir);
210
202
  var fileList = fs.readdirSync(inputDir);
203
+ var status = createDirectory(outputDir);
211
204
 
212
- fileList.forEach(function(fileName) {
205
+ for (var i = 0; status === 0 && i < fileList.length; i++) {
206
+ var fileName = fileList[i];
213
207
  var inputFilePath = inputDir + '/' + fileName;
214
208
  var outputFilePath = outputDir + '/' + fileName;
215
209
 
216
210
  var stat = fs.statSync(inputFilePath);
217
211
  if (stat.isDirectory()) {
218
- processDirectory(inputFilePath, outputFilePath);
219
- return;
212
+ status = processDirectory(inputFilePath, outputFilePath);
220
213
  }
214
+ else {
215
+ var originalContent = fs.readFileSync(inputFilePath, 'utf8');
216
+ status = runMinify(originalContent, outputFilePath);
217
+ }
218
+ }
221
219
 
222
- var originalContent = fs.readFileSync(inputFilePath, 'utf8');
223
- runMinify(originalContent, outputFilePath);
224
- });
220
+ return status;
225
221
  }
226
222
 
227
223
  if (options.version) {
@@ -230,44 +226,58 @@ cli.main(function(args, options) {
230
226
  }
231
227
 
232
228
  if (options['config-file']) {
233
- var fileOptions;
234
- var fileOptionsPath = path.resolve(options['config-file']);
235
- try {
236
- fs.accessSync(fileOptionsPath, fs.R_OK);
237
- }
238
- catch (e) {
239
- cli.fatal('The specified config file doesn’t exist or is unreadable:\n' + fileOptionsPath);
240
- }
229
+ var configPath = path.resolve(options['config-file']);
241
230
  try {
242
- fileOptions = JSON.parse(fs.readFileSync(fileOptionsPath), 'utf8');
231
+ var configData;
232
+ try {
233
+ configData = fs.readFileSync(configPath, { encoding: 'utf8' });
234
+ }
235
+ catch (e) {
236
+ cli.fatal('The specified config file doesn’t exist or is unreadable:\n' + configPath);
237
+ }
238
+ configData = JSON.parse(configData);
239
+ mainOptionKeys.forEach(function(key) {
240
+ var value = configData[key];
241
+ if (value !== undefined) {
242
+ switch (mainOptions[key][1]) {
243
+ case 'json-regex':
244
+ minifyOptions[key] = value.map(stringToRegExp);
245
+ break;
246
+ case 'string-regex':
247
+ minifyOptions[key] = stringToRegExp(value);
248
+ break;
249
+ default:
250
+ minifyOptions[key] = value;
251
+ }
252
+ }
253
+ });
243
254
  }
244
255
  catch (je) {
245
256
  try {
246
- fileOptions = require(fileOptionsPath);
257
+ minifyOptions = require(configPath);
247
258
  }
248
259
  catch (ne) {
249
- cli.fatal('Cannot read the specified config file. \nAs JSON: ' + je.message + '\nAs module: ' + ne.message);
260
+ cli.fatal('Cannot read the specified config file.\nAs JSON: ' + je.message + '\nAs module: ' + ne.message);
250
261
  }
251
262
  }
252
-
253
- if (fileOptions && typeof fileOptions === 'object') {
254
- minifyOptions = fileOptions;
255
- }
256
263
  }
257
264
  mainOptionKeys.forEach(function(key) {
258
265
  var paramKey = changeCase.paramCase(key);
259
266
  var value = options[paramKey];
260
- if (options[paramKey] !== null) {
267
+ if (value !== null) {
261
268
  switch (mainOptions[key][1]) {
262
269
  case 'json':
263
270
  minifyOptions[key] = parseJSONOption(value);
264
271
  break;
265
272
  case 'json-regex':
266
- minifyOptions[key] = parseJSONOption(value, { regexArray: true });
273
+ minifyOptions[key] = parseJSONOption(value, true);
267
274
  break;
268
275
  case 'string-regex':
269
276
  minifyOptions[key] = stringToRegExp(value);
270
277
  break;
278
+ case 'site-url':
279
+ minifyOptions[key] = { site: value };
280
+ break;
271
281
  case true:
272
282
  minifyOptions[key] = value;
273
283
  break;
@@ -281,16 +291,12 @@ cli.main(function(args, options) {
281
291
  minifyOptions.lint = new HTMLLint();
282
292
  }
283
293
 
284
- if (args.length) {
285
- input = args;
286
- }
287
-
288
294
  if (options['input-dir'] || options['output-dir']) {
289
295
  var inputDir = options['input-dir'];
290
296
  var outputDir = options['output-dir'];
291
297
 
292
298
  if (!inputDir) {
293
- cli.error('The option output-dir need to be use with the option include-dir. If you are working with only 1 file, use -o.');
299
+ cli.error('The option output-dir needs to be used with the option input-dir. If you are working with a single file, use -o.');
294
300
  cli.exit(2);
295
301
  }
296
302
 
@@ -298,7 +304,7 @@ cli.main(function(args, options) {
298
304
  fs.statSync(inputDir);
299
305
  }
300
306
  catch (e) {
301
- cli.error('The input directory does not exits');
307
+ cli.error('The input directory does not exist');
302
308
  cli.exit(2);
303
309
  }
304
310
 
@@ -309,27 +315,23 @@ cli.main(function(args, options) {
309
315
  }
310
316
 
311
317
  try {
312
- processDirectory(inputDir, outputDir);
318
+ cli.exit(processDirectory(inputDir, outputDir));
313
319
  }
314
320
  catch (e) {
315
- cli.error('Something wrong happened');
321
+ cli.error('Error while processing input files');
316
322
  cli.error(e);
317
323
  cli.exit(3);
318
324
  }
319
-
320
- cli.exit(0);
321
- return;
322
325
  }
323
326
 
324
327
  if (options.output) {
325
328
  output = options.output;
326
329
  }
327
330
 
328
- if (input !== null) { // Minifying one or more files specified on the CMD line
329
-
331
+ if (args.length) { // Minifying one or more files specified on the CMD line
330
332
  var original = '';
331
333
 
332
- input.forEach(function(afile) {
334
+ args.forEach(function(afile) {
333
335
  try {
334
336
  original += fs.readFileSync(afile, 'utf8');
335
337
  }
@@ -339,10 +341,11 @@ cli.main(function(args, options) {
339
341
  }
340
342
  });
341
343
 
342
- runMinify(original, output);
343
-
344
+ cli.exit(runMinify(original, output));
344
345
  }
345
346
  else { // Minifying input coming from STDIN
346
- process.stdin.pipe(concat({ encoding: 'string' }, runMinify));
347
+ process.stdin.pipe(concat({ encoding: 'string' }, function(content) {
348
+ cli.exit(runMinify(content, output));
349
+ }));
347
350
  }
348
351
  });