(http://twitter.com/jakubpawlowicz)",
+ "description": "A well-tested CSS minifier",
+ "license": "MIT",
+ "keywords": [
+ "css",
+ "minifier"
+ ],
+ "homepage": "https://github.com/jakubpawlowicz/clean-css",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jakubpawlowicz/clean-css.git"
+ },
+ "bugs": {
+ "url": "https://github.com/jakubpawlowicz/clean-css/issues"
+ },
+ "main": "index.js",
+ "files": [
+ "lib",
+ "History.md",
+ "index.js",
+ "LICENSE"
+ ],
+ "scripts": {
+ "browserify": "browserify --standalone CleanCSS index.js | uglifyjs --compress --mangle -o cleancss-browser.js",
+ "bench": "node ./test/bench.js",
+ "check": "jshint .",
+ "prepublish": "npm run check",
+ "test": "vows"
+ },
+ "dependencies": {
+ "source-map": "~0.6.0"
+ },
+ "devDependencies": {
+ "browserify": "^14.0.0",
+ "http-proxy": "1.x",
+ "jshint": "2.x",
+ "nock": "9.x",
+ "server-destroy": "1.x",
+ "uglify-js": ">=2.6.1",
+ "vows": "0.8.x"
+ },
+ "engines": {
+ "node": ">= 4.0"
+ }
+}
diff --git a/node_modules/html-minifier/LICENSE b/node_modules/html-minifier/LICENSE
new file mode 100644
index 0000000000..b154b48834
--- /dev/null
+++ b/node_modules/html-minifier/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2018 Juriy "kangax" Zaytsev
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/node_modules/html-minifier/README.md b/node_modules/html-minifier/README.md
new file mode 100644
index 0000000000..37855631af
--- /dev/null
+++ b/node_modules/html-minifier/README.md
@@ -0,0 +1,160 @@
+# HTMLMinifier
+
+[](https://www.npmjs.com/package/html-minifier)
+[](https://travis-ci.org/kangax/html-minifier)
+[](https://david-dm.org/kangax/html-minifier)
+
+[HTMLMinifier](https://kangax.github.io/html-minifier/) is a highly **configurable**, **well-tested**, JavaScript-based HTML minifier.
+
+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).
+
+[Test suite is available online](https://kangax.github.io/html-minifier/tests/).
+
+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).
+
+For lint-like capabilities take a look at [HTMLLint](https://github.com/kangax/html-lint).
+
+## Minification comparison
+
+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)?
+
+| Site | Original size *(KB)* | HTMLMinifier | minimize | Will Peavy | htmlcompressor.com |
+| ---------------------------------------------------------------------------- |:--------------------:| ------------:| --------:| ----------:| ------------------:|
+| [Google](https://www.google.com/) | 46 | **42** | 46 | 48 | 46 |
+| [HTMLMinifier](https://github.com/kangax/html-minifier) | 125 | **98** | 111 | 117 | 111 |
+| [Twitter](https://twitter.com/) | 207 | **165** | 200 | 224 | 200 |
+| [Stack Overflow](https://stackoverflow.com/) | 253 | **195** | 207 | 215 | 204 |
+| [Bootstrap CSS](https://getbootstrap.com/docs/3.3/css/) | 271 | **260** | 269 | 228 | 269 |
+| [BBC](https://www.bbc.co.uk/) | 298 | **239** | 290 | 291 | 280 |
+| [Amazon](https://www.amazon.co.uk/) | 422 | **316** | 412 | 425 | n/a |
+| [NBC](https://www.nbc.com/) | 553 | **530** | 552 | 553 | 534 |
+| [Wikipedia](https://en.wikipedia.org/wiki/President_of_the_United_States) | 565 | **461** | 548 | 569 | 548 |
+| [New York Times](https://www.nytimes.com/) | 678 | **606** | 675 | 670 | n/a |
+| [Eloquent Javascript](https://eloquentjavascript.net/1st_edition/print.html) | 870 | **815** | 840 | 864 | n/a |
+| [ES6 table](https://kangax.github.io/compat-table/es6/) | 5911 | **5051** | 5595 | n/a | n/a |
+| [ES draft](https://tc39.github.io/ecma262/) | 6126 | **5495** | 5664 | n/a | n/a |
+
+## Options Quick Reference
+
+Most of the options are disabled by default.
+
+| Option | Description | Default |
+|--------------------------------|-----------------|---------|
+| `caseSensitive` | Treat attributes in case sensitive manner (useful for custom HTML tags) | `false` |
+| `collapseBooleanAttributes` | [Omit attribute values from boolean attributes](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_boolean_attributes) | `false` |
+| `collapseInlineTagWhitespace` | Don't leave any spaces between `display:inline;` elements when collapsing. Must be used in conjunction with `collapseWhitespace=true` | `false` |
+| `collapseWhitespace` | [Collapse white space that contributes to text nodes in a document tree](http://perfectionkills.com/experimenting-with-html-minifier/#collapse_whitespace) | `false` |
+| `conservativeCollapse` | Always collapse to 1 space (never remove it entirely). Must be used in conjunction with `collapseWhitespace=true` | `false` |
+| `continueOnParseError` | [Handle parse errors](https://html.spec.whatwg.org/multipage/parsing.html#parse-errors) instead of aborting. | `false` |
+| `customAttrAssign` | Arrays of regex'es that allow to support custom attribute assign expressions (e.g. `''`) | `[ ]` |
+| `customAttrCollapse` | Regex that specifies custom attribute to strip newlines from (e.g. `/ng-class/`) | |
+| `customAttrSurround` | Arrays of regex'es that allow to support custom attribute surround expressions (e.g. ``) | `[ ]` |
+| `customEventAttributes` | Arrays of regex'es that allow to support custom event attributes for `minifyJS` (e.g. `ng-click`) | `[ /^on[a-z]{3,}$/ ]` |
+| `decodeEntities` | Use direct Unicode characters whenever possible | `false` |
+| `html5` | Parse input according to HTML5 specifications | `true` |
+| `ignoreCustomComments` | Array of regex'es that allow to ignore certain comments, when matched | `[ /^!/ ]` |
+| `ignoreCustomFragments` | Array of regex'es that allow to ignore certain fragments, when matched (e.g. ``, `{{ ... }}`, etc.) | `[ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ]` |
+| `includeAutoGeneratedTags` | Insert tags generated by HTML parser | `true` |
+| `keepClosingSlash` | Keep the trailing slash on singleton elements | `false` |
+| `maxLineLength` | Specify a maximum line length. Compressed output will be split by newlines at valid HTML split-points |
+| `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)`) |
+| `minifyJS` | Minify JavaScript in script elements and event attributes (uses [UglifyJS](https://github.com/mishoo/UglifyJS2)) | `false` (could be `true`, `Object`, `Function(text, inline)`) |
+| `minifyURLs` | Minify URLs in various attributes (uses [relateurl](https://github.com/stevenvachon/relateurl)) | `false` (could be `String`, `Object`, `Function(text)`) |
+| `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` |
+| `preventAttributesEscaping` | Prevents the escaping of the values of attributes | `false` |
+| `processConditionalComments` | Process contents of conditional comments through minifier | `false` |
+| `processScripts` | Array of strings corresponding to types of script elements to process through minifier (e.g. `text/ng-template`, `text/x-handlebars-template`, etc.) | `[ ]` |
+| `quoteCharacter` | Type of quote to use for attribute values (' or ") | |
+| `removeAttributeQuotes` | [Remove quotes around attributes when possible](http://perfectionkills.com/experimenting-with-html-minifier/#remove_attribute_quotes) | `false` |
+| `removeComments` | [Strip HTML comments](http://perfectionkills.com/experimenting-with-html-minifier/#remove_comments) | `false` |
+| `removeEmptyAttributes` | [Remove all attributes with whitespace-only values](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_or_blank_attributes) | `false` (could be `true`, `Function(attrName, tag)`) |
+| `removeEmptyElements` | [Remove all elements with empty contents](http://perfectionkills.com/experimenting-with-html-minifier/#remove_empty_elements) | `false` |
+| `removeOptionalTags` | [Remove optional tags](http://perfectionkills.com/experimenting-with-html-minifier/#remove_optional_tags) | `false` |
+| `removeRedundantAttributes` | [Remove attributes when value matches default.](http://perfectionkills.com/experimenting-with-html-minifier/#remove_redundant_attributes) | `false` |
+| `removeScriptTypeAttributes` | Remove `type="text/javascript"` from `script` tags. Other `type` attribute values are left intact | `false` |
+| `removeStyleLinkTypeAttributes`| Remove `type="text/css"` from `style` and `link` tags. Other `type` attribute values are left intact | `false` |
+| `removeTagWhitespace` | Remove space between attributes whenever possible. **Note that this will result in invalid HTML!** | `false` |
+| `sortAttributes` | [Sort attributes by frequency](#sorting-attributes--style-classes) | `false` |
+| `sortClassName` | [Sort style classes by frequency](#sorting-attributes--style-classes) | `false` |
+| `trimCustomFragments` | Trim white space around `ignoreCustomFragments`. | `false` |
+| `useShortDoctype` | [Replaces the `doctype` with the short (HTML5) doctype](http://perfectionkills.com/experimenting-with-html-minifier/#use_short_doctype) | `false` |
+
+### Sorting attributes / style classes
+
+Minifier options like `sortAttributes` and `sortClassName` won't impact the plain-text size of the output. However, they form long repetitive chains of characters that should improve compression ratio of gzip used in HTTP compression.
+
+## Special cases
+
+### Ignoring chunks of markup
+
+If you have chunks of markup you would like preserved, you can wrap them ``.
+
+### Preserving SVG tags
+
+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.
+
+### Working with invalid markup
+
+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.
+
+Input markup (e.g. `foo`)
+
+↓
+
+Internal representation of markup in a form of tree (e.g. `{ tag: "p", attr: "id", children: ["foo"] }`)
+
+↓
+
+Transformation of internal representation (e.g. removal of `id` attribute)
+
+↓
+
+Output of resulting markup (e.g. `
foo
`)
+
+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.
+
+## Installation Instructions
+
+From NPM for use as a command line app:
+
+```shell
+npm install html-minifier -g
+```
+
+From NPM for programmatic use:
+
+```shell
+npm install html-minifier
+```
+
+From Git:
+
+```shell
+git clone git://github.com/kangax/html-minifier.git
+cd html-minifier
+npm link .
+```
+
+## Usage
+
+Note that almost all options are disabled by default. For command line usage please see `html-minifier --help` for a list of available options. Experiment and find what works best for you and your project.
+
+* **Sample command line:** ``html-minifier --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --remove-tag-whitespace --use-short-doctype --minify-css true --minify-js true``
+
+### Node.js
+
+```js
+var minify = require('html-minifier').minify;
+var result = minify('foo
', {
+ removeAttributeQuotes: true
+});
+result; // 'foo
'
+```
+
+## Running benchmarks
+
+Benchmarks for minified HTML:
+
+```shell
+node benchmark.js
+```
diff --git a/node_modules/html-minifier/cli.js b/node_modules/html-minifier/cli.js
new file mode 100755
index 0000000000..2d0a9b30c1
--- /dev/null
+++ b/node_modules/html-minifier/cli.js
@@ -0,0 +1,311 @@
+#!/usr/bin/env node
+/**
+ * html-minifier CLI tool
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2016 Zoltan Frombach
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+ * the Software, and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+ * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+ * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+ * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+'use strict';
+
+var camelCase = require('camel-case');
+var fs = require('fs');
+var info = require('./package.json');
+var minify = require('./' + info.main).minify;
+var paramCase = require('param-case');
+var path = require('path');
+var program = require('commander');
+
+program._name = info.name;
+program.version(info.version);
+
+function fatal(message) {
+ console.error(message);
+ process.exit(1);
+}
+
+/**
+ * JSON does not support regexes, so, e.g., JSON.parse() will not create
+ * a RegExp from the JSON value `[ "/matchString/" ]`, which is
+ * technically just an array containing a string that begins and end with
+ * a forward slash. To get a RegExp from a JSON string, it must be
+ * constructed explicitly in JavaScript.
+ *
+ * The likelihood of actually wanting to match text that is enclosed in
+ * forward slashes is probably quite rare, so if forward slashes were
+ * included in an argument that requires a regex, the user most likely
+ * thought they were part of the syntax for specifying a regex.
+ *
+ * In the unlikely case that forward slashes are indeed desired in the
+ * search string, the user would need to enclose the expression in a
+ * second set of slashes:
+ *
+ * --customAttrSrround "[\"//matchString//\"]"
+ */
+function parseRegExp(value) {
+ if (value) {
+ return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
+ }
+}
+
+function parseJSON(value) {
+ if (value) {
+ try {
+ return JSON.parse(value);
+ }
+ catch (e) {
+ if (/^{/.test(value)) {
+ fatal('Could not parse JSON value \'' + value + '\'');
+ }
+ return value;
+ }
+ }
+}
+
+function parseJSONArray(value) {
+ if (value) {
+ value = parseJSON(value);
+ return Array.isArray(value) ? value : [value];
+ }
+}
+
+function parseJSONRegExpArray(value) {
+ value = parseJSONArray(value);
+ return value && value.map(parseRegExp);
+}
+
+function parseString(value) {
+ return value;
+}
+
+var mainOptions = {
+ caseSensitive: 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)',
+ collapseBooleanAttributes: 'Omit attribute values from boolean attributes',
+ collapseInlineTagWhitespace: 'Collapse white space around inline tag',
+ collapseWhitespace: 'Collapse white space that contributes to text nodes in a document tree.',
+ conservativeCollapse: 'Always collapse to 1 space (never remove it entirely)',
+ continueOnParseError: 'Handle parse errors instead of aborting',
+ customAttrAssign: ['Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'\')', parseJSONRegExpArray],
+ customAttrCollapse: ['Regex that specifies custom attribute to strip newlines from (e.g. /ng-class/)', parseRegExp],
+ customAttrSurround: ['Arrays of regex\'es that allow to support custom attribute surround expressions (e.g. )', parseJSONRegExpArray],
+ customEventAttributes: ['Arrays of regex\'es that allow to support custom event attributes for minifyJS (e.g. ng-click)', parseJSONRegExpArray],
+ decodeEntities: 'Use direct Unicode characters whenever possible',
+ html5: 'Parse input according to HTML5 specifications',
+ ignoreCustomComments: ['Array of regex\'es that allow to ignore certain comments, when matched', parseJSONRegExpArray],
+ ignoreCustomFragments: ['Array of regex\'es that allow to ignore certain fragments, when matched (e.g. , {{ ... }})', parseJSONRegExpArray],
+ includeAutoGeneratedTags: 'Insert tags generated by HTML parser',
+ keepClosingSlash: 'Keep the trailing slash on singleton elements',
+ maxLineLength: ['Max line length', parseInt],
+ minifyCSS: ['Minify CSS in style elements and style attributes (uses clean-css)', parseJSON],
+ minifyJS: ['Minify Javascript in script elements and on* attributes (uses uglify-js)', parseJSON],
+ minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
+ preserveLineBreaks: 'Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break.',
+ preventAttributesEscaping: 'Prevents the escaping of the values of attributes.',
+ processConditionalComments: 'Process contents of conditional comments through minifier',
+ processScripts: ['Array of strings corresponding to types of script elements to process through minifier (e.g. "text/ng-template", "text/x-handlebars-template", etc.)', parseJSONArray],
+ quoteCharacter: ['Type of quote to use for attribute values (\' or ")', parseString],
+ removeAttributeQuotes: 'Remove quotes around attributes when possible.',
+ removeComments: 'Strip HTML comments',
+ removeEmptyAttributes: 'Remove all attributes with whitespace-only values',
+ removeEmptyElements: 'Remove all elements with empty contents',
+ removeOptionalTags: 'Remove unrequired tags',
+ removeRedundantAttributes: 'Remove attributes when value matches default.',
+ removeScriptTypeAttributes: 'Remove type="text/javascript" from script tags. Other type attribute values are left intact.',
+ removeStyleLinkTypeAttributes: 'Remove type="text/css" from style and link tags. Other type attribute values are left intact.',
+ removeTagWhitespace: 'Remove space between attributes whenever possible',
+ sortAttributes: 'Sort attributes by frequency',
+ sortClassName: 'Sort style classes by frequency',
+ trimCustomFragments: 'Trim white space around ignoreCustomFragments.',
+ useShortDoctype: 'Replaces the doctype with the short (HTML5) doctype'
+};
+var mainOptionKeys = Object.keys(mainOptions);
+mainOptionKeys.forEach(function(key) {
+ var option = mainOptions[key];
+ if (Array.isArray(option)) {
+ key = key === 'minifyURLs' ? '--minify-urls' : '--' + paramCase(key);
+ key += option[1] === parseJSON ? ' [value]' : ' ';
+ program.option(key, option[0], option[1]);
+ }
+ else if (~['html5', 'includeAutoGeneratedTags'].indexOf(key)) {
+ program.option('--no-' + paramCase(key), option);
+ }
+ else {
+ program.option('--' + paramCase(key), option);
+ }
+});
+program.option('-o --output ', 'Specify output file (if not specified STDOUT will be used for output)');
+
+function readFile(file) {
+ try {
+ return fs.readFileSync(file, { encoding: 'utf8' });
+ }
+ catch (e) {
+ fatal('Cannot read ' + file + '\n' + e.message);
+ }
+}
+
+var config = {};
+program.option('-c --config-file ', 'Use config file', function(configPath) {
+ var data = readFile(configPath);
+ try {
+ config = JSON.parse(data);
+ }
+ catch (je) {
+ try {
+ config = require(path.resolve(configPath));
+ }
+ catch (ne) {
+ fatal('Cannot read the specified config file.\nAs JSON: ' + je.message + '\nAs module: ' + ne.message);
+ }
+ }
+ mainOptionKeys.forEach(function(key) {
+ if (key in config) {
+ var option = mainOptions[key];
+ if (Array.isArray(option)) {
+ var value = config[key];
+ config[key] = option[1](typeof value === 'string' ? value : JSON.stringify(value));
+ }
+ }
+ });
+});
+program.option('--input-dir ', 'Specify an input directory');
+program.option('--output-dir ', 'Specify an output directory');
+program.option('--file-ext ', 'Specify an extension to be read, ex: html');
+var content;
+program.arguments('[files...]').action(function(files) {
+ content = files.map(readFile).join('');
+}).parse(process.argv);
+
+function createOptions() {
+ var options = {};
+ mainOptionKeys.forEach(function(key) {
+ var param = program[key === 'minifyURLs' ? 'minifyUrls' : camelCase(key)];
+ if (typeof param !== 'undefined') {
+ options[key] = param;
+ }
+ else if (key in config) {
+ options[key] = config[key];
+ }
+ });
+ return options;
+}
+
+function mkdir(outputDir, callback) {
+ fs.mkdir(outputDir, function(err) {
+ if (err) {
+ switch (err.code) {
+ case 'ENOENT':
+ return mkdir(path.join(outputDir, '..'), function() {
+ mkdir(outputDir, callback);
+ });
+ case 'EEXIST':
+ break;
+ default:
+ fatal('Cannot create directory ' + outputDir + '\n' + err.message);
+ }
+ }
+ callback();
+ });
+}
+
+function processFile(inputFile, outputFile) {
+ fs.readFile(inputFile, { encoding: 'utf8' }, function(err, data) {
+ if (err) {
+ fatal('Cannot read ' + inputFile + '\n' + err.message);
+ }
+ var minified;
+ try {
+ minified = minify(data, createOptions());
+ }
+ catch (e) {
+ fatal('Minification error on ' + inputFile + '\n' + e.message);
+ }
+ fs.writeFile(outputFile, minified, { encoding: 'utf8' }, function(err) {
+ if (err) {
+ fatal('Cannot write ' + outputFile + '\n' + err.message);
+ }
+ });
+ });
+}
+
+function processDirectory(inputDir, outputDir, fileExt) {
+ fs.readdir(inputDir, function(err, files) {
+ if (err) {
+ fatal('Cannot read directory ' + inputDir + '\n' + err.message);
+ }
+ files.forEach(function(file) {
+ var inputFile = path.join(inputDir, file);
+ var outputFile = path.join(outputDir, file);
+ fs.stat(inputFile, function(err, stat) {
+ if (err) {
+ fatal('Cannot read ' + inputFile + '\n' + err.message);
+ }
+ else if (stat.isDirectory()) {
+ processDirectory(inputFile, outputFile, fileExt);
+ }
+ else if (!fileExt || path.extname(file) === '.' + fileExt) {
+ mkdir(outputDir, function() {
+ processFile(inputFile, outputFile);
+ });
+ }
+ });
+ });
+ });
+}
+
+function writeMinify() {
+ var minified;
+ try {
+ minified = minify(content, createOptions());
+ }
+ catch (e) {
+ fatal('Minification error:\n' + e.message);
+ }
+ (program.output ? fs.createWriteStream(program.output).on('error', function(e) {
+ fatal('Cannot write ' + program.output + '\n' + e.message);
+ }) : process.stdout).write(minified);
+}
+
+var inputDir = program.inputDir;
+var outputDir = program.outputDir;
+var fileExt = program.fileExt;
+if (inputDir || outputDir) {
+ if (!inputDir) {
+ fatal('The option output-dir needs to be used with the option input-dir. If you are working with a single file, use -o.');
+ }
+ else if (!outputDir) {
+ fatal('You need to specify where to write the output files with the option --output-dir');
+ }
+ processDirectory(inputDir, outputDir, fileExt);
+}
+// Minifying one or more files specified on the CMD line
+else if (content) {
+ writeMinify();
+}
+// Minifying input coming from STDIN
+else {
+ content = '';
+ process.stdin.setEncoding('utf8');
+ process.stdin.on('data', function(data) {
+ content += data;
+ }).on('end', writeMinify);
+}
diff --git a/node_modules/html-minifier/package.json b/node_modules/html-minifier/package.json
new file mode 100644
index 0000000000..2e67361ab4
--- /dev/null
+++ b/node_modules/html-minifier/package.json
@@ -0,0 +1,86 @@
+{
+ "name": "html-minifier",
+ "description": "Highly configurable, well-tested, JavaScript-based HTML minifier.",
+ "version": "4.0.0",
+ "keywords": [
+ "cli",
+ "compress",
+ "compressor",
+ "css",
+ "html",
+ "htmlmin",
+ "javascript",
+ "min",
+ "minification",
+ "minifier",
+ "minify",
+ "optimize",
+ "optimizer",
+ "pack",
+ "packer",
+ "parse",
+ "parser",
+ "uglifier",
+ "uglify"
+ ],
+ "homepage": "https://kangax.github.io/html-minifier/",
+ "author": "Juriy \"kangax\" Zaytsev",
+ "maintainers": [
+ "Alex Lam ",
+ "Juriy Zaytsev (http://perfectionkills.com/)"
+ ],
+ "contributors": [
+ "Gilmore Davidson (https://github.com/gilmoreorless)",
+ "Hugo Wetterberg ",
+ "Zoltan Frombach "
+ ],
+ "license": "MIT",
+ "bin": {
+ "html-minifier": "./cli.js"
+ },
+ "main": "src/htmlminifier.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/kangax/html-minifier.git"
+ },
+ "bugs": {
+ "url": "https://github.com/kangax/html-minifier/issues"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "scripts": {
+ "dist": "grunt dist",
+ "test": "grunt test"
+ },
+ "dependencies": {
+ "camel-case": "^3.0.0",
+ "clean-css": "^4.2.1",
+ "commander": "^2.19.0",
+ "he": "^1.2.0",
+ "param-case": "^2.1.1",
+ "relateurl": "^0.2.7",
+ "uglify-js": "^3.5.1"
+ },
+ "devDependencies": {
+ "grunt": "^1.0.4",
+ "grunt-browserify": "^5.3.0",
+ "grunt-contrib-uglify": "^4.0.1",
+ "grunt-eslint": "^21.0.0",
+ "phantomjs-prebuilt": "^2.1.16",
+ "qunit": "^2.9.2"
+ },
+ "benchmarkDependencies": {
+ "brotli": "^1.3.2",
+ "chalk": "^2.4.2",
+ "cli-table": "^0.3.1",
+ "lzma": "^2.3.2",
+ "minimize": "^2.2.0",
+ "progress": "^2.0.3"
+ },
+ "files": [
+ "src/*.js",
+ "cli.js",
+ "sample-cli-config-file.conf"
+ ]
+}
diff --git a/node_modules/html-minifier/sample-cli-config-file.conf b/node_modules/html-minifier/sample-cli-config-file.conf
new file mode 100644
index 0000000000..8b8748cc9e
--- /dev/null
+++ b/node_modules/html-minifier/sample-cli-config-file.conf
@@ -0,0 +1,40 @@
+{
+ "caseSensitive": false,
+ "collapseBooleanAttributes": true,
+ "collapseInlineTagWhitespace": false,
+ "collapseWhitespace": true,
+ "conservativeCollapse": false,
+ "continueOnParseError": true,
+ "customAttrCollapse": ".*",
+ "decodeEntities": true,
+ "html5": true,
+ "ignoreCustomFragments": [
+ "<#[\\s\\S]*?#>",
+ "<%[\\s\\S]*?%>",
+ "<\\?[\\s\\S]*?\\?>"
+ ],
+ "includeAutoGeneratedTags": false,
+ "keepClosingSlash": false,
+ "maxLineLength": 0,
+ "minifyCSS": true,
+ "minifyJS": true,
+ "preserveLineBreaks": false,
+ "preventAttributesEscaping": false,
+ "processConditionalComments": true,
+ "processScripts": [
+ "text/html"
+ ],
+ "removeAttributeQuotes": true,
+ "removeComments": true,
+ "removeEmptyAttributes": true,
+ "removeEmptyElements": true,
+ "removeOptionalTags": true,
+ "removeRedundantAttributes": true,
+ "removeScriptTypeAttributes": true,
+ "removeStyleLinkTypeAttributes": true,
+ "removeTagWhitespace": true,
+ "sortAttributes": true,
+ "sortClassName": true,
+ "trimCustomFragments": true,
+ "useShortDoctype": true
+}
diff --git a/node_modules/html-minifier/src/htmlminifier.js b/node_modules/html-minifier/src/htmlminifier.js
new file mode 100644
index 0000000000..d7efa99148
--- /dev/null
+++ b/node_modules/html-minifier/src/htmlminifier.js
@@ -0,0 +1,1344 @@
+'use strict';
+
+var CleanCSS = require('clean-css');
+var decode = require('he').decode;
+var HTMLParser = require('./htmlparser').HTMLParser;
+var RelateUrl = require('relateurl');
+var TokenChain = require('./tokenchain');
+var UglifyJS = require('uglify-js');
+var utils = require('./utils');
+
+function trimWhitespace(str) {
+ return str && str.replace(/^[ \n\r\t\f]+/, '').replace(/[ \n\r\t\f]+$/, '');
+}
+
+function collapseWhitespaceAll(str) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ return str && str.replace(/[ \n\r\t\f\xA0]+/g, function(spaces) {
+ return spaces === '\t' ? '\t' : spaces.replace(/(^|\xA0+)[^\xA0]+/g, '$1 ');
+ });
+}
+
+function collapseWhitespace(str, options, trimLeft, trimRight, collapseAll) {
+ var lineBreakBefore = '', lineBreakAfter = '';
+
+ if (options.preserveLineBreaks) {
+ str = str.replace(/^[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*/, function() {
+ lineBreakBefore = '\n';
+ return '';
+ }).replace(/[ \n\r\t\f]*?[\n\r][ \n\r\t\f]*$/, function() {
+ lineBreakAfter = '\n';
+ return '';
+ });
+ }
+
+ if (trimLeft) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ str = str.replace(/^[ \n\r\t\f\xA0]+/, function(spaces) {
+ var conservative = !lineBreakBefore && options.conservativeCollapse;
+ if (conservative && spaces === '\t') {
+ return '\t';
+ }
+ return spaces.replace(/^[^\xA0]+/, '').replace(/(\xA0+)[^\xA0]+/g, '$1 ') || (conservative ? ' ' : '');
+ });
+ }
+
+ if (trimRight) {
+ // Non-breaking space is specifically handled inside the replacer function here:
+ str = str.replace(/[ \n\r\t\f\xA0]+$/, function(spaces) {
+ var conservative = !lineBreakAfter && options.conservativeCollapse;
+ if (conservative && spaces === '\t') {
+ return '\t';
+ }
+ return spaces.replace(/[^\xA0]+(\xA0+)/g, ' $1').replace(/[^\xA0]+$/, '') || (conservative ? ' ' : '');
+ });
+ }
+
+ if (collapseAll) {
+ // strip non space whitespace then compress spaces to one
+ str = collapseWhitespaceAll(str);
+ }
+
+ return lineBreakBefore + str + lineBreakAfter;
+}
+
+var createMapFromString = utils.createMapFromString;
+// non-empty tags that will maintain whitespace around them
+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');
+// non-empty tags that will maintain whitespace within them
+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');
+// self-closing tags that will maintain whitespace around them
+var selfClosingInlineTags = createMapFromString('comment,img,input,wbr');
+
+function collapseWhitespaceSmart(str, prevTag, nextTag, options) {
+ var trimLeft = prevTag && !selfClosingInlineTags(prevTag);
+ if (trimLeft && !options.collapseInlineTagWhitespace) {
+ trimLeft = prevTag.charAt(0) === '/' ? !inlineTags(prevTag.slice(1)) : !inlineTextTags(prevTag);
+ }
+ var trimRight = nextTag && !selfClosingInlineTags(nextTag);
+ if (trimRight && !options.collapseInlineTagWhitespace) {
+ trimRight = nextTag.charAt(0) === '/' ? !inlineTextTags(nextTag.slice(1)) : !inlineTags(nextTag);
+ }
+ return collapseWhitespace(str, options, trimLeft, trimRight, prevTag && nextTag);
+}
+
+function isConditionalComment(text) {
+ return /^\[if\s[^\]]+]|\[endif]$/.test(text);
+}
+
+function isIgnoredComment(text, options) {
+ for (var i = 0, len = options.ignoreCustomComments.length; i < len; i++) {
+ if (options.ignoreCustomComments[i].test(text)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function isEventAttribute(attrName, options) {
+ var patterns = options.customEventAttributes;
+ if (patterns) {
+ for (var i = patterns.length; i--;) {
+ if (patterns[i].test(attrName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return /^on[a-z]{3,}$/.test(attrName);
+}
+
+function canRemoveAttributeQuotes(value) {
+ // https://mathiasbynens.be/notes/unquoted-attribute-values
+ return /^[^ \t\n\f\r"'`=<>]+$/.test(value);
+}
+
+function attributesInclude(attributes, attribute) {
+ for (var i = attributes.length; i--;) {
+ if (attributes[i].name.toLowerCase() === attribute) {
+ return true;
+ }
+ }
+ return false;
+}
+
+function isAttributeRedundant(tag, attrName, attrValue, attrs) {
+ attrValue = attrValue ? trimWhitespace(attrValue.toLowerCase()) : '';
+
+ return (
+ tag === 'script' &&
+ attrName === 'language' &&
+ attrValue === 'javascript' ||
+
+ tag === 'form' &&
+ attrName === 'method' &&
+ attrValue === 'get' ||
+
+ tag === 'input' &&
+ attrName === 'type' &&
+ attrValue === 'text' ||
+
+ tag === 'script' &&
+ attrName === 'charset' &&
+ !attributesInclude(attrs, 'src') ||
+
+ tag === 'a' &&
+ attrName === 'name' &&
+ attributesInclude(attrs, 'id') ||
+
+ tag === 'area' &&
+ attrName === 'shape' &&
+ attrValue === 'rect'
+ );
+}
+
+// https://mathiasbynens.be/demo/javascript-mime-type
+// https://developer.mozilla.org/en/docs/Web/HTML/Element/script#attr-type
+var executableScriptsMimetypes = utils.createMap([
+ 'text/javascript',
+ 'text/ecmascript',
+ 'text/jscript',
+ 'application/javascript',
+ 'application/x-javascript',
+ 'application/ecmascript'
+]);
+
+function isScriptTypeAttribute(attrValue) {
+ attrValue = trimWhitespace(attrValue.split(/;/, 2)[0]).toLowerCase();
+ return attrValue === '' || executableScriptsMimetypes(attrValue);
+}
+
+function isExecutableScript(tag, attrs) {
+ if (tag !== 'script') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ var attrName = attrs[i].name.toLowerCase();
+ if (attrName === 'type') {
+ return isScriptTypeAttribute(attrs[i].value);
+ }
+ }
+ return true;
+}
+
+function isStyleLinkTypeAttribute(attrValue) {
+ attrValue = trimWhitespace(attrValue).toLowerCase();
+ return attrValue === '' || attrValue === 'text/css';
+}
+
+function isStyleSheet(tag, attrs) {
+ if (tag !== 'style') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ var attrName = attrs[i].name.toLowerCase();
+ if (attrName === 'type') {
+ return isStyleLinkTypeAttribute(attrs[i].value);
+ }
+ }
+ return true;
+}
+
+var isSimpleBoolean = createMapFromString('allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible');
+var isBooleanValue = createMapFromString('true,false');
+
+function isBooleanAttribute(attrName, attrValue) {
+ return isSimpleBoolean(attrName) || attrName === 'draggable' && !isBooleanValue(attrValue);
+}
+
+function isUriTypeAttribute(attrName, tag) {
+ return (
+ /^(?:a|area|link|base)$/.test(tag) && attrName === 'href' ||
+ tag === 'img' && /^(?:src|longdesc|usemap)$/.test(attrName) ||
+ tag === 'object' && /^(?:classid|codebase|data|usemap)$/.test(attrName) ||
+ tag === 'q' && attrName === 'cite' ||
+ tag === 'blockquote' && attrName === 'cite' ||
+ (tag === 'ins' || tag === 'del') && attrName === 'cite' ||
+ tag === 'form' && attrName === 'action' ||
+ tag === 'input' && (attrName === 'src' || attrName === 'usemap') ||
+ tag === 'head' && attrName === 'profile' ||
+ tag === 'script' && (attrName === 'src' || attrName === 'for')
+ );
+}
+
+function isNumberTypeAttribute(attrName, tag) {
+ return (
+ /^(?:a|area|object|button)$/.test(tag) && attrName === 'tabindex' ||
+ tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex') ||
+ tag === 'select' && (attrName === 'size' || attrName === 'tabindex') ||
+ tag === 'textarea' && /^(?:rows|cols|tabindex)$/.test(attrName) ||
+ tag === 'colgroup' && attrName === 'span' ||
+ tag === 'col' && attrName === 'span' ||
+ (tag === 'th' || tag === 'td') && (attrName === 'rowspan' || attrName === 'colspan')
+ );
+}
+
+function isLinkType(tag, attrs, value) {
+ if (tag !== 'link') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ if (attrs[i].name === 'rel' && attrs[i].value === value) {
+ return true;
+ }
+ }
+}
+
+function isMediaQuery(tag, attrs, attrName) {
+ return attrName === 'media' && (isLinkType(tag, attrs, 'stylesheet') || isStyleSheet(tag, attrs));
+}
+
+var srcsetTags = createMapFromString('img,source');
+
+function isSrcset(attrName, tag) {
+ return attrName === 'srcset' && srcsetTags(tag);
+}
+
+function cleanAttributeValue(tag, attrName, attrValue, options, attrs) {
+ if (isEventAttribute(attrName, options)) {
+ attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
+ return options.minifyJS(attrValue, true);
+ }
+ else if (attrName === 'class') {
+ attrValue = trimWhitespace(attrValue);
+ if (options.sortClassName) {
+ attrValue = options.sortClassName(attrValue);
+ }
+ else {
+ attrValue = collapseWhitespaceAll(attrValue);
+ }
+ return attrValue;
+ }
+ else if (isUriTypeAttribute(attrName, tag)) {
+ attrValue = trimWhitespace(attrValue);
+ return isLinkType(tag, attrs, 'canonical') ? attrValue : options.minifyURLs(attrValue);
+ }
+ else if (isNumberTypeAttribute(attrName, tag)) {
+ return trimWhitespace(attrValue);
+ }
+ else if (attrName === 'style') {
+ attrValue = trimWhitespace(attrValue);
+ if (attrValue) {
+ if (/;$/.test(attrValue) && !/?[0-9a-zA-Z]+;$/.test(attrValue)) {
+ attrValue = attrValue.replace(/\s*;$/, ';');
+ }
+ attrValue = options.minifyCSS(attrValue, 'inline');
+ }
+ return attrValue;
+ }
+ else if (isSrcset(attrName, tag)) {
+ // https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-srcset
+ attrValue = trimWhitespace(attrValue).split(/\s+,\s*|\s*,\s+/).map(function(candidate) {
+ var url = candidate;
+ var descriptor = '';
+ var match = candidate.match(/\s+([1-9][0-9]*w|[0-9]+(?:\.[0-9]+)?x)$/);
+ if (match) {
+ url = url.slice(0, -match[0].length);
+ var num = +match[1].slice(0, -1);
+ var suffix = match[1].slice(-1);
+ if (num !== 1 || suffix !== 'x') {
+ descriptor = ' ' + num + suffix;
+ }
+ }
+ return options.minifyURLs(url) + descriptor;
+ }).join(', ');
+ }
+ else if (isMetaViewport(tag, attrs) && attrName === 'content') {
+ attrValue = attrValue.replace(/\s+/g, '').replace(/[0-9]+\.[0-9]+/g, function(numString) {
+ // "0.90000" -> "0.9"
+ // "1.0" -> "1"
+ // "1.0001" -> "1.0001" (unchanged)
+ return (+numString).toString();
+ });
+ }
+ else if (isContentSecurityPolicy(tag, attrs) && attrName.toLowerCase() === 'content') {
+ return collapseWhitespaceAll(attrValue);
+ }
+ else if (options.customAttrCollapse && options.customAttrCollapse.test(attrName)) {
+ attrValue = attrValue.replace(/\n+|\r+|\s{2,}/g, '');
+ }
+ else if (tag === 'script' && attrName === 'type') {
+ attrValue = trimWhitespace(attrValue.replace(/\s*;\s*/g, ';'));
+ }
+ else if (isMediaQuery(tag, attrs, attrName)) {
+ attrValue = trimWhitespace(attrValue);
+ return options.minifyCSS(attrValue, 'media');
+ }
+ return attrValue;
+}
+
+function isMetaViewport(tag, attrs) {
+ if (tag !== 'meta') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ if (attrs[i].name === 'name' && attrs[i].value === 'viewport') {
+ return true;
+ }
+ }
+}
+
+function isContentSecurityPolicy(tag, attrs) {
+ if (tag !== 'meta') {
+ return false;
+ }
+ for (var i = 0, len = attrs.length; i < len; i++) {
+ if (attrs[i].name.toLowerCase() === 'http-equiv' && attrs[i].value.toLowerCase() === 'content-security-policy') {
+ return true;
+ }
+ }
+}
+
+function ignoreCSS(id) {
+ return '/* clean-css ignore:start */' + id + '/* clean-css ignore:end */';
+}
+
+// Wrap CSS declarations for CleanCSS > 3.x
+// See https://github.com/jakubpawlowicz/clean-css/issues/418
+function wrapCSS(text, type) {
+ switch (type) {
+ case 'inline':
+ return '*{' + text + '}';
+ case 'media':
+ return '@media ' + text + '{a{top:0}}';
+ default:
+ return text;
+ }
+}
+
+function unwrapCSS(text, type) {
+ var matches;
+ switch (type) {
+ case 'inline':
+ matches = text.match(/^\*\{([\s\S]*)\}$/);
+ break;
+ case 'media':
+ matches = text.match(/^@media ([\s\S]*?)\s*{[\s\S]*}$/);
+ break;
+ }
+ return matches ? matches[1] : text;
+}
+
+function cleanConditionalComment(comment, options) {
+ return options.processConditionalComments ? comment.replace(/^(\[if\s[^\]]+]>)([\s\S]*?)( -1) {
+ return minify(text, options);
+ }
+ }
+ return text;
+}
+
+// Tag omission rules from https://html.spec.whatwg.org/multipage/syntax.html#optional-tags
+// with the following deviations:
+// - retain if followed by