diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 |
commit | de98e0b232509d5f40c135d540a70e415272ff85 (patch) | |
tree | a79222a5b58484ab3b80d18efcaaa7ccc4769b33 /node_modules/html-minifier/cli.js | |
parent | e0c9d480a73fa629c1e4a47d3e721f1d2d345406 (diff) |
node_modules
Diffstat (limited to 'node_modules/html-minifier/cli.js')
-rwxr-xr-x | node_modules/html-minifier/cli.js | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/node_modules/html-minifier/cli.js b/node_modules/html-minifier/cli.js new file mode 100755 index 000000000..aba7ec876 --- /dev/null +++ b/node_modules/html-minifier/cli.js @@ -0,0 +1,309 @@ +#!/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)', + customAttrAssign: ['Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'<div flex?="{{mode != cover}}"></div>\')', 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. <input {{#if value}}checked="checked"{{/if}}>)', 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. <?php ... ?>, {{ ... }})', 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]; + key = '--' + paramCase(key); + if (Array.isArray(option)) { + var optional = option[1] === parseJSON; + program.option(key + (optional ? ' [value]' : ' <value>'), option[0], option[1]); + } + else { + program.option(key, option); + } +}); +program.option('-o --output <file>', 'Specify output file (if not specified STDOUT will be used for output)', function(outputPath) { + return fs.createWriteStream(outputPath).on('error', function(e) { + fatal('Cannot write ' + outputPath + '\n' + e.message); + }); +}, process.stdout); + +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 <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 <dir>', 'Specify an input directory'); +program.option('--output-dir <dir>', 'Specify an output directory'); +program.option('--file-ext <text>', '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[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.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 (typeof content === 'string') { + writeMinify(); +} +// Minifying input coming from STDIN +else { + content = ''; + process.stdin.setEncoding('utf8'); + process.stdin.on('data', function(data) { + content += data; + }).on('end', writeMinify); +} |