source: imaps-frontend/node_modules/html-minifier-terser/cli.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100755
File size: 11.8 KB
Line 
1#!/usr/bin/env node
2/**
3 * html-minifier-terser CLI tool
4 *
5 * The MIT License (MIT)
6 *
7 * Copyright (c) 2014-2016 Zoltan Frombach
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy of
10 * this software and associated documentation files (the "Software"), to deal in
11 * the Software without restriction, including without limitation the rights to
12 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
13 * the Software, and to permit persons to whom the Software is furnished to do so,
14 * subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in all
17 * copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
21 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
22 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
23 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 */
27
28'use strict';
29
30var camelCase = require('camel-case').camelCase;
31var fs = require('fs');
32var info = require('./package.json');
33var minify = require('./' + info.main).minify;
34var paramCase = require('param-case').paramCase;
35var path = require('path');
36var { Command } = require('commander');
37
38const program = new Command();
39program.name(info.name);
40program.version(info.version);
41
42function fatal(message) {
43 console.error(message);
44 process.exit(1);
45}
46
47/**
48 * JSON does not support regexes, so, e.g., JSON.parse() will not create
49 * a RegExp from the JSON value `[ "/matchString/" ]`, which is
50 * technically just an array containing a string that begins and end with
51 * a forward slash. To get a RegExp from a JSON string, it must be
52 * constructed explicitly in JavaScript.
53 *
54 * The likelihood of actually wanting to match text that is enclosed in
55 * forward slashes is probably quite rare, so if forward slashes were
56 * included in an argument that requires a regex, the user most likely
57 * thought they were part of the syntax for specifying a regex.
58 *
59 * In the unlikely case that forward slashes are indeed desired in the
60 * search string, the user would need to enclose the expression in a
61 * second set of slashes:
62 *
63 * --customAttrSrround "[\"//matchString//\"]"
64 */
65function parseRegExp(value) {
66 if (value) {
67 return new RegExp(value.replace(/^\/(.*)\/$/, '$1'));
68 }
69}
70
71function parseJSON(value) {
72 if (value) {
73 try {
74 return JSON.parse(value);
75 }
76 catch (e) {
77 if (/^{/.test(value)) {
78 fatal('Could not parse JSON value \'' + value + '\'');
79 }
80 return value;
81 }
82 }
83}
84
85function parseJSONArray(value) {
86 if (value) {
87 value = parseJSON(value);
88 return Array.isArray(value) ? value : [value];
89 }
90}
91
92function parseJSONRegExpArray(value) {
93 value = parseJSONArray(value);
94 return value && value.map(parseRegExp);
95}
96
97function parseString(value) {
98 return value;
99}
100
101var mainOptions = {
102 caseSensitive: 'Treat attributes in case sensitive manner (useful for SVG; e.g. viewBox)',
103 collapseBooleanAttributes: 'Omit attribute values from boolean attributes',
104 collapseInlineTagWhitespace: 'Collapse white space around inline tag',
105 collapseWhitespace: 'Collapse white space that contributes to text nodes in a document tree.',
106 conservativeCollapse: 'Always collapse to 1 space (never remove it entirely)',
107 continueOnParseError: 'Handle parse errors instead of aborting',
108 customAttrAssign: ['Arrays of regex\'es that allow to support custom attribute assign expressions (e.g. \'<div flex?="{{mode != cover}}"></div>\')', parseJSONRegExpArray],
109 customAttrCollapse: ['Regex that specifies custom attribute to strip newlines from (e.g. /ng-class/)', parseRegExp],
110 customAttrSurround: ['Arrays of regex\'es that allow to support custom attribute surround expressions (e.g. <input {{#if value}}checked="checked"{{/if}}>)', parseJSONRegExpArray],
111 customEventAttributes: ['Arrays of regex\'es that allow to support custom event attributes for minifyJS (e.g. ng-click)', parseJSONRegExpArray],
112 decodeEntities: 'Use direct Unicode characters whenever possible',
113 html5: 'Parse input according to HTML5 specifications',
114 ignoreCustomComments: ['Array of regex\'es that allow to ignore certain comments, when matched', parseJSONRegExpArray],
115 ignoreCustomFragments: ['Array of regex\'es that allow to ignore certain fragments, when matched (e.g. <?php ... ?>, {{ ... }})', parseJSONRegExpArray],
116 includeAutoGeneratedTags: 'Insert tags generated by HTML parser',
117 keepClosingSlash: 'Keep the trailing slash on singleton elements',
118 maxLineLength: ['Max line length', parseInt],
119 minifyCSS: ['Minify CSS in style elements and style attributes (uses clean-css)', parseJSON],
120 minifyJS: ['Minify Javascript in script elements and on* attributes (uses terser)', parseJSON],
121 minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON],
122 noNewlinesBeforeTagClose: 'Never add a newline before a tag that closes an element',
123 preserveLineBreaks: 'Always collapse to 1 line break (never remove it entirely) when whitespace between tags include a line break.',
124 preventAttributesEscaping: 'Prevents the escaping of the values of attributes.',
125 processConditionalComments: 'Process contents of conditional comments through minifier',
126 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],
127 quoteCharacter: ['Type of quote to use for attribute values (\' or ")', parseString],
128 removeAttributeQuotes: 'Remove quotes around attributes when possible.',
129 removeComments: 'Strip HTML comments',
130 removeEmptyAttributes: 'Remove all attributes with whitespace-only values',
131 removeEmptyElements: 'Remove all elements with empty contents',
132 removeOptionalTags: 'Remove unrequired tags',
133 removeRedundantAttributes: 'Remove attributes when value matches default.',
134 removeScriptTypeAttributes: 'Removes the following attributes from script tags: text/javascript, text/ecmascript, text/jscript, application/javascript, application/x-javascript, application/ecmascript. Other type attribute values are left intact',
135 removeStyleLinkTypeAttributes: 'Remove type="text/css" from style and link tags. Other type attribute values are left intact.',
136 removeTagWhitespace: 'Remove space between attributes whenever possible',
137 sortAttributes: 'Sort attributes by frequency',
138 sortClassName: 'Sort style classes by frequency',
139 trimCustomFragments: 'Trim white space around ignoreCustomFragments.',
140 useShortDoctype: 'Replaces the doctype with the short (HTML5) doctype'
141};
142var mainOptionKeys = Object.keys(mainOptions);
143mainOptionKeys.forEach(function(key) {
144 var option = mainOptions[key];
145 if (Array.isArray(option)) {
146 key = key === 'minifyURLs' ? '--minify-urls' : '--' + paramCase(key);
147 key += option[1] === parseJSON ? ' [value]' : ' <value>';
148 program.option(key, option[0], option[1]);
149 }
150 else if (~['html5', 'includeAutoGeneratedTags'].indexOf(key)) {
151 program.option('--no-' + paramCase(key), option);
152 }
153 else {
154 program.option('--' + paramCase(key), option);
155 }
156});
157program.option('-o --output <file>', 'Specify output file (if not specified STDOUT will be used for output)');
158
159function readFile(file) {
160 try {
161 return fs.readFileSync(file, { encoding: 'utf8' });
162 }
163 catch (e) {
164 fatal('Cannot read ' + file + '\n' + e.message);
165 }
166}
167
168var config = {};
169program.option('-c --config-file <file>', 'Use config file', function(configPath) {
170 var data = readFile(configPath);
171 try {
172 config = JSON.parse(data);
173 }
174 catch (je) {
175 try {
176 config = require(path.resolve(configPath));
177 }
178 catch (ne) {
179 fatal('Cannot read the specified config file.\nAs JSON: ' + je.message + '\nAs module: ' + ne.message);
180 }
181 }
182 mainOptionKeys.forEach(function(key) {
183 if (key in config) {
184 var option = mainOptions[key];
185 if (Array.isArray(option)) {
186 var value = config[key];
187 config[key] = option[1](typeof value === 'string' ? value : JSON.stringify(value));
188 }
189 }
190 });
191});
192program.option('--input-dir <dir>', 'Specify an input directory');
193program.option('--output-dir <dir>', 'Specify an output directory');
194program.option('--file-ext <text>', 'Specify an extension to be read, ex: html');
195var content;
196program.arguments('[files...]').action(function(files) {
197 content = files.map(readFile).join('');
198}).parse(process.argv);
199
200const programOptions = program.opts();
201
202function createOptions() {
203 var options = {};
204 mainOptionKeys.forEach(function(key) {
205 var param = programOptions[key === 'minifyURLs' ? 'minifyUrls' : camelCase(key)];
206 if (typeof param !== 'undefined') {
207 options[key] = param;
208 }
209 else if (key in config) {
210 options[key] = config[key];
211 }
212 });
213 return options;
214}
215
216function mkdir(outputDir, callback) {
217 fs.mkdir(outputDir, function(err) {
218 if (err) {
219 switch (err.code) {
220 case 'ENOENT':
221 return mkdir(path.join(outputDir, '..'), function() {
222 mkdir(outputDir, callback);
223 });
224 case 'EEXIST':
225 break;
226 default:
227 fatal('Cannot create directory ' + outputDir + '\n' + err.message);
228 }
229 }
230 callback();
231 });
232}
233
234function processFile(inputFile, outputFile) {
235 fs.readFile(inputFile, { encoding: 'utf8' }, async function(err, data) {
236 if (err) {
237 fatal('Cannot read ' + inputFile + '\n' + err.message);
238 }
239 var minified;
240 try {
241 minified = await minify(data, createOptions());
242 }
243 catch (e) {
244 fatal('Minification error on ' + inputFile + '\n' + e.message);
245 }
246 fs.writeFile(outputFile, minified, { encoding: 'utf8' }, function(err) {
247 if (err) {
248 fatal('Cannot write ' + outputFile + '\n' + err.message);
249 }
250 });
251 });
252}
253
254function processDirectory(inputDir, outputDir, fileExt) {
255 fs.readdir(inputDir, function(err, files) {
256 if (err) {
257 fatal('Cannot read directory ' + inputDir + '\n' + err.message);
258 }
259 files.forEach(function(file) {
260 var inputFile = path.join(inputDir, file);
261 var outputFile = path.join(outputDir, file);
262 fs.stat(inputFile, function(err, stat) {
263 if (err) {
264 fatal('Cannot read ' + inputFile + '\n' + err.message);
265 }
266 else if (stat.isDirectory()) {
267 processDirectory(inputFile, outputFile, fileExt);
268 }
269 else if (!fileExt || path.extname(file) === '.' + fileExt) {
270 mkdir(outputDir, function() {
271 processFile(inputFile, outputFile);
272 });
273 }
274 });
275 });
276 });
277}
278
279async function writeMinify() {
280 var minified;
281 try {
282 minified = await minify(content, createOptions());
283 }
284 catch (e) {
285 fatal('Minification error:\n' + e.message);
286 }
287 (programOptions.output ? fs.createWriteStream(programOptions.output).on('error', function(e) {
288 fatal('Cannot write ' + programOptions.output + '\n' + e.message);
289 }) : process.stdout).write(minified);
290}
291
292var inputDir = programOptions.inputDir;
293var outputDir = programOptions.outputDir;
294var fileExt = programOptions.fileExt;
295if (inputDir || outputDir) {
296 if (!inputDir) {
297 fatal('The option output-dir needs to be used with the option input-dir. If you are working with a single file, use -o.');
298 }
299 else if (!outputDir) {
300 fatal('You need to specify where to write the output files with the option --output-dir');
301 }
302 processDirectory(inputDir, outputDir, fileExt);
303}
304// Minifying one or more files specified on the CMD line
305else if (content) {
306 writeMinify();
307}
308// Minifying input coming from STDIN
309else {
310 content = '';
311 process.stdin.setEncoding('utf8');
312 process.stdin.on('data', function(data) {
313 content += data;
314 }).on('end', writeMinify);
315}
Note: See TracBrowser for help on using the repository browser.