Buckets:
| /** | |
| * html-minifier-terser 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. | |
| * | |
| */ | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { createRequire } from 'module'; | |
| import { camelCase } from 'camel-case'; | |
| import { paramCase } from 'param-case'; | |
| import { Command } from 'commander'; | |
| import { minify } from './src/htmlminifier.js'; | |
| const require = createRequire(import.meta.url); | |
| const pkg = require('./package.json'); | |
| const program = new Command(); | |
| program.name(pkg.name); | |
| program.version(pkg.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; | |
| } | |
| const 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. \'<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 terser)', parseJSON], | |
| minifyURLs: ['Minify URLs in various attributes (uses relateurl)', parseJSON], | |
| noNewlinesBeforeTagClose: 'Never add a newline before a tag that closes an element', | |
| 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: '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', | |
| 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' | |
| }; | |
| // configure commandline flags | |
| const mainOptionKeys = Object.keys(mainOptions); | |
| mainOptionKeys.forEach(function (key) { | |
| const option = mainOptions[key]; | |
| if (Array.isArray(option)) { | |
| key = key === 'minifyURLs' ? '--minify-urls' : '--' + paramCase(key); | |
| key += option[1] === parseJSON ? ' [value]' : ' <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 <file>', '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); | |
| } | |
| } | |
| let config = {}; | |
| program.option('-c --config-file <file>', 'Use config file', function (configPath) { | |
| const 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) { | |
| const option = mainOptions[key]; | |
| if (Array.isArray(option)) { | |
| const 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'); | |
| let content; | |
| program.arguments('[files...]').action(function (files) { | |
| content = files.map(readFile).join(''); | |
| }).parse(process.argv); | |
| const programOptions = program.opts(); | |
| function createOptions() { | |
| const options = {}; | |
| mainOptionKeys.forEach(function (key) { | |
| const param = programOptions[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, { recursive: true }, function (err) { | |
| if (err) { | |
| fatal('Cannot create directory ' + outputDir + '\n' + err.message); | |
| } | |
| callback(); | |
| }); | |
| } | |
| function processFile(inputFile, outputFile) { | |
| fs.readFile(inputFile, { encoding: 'utf8' }, async function (err, data) { | |
| if (err) { | |
| fatal('Cannot read ' + inputFile + '\n' + err.message); | |
| } | |
| let minified; | |
| try { | |
| minified = await 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) { | |
| const inputFile = path.join(inputDir, file); | |
| const 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); | |
| }); | |
| } | |
| }); | |
| }); | |
| }); | |
| } | |
| const writeMinify = async () => { | |
| const minifierOptions = createOptions(); | |
| let minified; | |
| try { | |
| minified = await minify(content, minifierOptions); | |
| } catch (e) { | |
| fatal('Minification error:\n' + e.message); | |
| } | |
| let stream = process.stdout; | |
| if (programOptions.output) { | |
| stream = fs.createWriteStream(programOptions.output) | |
| .on('error', (e) => { | |
| fatal('Cannot write ' + programOptions.output + '\n' + e.message); | |
| }); | |
| } | |
| stream.write(minified); | |
| }; | |
| const { inputDir, outputDir, fileExt } = programOptions; | |
| 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); | |
| } else if (content) { // Minifying one or more files specified on the CMD line | |
| writeMinify(); | |
| } else { // Minifying input coming from STDIN | |
| content = ''; | |
| process.stdin.setEncoding('utf8'); | |
| process.stdin.on('data', function (data) { | |
| content += data; | |
| }).on('end', writeMinify); | |
| } | |
Xet Storage Details
- Size:
- 12 kB
- Xet hash:
- 1b98ccba6684b0414778b556d6aeda66e78e5cc9bb34499ca3fb52e8f6f506de
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.