html-minifier-next 7.5.3 → 8.0.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/README.md +60 -24
- package/cli.js +178 -55
- package/dist/types/htmlminifier.d.ts +24 -4
- package/dist/types/htmlminifier.d.ts.map +1 -1
- package/dist/types/lib/attributes.d.ts +2 -2
- package/dist/types/lib/attributes.d.ts.map +1 -1
- package/dist/types/lib/constants.d.ts +3 -2
- package/dist/types/lib/constants.d.ts.map +1 -1
- package/dist/types/lib/elements.d.ts +1 -1
- package/dist/types/lib/fragments.d.ts +46 -0
- package/dist/types/lib/fragments.d.ts.map +1 -0
- package/dist/types/lib/option-definitions.d.ts +21 -194
- package/dist/types/lib/option-definitions.d.ts.map +1 -1
- package/dist/types/lib/options.d.ts +33 -5
- package/dist/types/lib/options.d.ts.map +1 -1
- package/dist/types/lib/unused-css.d.ts +43 -0
- package/dist/types/lib/unused-css.d.ts.map +1 -0
- package/dist/types/lib/utils.d.ts +26 -1
- package/dist/types/lib/utils.d.ts.map +1 -1
- package/dist/types/tokenchain.d.ts +2 -2
- package/dist/types/tokenchain.d.ts.map +1 -1
- package/html-minifier-next.schema.json +11 -8
- package/package.json +2 -2
- package/src/htmlminifier.js +86 -88
- package/src/htmlparser.js +4 -4
- package/src/lib/attributes.js +65 -8
- package/src/lib/constants.js +8 -8
- package/src/lib/elements.js +3 -3
- package/src/lib/fragments.js +274 -0
- package/src/lib/option-definitions.js +19 -7
- package/src/lib/options.js +151 -28
- package/src/lib/unused-css.js +377 -0
- package/src/lib/utils.js +330 -2
- package/src/tokenchain.js +37 -30
package/cli.js
CHANGED
|
@@ -36,7 +36,9 @@ import readline from 'readline';
|
|
|
36
36
|
import { Command, Option } from 'commander';
|
|
37
37
|
|
|
38
38
|
// Simple case conversion for CLI option names (ASCII-only, no Unicode needed)
|
|
39
|
+
/** @param {string} str */
|
|
39
40
|
const paramCase = (str) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
|
|
41
|
+
/** @param {string} str */
|
|
40
42
|
const camelCase = (str) => paramCase(str).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
41
43
|
|
|
42
44
|
// Commander derives its internal option key by applying paramCase then camelCase to the flag name,
|
|
@@ -44,6 +46,7 @@ const camelCase = (str) => paramCase(str).replace(/-([a-z])/g, (_, c) => c.toUpp
|
|
|
44
46
|
// because option definition keys may differ from the result of that round-trip (e.g.,
|
|
45
47
|
// `minifyURLs` → Commander key `minifyUrls`, `noNewlinesBeforeTagClose` → `newlinesBeforeTagClose`),
|
|
46
48
|
// `commanderOptionKey` uses the same paramCase + camelCase path to compute the key Commander will use
|
|
49
|
+
/** @param {string} key */
|
|
47
50
|
const commanderOptionKey = (key) => {
|
|
48
51
|
const pc = paramCase(key);
|
|
49
52
|
return pc.startsWith('no-') ? camelCase(pc.slice(3)) : camelCase(pc);
|
|
@@ -67,13 +70,21 @@ const MARK_RESET = process.stderr.isTTY ? '\x1b[0m' : '';
|
|
|
67
70
|
const program = new Command();
|
|
68
71
|
program.name(pkg.name);
|
|
69
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {string} message
|
|
75
|
+
* @returns {never}
|
|
76
|
+
*/
|
|
70
77
|
function fatal(message) {
|
|
71
78
|
console.error(`${MARK_ERROR}${message}${MARK_RESET}`);
|
|
72
79
|
process.exit(1);
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
// `catch` bindings are `unknown`, and throw values need not be `Error`s
|
|
83
|
+
/** @param {unknown} err */
|
|
84
|
+
const errorMessage = (err) => err instanceof Error ? err.message : String(err);
|
|
85
|
+
|
|
75
86
|
// Handle broken pipe (e.g., when piping to `head`)
|
|
76
|
-
process.stdout.on('error', (err) => {
|
|
87
|
+
process.stdout.on('error', (/** @type {NodeJS.ErrnoException} */ err) => {
|
|
77
88
|
if (err && err.code === 'EPIPE') {
|
|
78
89
|
process.exit(0);
|
|
79
90
|
}
|
|
@@ -99,6 +110,7 @@ process.stdout.on('error', (err) => {
|
|
|
99
110
|
* --customAttrSurround "[\"//matchString//\"]"
|
|
100
111
|
*/
|
|
101
112
|
|
|
113
|
+
/** @param {string} value */
|
|
102
114
|
function parseJSON(value) {
|
|
103
115
|
if (value) {
|
|
104
116
|
try {
|
|
@@ -112,21 +124,24 @@ function parseJSON(value) {
|
|
|
112
124
|
}
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
/** @param {string} value */
|
|
115
128
|
function parseJSONArray(value) {
|
|
116
|
-
if (value)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
129
|
+
if (!value) return undefined;
|
|
130
|
+
const parsed = parseJSON(value);
|
|
131
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
120
132
|
}
|
|
121
133
|
|
|
134
|
+
/** @param {string} value */
|
|
122
135
|
function parseJSONRegExpArray(value) {
|
|
123
|
-
|
|
124
|
-
return
|
|
136
|
+
const values = parseJSONArray(value);
|
|
137
|
+
return values && values.map(parseRegExp);
|
|
125
138
|
}
|
|
126
139
|
|
|
140
|
+
/** @param {string} value */
|
|
127
141
|
const parseString = value => value;
|
|
128
142
|
|
|
129
|
-
|
|
143
|
+
/** @param {string} optionName */
|
|
144
|
+
const parseValidInt = (optionName) => (/** @type {string} */ value) => {
|
|
130
145
|
const s = String(value).trim();
|
|
131
146
|
// Accept only non-negative whole integers
|
|
132
147
|
if (!/^\d+$/.test(s)) {
|
|
@@ -136,20 +151,34 @@ const parseValidInt = (optionName) => (value) => {
|
|
|
136
151
|
return num;
|
|
137
152
|
};
|
|
138
153
|
|
|
139
|
-
// Map option types to CLI parsers
|
|
154
|
+
// Map option types to CLI parsers—`int` is resolved by `getParser`, as its
|
|
155
|
+
// parser closes over the option name for error messages
|
|
156
|
+
/** @type {Record<string, (value: string) => any>} */
|
|
140
157
|
const typeParsers = {
|
|
141
158
|
regexp: parseRegExp,
|
|
142
159
|
regexpArray: parseJSONRegExpArray,
|
|
143
160
|
json: parseJSON,
|
|
161
|
+
jsonObject: parseJSON,
|
|
144
162
|
jsonArray: parseJSONArray,
|
|
145
|
-
string: parseString
|
|
146
|
-
int: (key) => parseValidInt(key)
|
|
163
|
+
string: parseString
|
|
147
164
|
};
|
|
148
165
|
|
|
166
|
+
/**
|
|
167
|
+
* @param {string} key Option definition key, for the `int` parser’s error messages
|
|
168
|
+
* @param {string} type Option definition type
|
|
169
|
+
* @returns {(value: string) => any}
|
|
170
|
+
*/
|
|
171
|
+
function getParser(key, type) {
|
|
172
|
+
const parser = type === 'int' ? parseValidInt(key) : typeParsers[type];
|
|
173
|
+
if (!parser) {
|
|
174
|
+
fatal(`No CLI parser for option \`${key}\` (type \`${type}\`)—add the type to \`typeParsers\` in cli.js`);
|
|
175
|
+
}
|
|
176
|
+
return parser;
|
|
177
|
+
}
|
|
178
|
+
|
|
149
179
|
// Configure command-line flags from shared option definitions
|
|
150
180
|
const mainOptionKeys = Object.keys(optionDefinitions);
|
|
151
|
-
|
|
152
|
-
const { description, descriptionAffirmative, type } = optionDefinitions[key];
|
|
181
|
+
Object.entries(optionDefinitions).forEach(function ([key, { description, descriptionAffirmative, type }]) {
|
|
153
182
|
const flag = paramCase(key);
|
|
154
183
|
if (type === 'invertedBoolean') {
|
|
155
184
|
// The positive form (to re-enable after a preset/config disables it) is hidden from
|
|
@@ -165,9 +194,8 @@ mainOptionKeys.forEach(function (key) {
|
|
|
165
194
|
program.addOption(new Option('--no-' + flag, 'Disable --' + flag).hideHelp());
|
|
166
195
|
}
|
|
167
196
|
} else {
|
|
168
|
-
const cliFlag = '--' + flag + (type === 'json' ? ' [value]' : ' <value>');
|
|
169
|
-
|
|
170
|
-
program.option(cliFlag, description, parser);
|
|
197
|
+
const cliFlag = '--' + flag + (type === 'json' || type === 'jsonObject' ? ' [value]' : ' <value>');
|
|
198
|
+
program.option(cliFlag, description, getParser(key, type));
|
|
171
199
|
}
|
|
172
200
|
});
|
|
173
201
|
program.option('-i --input <file>', 'Specify input file (alternative to positional argument; pair with `--output` for output)');
|
|
@@ -177,6 +205,7 @@ program.option('-d --dry', 'Dry run: Process and report statistics without writi
|
|
|
177
205
|
program.addHelpText('after', '\nBoolean options support a `--no-<flag>` form to disable them, overriding a preset or config file (e.g., `--preset=comprehensive --no-collapse-whitespace`).');
|
|
178
206
|
|
|
179
207
|
// Lazy import wrapper for HMN
|
|
208
|
+
/** @type {Promise<typeof import('./src/htmlminifier.js').minify> | undefined} */
|
|
180
209
|
let minifyFnPromise;
|
|
181
210
|
async function getMinify() {
|
|
182
211
|
if (!minifyFnPromise) {
|
|
@@ -185,11 +214,12 @@ async function getMinify() {
|
|
|
185
214
|
return minifyFnPromise;
|
|
186
215
|
}
|
|
187
216
|
|
|
217
|
+
/** @param {string} file */
|
|
188
218
|
function readFile(file) {
|
|
189
219
|
try {
|
|
190
220
|
return fs.readFileSync(file, { encoding: 'utf8' });
|
|
191
221
|
} catch (err) {
|
|
192
|
-
fatal('Cannot read ' + file + '\n' + err
|
|
222
|
+
fatal('Cannot read ' + file + '\n' + errorMessage(err));
|
|
193
223
|
}
|
|
194
224
|
}
|
|
195
225
|
|
|
@@ -197,7 +227,7 @@ function readFile(file) {
|
|
|
197
227
|
* Load config from a file path. Extensions .json, .js, and .mjs are handled
|
|
198
228
|
* directly; for unknown extensions, JSON is tried first, then module import.
|
|
199
229
|
* @param {string} configPath - Path to config file
|
|
200
|
-
* @returns {Promise<
|
|
230
|
+
* @returns {Promise<Record<string, any>>} Loaded config object
|
|
201
231
|
*/
|
|
202
232
|
async function loadConfigFromPath(configPath) {
|
|
203
233
|
const abs = path.resolve(configPath);
|
|
@@ -205,24 +235,25 @@ async function loadConfigFromPath(configPath) {
|
|
|
205
235
|
|
|
206
236
|
if (ext === '.json') {
|
|
207
237
|
try { return JSON.parse(readFile(abs).replace(/^\uFEFF/, '')); }
|
|
208
|
-
catch (err) { fatal(`Cannot parse config file as JSON: ${err
|
|
238
|
+
catch (err) { fatal(`Cannot parse config file as JSON: ${errorMessage(err)}`); }
|
|
209
239
|
}
|
|
210
240
|
|
|
211
241
|
if (ext === '.js' || ext === '.mjs') {
|
|
212
242
|
// `import()` handles both ESM and CJS .js files—Node resolves the type via the
|
|
213
243
|
// nearest package.json `type` field, same as it does for regular module loading
|
|
214
244
|
try { const mod = await import(pathToFileURL(abs).href); return 'default' in mod ? mod.default : mod; }
|
|
215
|
-
catch (err) { fatal(`Cannot load config file: ${err
|
|
245
|
+
catch (err) { fatal(`Cannot load config file: ${errorMessage(err)}`); }
|
|
216
246
|
}
|
|
217
247
|
|
|
218
248
|
// Unknown extension: Try JSON, then module import
|
|
249
|
+
/** @type {unknown} */
|
|
219
250
|
let jsonErr;
|
|
220
251
|
try { return JSON.parse(readFile(abs).replace(/^\uFEFF/, '')); }
|
|
221
252
|
catch (err) { jsonErr = err; }
|
|
222
253
|
|
|
223
254
|
try { const mod = await import(pathToFileURL(abs).href); return 'default' in mod ? mod.default : mod; }
|
|
224
255
|
catch (esmErr) {
|
|
225
|
-
fatal(`Cannot read the specified config file.\nAs JSON: ${jsonErr
|
|
256
|
+
fatal(`Cannot read the specified config file.\nAs JSON: ${errorMessage(jsonErr)}\nAs module: ${errorMessage(esmErr)}`);
|
|
226
257
|
}
|
|
227
258
|
}
|
|
228
259
|
|
|
@@ -235,27 +266,26 @@ const CONFIG_FILES_DEFAULT = ['html-minifier-next.config.json', 'htmlminifier.co
|
|
|
235
266
|
|
|
236
267
|
/**
|
|
237
268
|
* Normalize and validate config object by applying parsers and transforming values.
|
|
238
|
-
* @param {
|
|
239
|
-
* @returns {
|
|
269
|
+
* @param {Record<string, any>} config - Raw config object
|
|
270
|
+
* @returns {Record<string, any>} Normalized config object
|
|
240
271
|
*/
|
|
241
272
|
function normalizeConfig(config) {
|
|
273
|
+
/** @type {Record<string, any>} */
|
|
242
274
|
const normalized = { ...config };
|
|
243
275
|
|
|
244
276
|
// Warn about unrecognized config keys—catches typos as well as options removed in earlier versions
|
|
245
277
|
Object.keys(normalized).forEach(function (key) {
|
|
246
278
|
if (!Object.hasOwn(optionDefinitions, key) && !CONFIG_KEYS_EXTRA.has(key)) {
|
|
247
|
-
console.error(`Ignoring unknown or deprecated config option
|
|
279
|
+
console.error(`Ignoring unknown or deprecated config option \`${key}\` (see \`--help\` or README for available options)`);
|
|
248
280
|
}
|
|
249
281
|
});
|
|
250
282
|
|
|
251
283
|
// Apply parsers to main options
|
|
252
|
-
|
|
284
|
+
Object.entries(optionDefinitions).forEach(function ([key, { type }]) {
|
|
253
285
|
if (key in normalized) {
|
|
254
|
-
const { type } = optionDefinitions[key];
|
|
255
286
|
if (type !== 'boolean' && type !== 'invertedBoolean') {
|
|
256
|
-
const parser = type === 'int' ? typeParsers.int(key) : typeParsers[type];
|
|
257
287
|
const value = normalized[key];
|
|
258
|
-
normalized[key] =
|
|
288
|
+
normalized[key] = getParser(key, type)(typeof value === 'string' ? value : JSON.stringify(value));
|
|
259
289
|
}
|
|
260
290
|
}
|
|
261
291
|
});
|
|
@@ -279,20 +309,29 @@ function normalizeConfig(config) {
|
|
|
279
309
|
return normalized;
|
|
280
310
|
}
|
|
281
311
|
|
|
312
|
+
/** @type {Record<string, any>} */
|
|
282
313
|
let config = {};
|
|
283
314
|
program.option('-z, --zero', 'Minify all HTML files in the current folder and its subfolders in place (except node_modules), using comprehensive settings (standalone—flag is ignored when combined with other options)');
|
|
284
315
|
program.option('-I --input-dir <dir>', 'Specify an input directory');
|
|
285
|
-
program.option('-X --ignore-dir <patterns>', 'Exclude directories—relative to input directory—from processing (comma-separated), e.g.,
|
|
316
|
+
program.option('-X --ignore-dir <patterns>', 'Exclude directories—relative to input directory—from processing (comma-separated), e.g., `libs` or `libs,vendor,node_modules`');
|
|
286
317
|
program.option('-O --output-dir <dir>', 'Specify an output directory');
|
|
287
|
-
program.option('-f --file-ext <extensions>', 'Specify file extension(s) to process (comma-separated); defaults to
|
|
318
|
+
program.option('-f --file-ext <extensions>', 'Specify file extension(s) to process (comma-separated); defaults to `html,htm,shtml,shtm`; use `*` for all files');
|
|
288
319
|
program.option('-p --preset <name>', `Use a preset configuration (${getPresetNames().join(', ')})`);
|
|
289
320
|
program.option('-c --config-file <file>', 'Use config file');
|
|
290
321
|
program.version(pkg.version, '-V, --version', 'Output the version number');
|
|
291
322
|
program.helpOption('-h, --help', 'Display help for command');
|
|
292
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Progress state shared between the file walk and the indicator; `total` stays
|
|
326
|
+
* `null` until the background count finishes
|
|
327
|
+
* @typedef {{current: number, total: number | null}} Progress
|
|
328
|
+
*/
|
|
329
|
+
|
|
293
330
|
(async () => {
|
|
331
|
+
/** @type {string} */
|
|
294
332
|
let content;
|
|
295
333
|
let filesProvided = false;
|
|
334
|
+
/** @type {string[]} */
|
|
296
335
|
let capturedFiles = [];
|
|
297
336
|
await program.arguments('[files...]').action(function (files) {
|
|
298
337
|
capturedFiles = files;
|
|
@@ -328,11 +367,12 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
328
367
|
console.error('Note: `--zero` was ignored—it can only be used on its own, to minify the current folder at comprehensive settings.');
|
|
329
368
|
} else {
|
|
330
369
|
const cwd = process.cwd();
|
|
370
|
+
const [execPath = '', scriptPath = ''] = process.argv;
|
|
331
371
|
const commandName = process.env.npm_command === 'exec'
|
|
332
372
|
? 'npx html-minifier-next'
|
|
333
|
-
:
|
|
334
|
-
? `${path.basename(
|
|
335
|
-
: path.basename(
|
|
373
|
+
: scriptPath.endsWith('.js')
|
|
374
|
+
? `${path.basename(execPath)} ${scriptPath}`
|
|
375
|
+
: path.basename(scriptPath);
|
|
336
376
|
|
|
337
377
|
process.stderr.write(
|
|
338
378
|
`${MARK_WARNING}Zero-config mode minifies all HTML files in the current folder and its subfolders (${cwd}) in place, using comprehensive settings. If you want to compare results and be able to revert, do this under version control.${MARK_RESET}\n` +
|
|
@@ -341,7 +381,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
341
381
|
);
|
|
342
382
|
|
|
343
383
|
const answer = await new Promise((resolve) => {
|
|
344
|
-
const rl = readline.createInterface({ input: process.stdin
|
|
384
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
345
385
|
rl.once('line', (line) => {
|
|
346
386
|
resolve(line.trim().toLowerCase());
|
|
347
387
|
rl.close();
|
|
@@ -362,6 +402,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
362
402
|
const ignorePatterns = ['node_modules'];
|
|
363
403
|
|
|
364
404
|
const showProgress = process.stderr.isTTY;
|
|
405
|
+
/** @type {Progress | null} */
|
|
365
406
|
let progress = null;
|
|
366
407
|
if (showProgress) {
|
|
367
408
|
progress = { current: 0, total: null };
|
|
@@ -399,12 +440,13 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
399
440
|
} else {
|
|
400
441
|
const configFileDefault = CONFIG_FILES_DEFAULT.find(name => fs.existsSync(path.resolve(name)));
|
|
401
442
|
if (configFileDefault) {
|
|
402
|
-
console.error(`Using config file
|
|
443
|
+
console.error(`Using config file ${configFileDefault}`);
|
|
403
444
|
config = normalizeConfig(await loadConfigFromPath(configFileDefault));
|
|
404
445
|
}
|
|
405
446
|
}
|
|
406
447
|
|
|
407
448
|
function createOptions() {
|
|
449
|
+
/** @type {Record<string, any>} */
|
|
408
450
|
const options = {};
|
|
409
451
|
|
|
410
452
|
// Priority order: preset < config < CLI
|
|
@@ -426,8 +468,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
426
468
|
});
|
|
427
469
|
|
|
428
470
|
// 3. Apply CLI options (overrides config and preset)
|
|
429
|
-
|
|
430
|
-
const { type } = optionDefinitions[key];
|
|
471
|
+
Object.entries(optionDefinitions).forEach(function ([key, { type }]) {
|
|
431
472
|
const ck = commanderOptionKey(key);
|
|
432
473
|
if (program.getOptionValueSource(ck) === 'cli') {
|
|
433
474
|
const val = programOptions[ck];
|
|
@@ -438,9 +479,30 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
438
479
|
}
|
|
439
480
|
});
|
|
440
481
|
|
|
482
|
+
// 4. Surface minifier diagnostics when verbose
|
|
483
|
+
if (programOptions.verbose || programOptions.dry) {
|
|
484
|
+
options.log = (/** @type {unknown} */ message) => {
|
|
485
|
+
// The hook carries the minifier’s per-call timing as well, which the run's own
|
|
486
|
+
// per-file statistics already cover
|
|
487
|
+
if (typeof message === 'string' && message.startsWith('minified in: ')) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
// Only `continueOnMinifyError` passes `Error` objects, always after leaving the
|
|
491
|
+
// offending content unminified—so say that
|
|
492
|
+
if (message instanceof Error) {
|
|
493
|
+
console.error(` ${MARK_WARNING}Warning: Minification failed, content left as-is: ${message.message || message}${MARK_RESET}`);
|
|
494
|
+
} else if (String(message).startsWith('Warning: ') || String(message).startsWith('HTML Minifier Next: ')) {
|
|
495
|
+
console.error(` ${MARK_WARNING}${message}${MARK_RESET}`);
|
|
496
|
+
} else {
|
|
497
|
+
console.error(` ${message}`);
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
441
502
|
return options;
|
|
442
503
|
}
|
|
443
504
|
|
|
505
|
+
/** @param {Record<string, unknown>} minifierOptions */
|
|
444
506
|
function getActiveOptionsDisplay(minifierOptions) {
|
|
445
507
|
const presetName = programOptions.preset || config.preset;
|
|
446
508
|
if (presetName) {
|
|
@@ -454,6 +516,10 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
454
516
|
}
|
|
455
517
|
}
|
|
456
518
|
|
|
519
|
+
/**
|
|
520
|
+
* @param {string} original
|
|
521
|
+
* @param {string} minified
|
|
522
|
+
*/
|
|
457
523
|
function calculateStats(original, minified) {
|
|
458
524
|
const originalSize = Buffer.byteLength(original, 'utf8');
|
|
459
525
|
const minifiedSize = Buffer.byteLength(minified, 'utf8');
|
|
@@ -468,24 +534,31 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
468
534
|
async function printCacheStats() {
|
|
469
535
|
const { getCacheStats } = await import('./src/htmlminifier.js');
|
|
470
536
|
const cacheStats = getCacheStats();
|
|
537
|
+
/** @type {Record<string, string>} */
|
|
471
538
|
const labels = { css: 'CSS', js: 'JS', svg: 'SVG' };
|
|
472
539
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
540
|
+
/** @type {string[]} */
|
|
541
|
+
const lines = [];
|
|
542
|
+
for (const [key, { gets, hits, size, limit }] of Object.entries(cacheStats)) {
|
|
543
|
+
if (gets === 0) continue;
|
|
476
544
|
const misses = gets - hits;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}, []);
|
|
545
|
+
lines.push(` ${labels[key]} cache: ${hits.toLocaleString()} hit${hits === 1 ? '' : 's'}, ${misses.toLocaleString()} miss${misses === 1 ? '' : 'es'}, ${size.toLocaleString()}/${limit.toLocaleString()} entries`);
|
|
546
|
+
}
|
|
480
547
|
|
|
481
548
|
if (lines.length === 0) return;
|
|
482
549
|
console.error('Cache stats:');
|
|
483
550
|
lines.forEach(line => console.error(line));
|
|
484
551
|
}
|
|
485
552
|
|
|
553
|
+
/**
|
|
554
|
+
* @param {string} inputFile
|
|
555
|
+
* @param {string} outputFile
|
|
556
|
+
* @param {boolean} [isDryRun]
|
|
557
|
+
* @param {boolean} [isVerbose]
|
|
558
|
+
*/
|
|
486
559
|
async function processFile(inputFile, outputFile, isDryRun = false, isVerbose = false) {
|
|
487
560
|
const data = await fs.promises.readFile(inputFile, { encoding: 'utf8' }).catch(err => {
|
|
488
|
-
fatal('Cannot read ' + inputFile + '\n' + err
|
|
561
|
+
fatal('Cannot read ' + inputFile + '\n' + errorMessage(err));
|
|
489
562
|
});
|
|
490
563
|
|
|
491
564
|
let minified;
|
|
@@ -493,7 +566,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
493
566
|
const minify = await getMinify();
|
|
494
567
|
minified = await minify(data, createOptions());
|
|
495
568
|
} catch (err) {
|
|
496
|
-
fatal('Minification error on ' + inputFile + '\n' + err
|
|
569
|
+
fatal('Minification error on ' + inputFile + '\n' + errorMessage(err));
|
|
497
570
|
}
|
|
498
571
|
|
|
499
572
|
const stats = calculateStats(data, minified);
|
|
@@ -514,6 +587,10 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
514
587
|
return { originalSize: stats.originalSize, minifiedSize: stats.minifiedSize, saved: stats.saved };
|
|
515
588
|
}
|
|
516
589
|
|
|
590
|
+
/**
|
|
591
|
+
* @param {string} fileExt
|
|
592
|
+
* @returns {string[]}
|
|
593
|
+
*/
|
|
517
594
|
function parseFileExtensions(fileExt) {
|
|
518
595
|
if (!fileExt) return [];
|
|
519
596
|
if (fileExt.trim() === '*') return ['*'];
|
|
@@ -524,6 +601,10 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
524
601
|
return [...new Set(list)];
|
|
525
602
|
}
|
|
526
603
|
|
|
604
|
+
/**
|
|
605
|
+
* @param {string} filename
|
|
606
|
+
* @param {string[]} fileExtensions
|
|
607
|
+
*/
|
|
527
608
|
function shouldProcessFile(filename, fileExtensions) {
|
|
528
609
|
// Wildcard: process all files
|
|
529
610
|
if (fileExtensions.includes('*')) {
|
|
@@ -568,6 +649,14 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
568
649
|
});
|
|
569
650
|
}
|
|
570
651
|
|
|
652
|
+
/**
|
|
653
|
+
* @param {string} dir
|
|
654
|
+
* @param {string[]} extensions
|
|
655
|
+
* @param {string | undefined} skipRootAbs
|
|
656
|
+
* @param {string[]} ignorePatterns
|
|
657
|
+
* @param {string} baseDir
|
|
658
|
+
* @returns {Promise<number>}
|
|
659
|
+
*/
|
|
571
660
|
async function countFiles(dir, extensions, skipRootAbs, ignorePatterns, baseDir) {
|
|
572
661
|
let count = 0;
|
|
573
662
|
|
|
@@ -603,6 +692,10 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
603
692
|
return count;
|
|
604
693
|
}
|
|
605
694
|
|
|
695
|
+
/**
|
|
696
|
+
* @param {number} current
|
|
697
|
+
* @param {number | null} total
|
|
698
|
+
*/
|
|
606
699
|
function updateProgress(current, total) {
|
|
607
700
|
// Clear the line first, then write simple progress
|
|
608
701
|
process.stderr.write(`\r\x1b[K`);
|
|
@@ -621,7 +714,15 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
621
714
|
}
|
|
622
715
|
|
|
623
716
|
// Utility: concurrency runner
|
|
717
|
+
/**
|
|
718
|
+
* @template T, R
|
|
719
|
+
* @param {T[]} items
|
|
720
|
+
* @param {number} limit
|
|
721
|
+
* @param {(item: T, index: number) => Promise<R>} worker
|
|
722
|
+
* @returns {Promise<R[]>}
|
|
723
|
+
*/
|
|
624
724
|
async function runWithConcurrency(items, limit, worker) {
|
|
725
|
+
/** @type {R[]} */
|
|
625
726
|
const results = new Array(items.length);
|
|
626
727
|
let next = 0;
|
|
627
728
|
let active = 0;
|
|
@@ -629,8 +730,9 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
629
730
|
const launch = () => {
|
|
630
731
|
while (active < limit && next < items.length) {
|
|
631
732
|
const current = next++;
|
|
733
|
+
const item = /** @type {T} */ (items[current]);
|
|
632
734
|
active++;
|
|
633
|
-
Promise.resolve(worker(
|
|
735
|
+
Promise.resolve(worker(item, current))
|
|
634
736
|
.then((res) => {
|
|
635
737
|
results[current] = res;
|
|
636
738
|
active--;
|
|
@@ -646,7 +748,16 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
646
748
|
});
|
|
647
749
|
}
|
|
648
750
|
|
|
751
|
+
/**
|
|
752
|
+
* @param {string} dir
|
|
753
|
+
* @param {string[]} extensions
|
|
754
|
+
* @param {string | undefined} skipRootAbs
|
|
755
|
+
* @param {string[]} ignorePatterns
|
|
756
|
+
* @param {string} baseDir
|
|
757
|
+
* @returns {Promise<string[]>}
|
|
758
|
+
*/
|
|
649
759
|
async function collectFiles(dir, extensions, skipRootAbs, ignorePatterns, baseDir) {
|
|
760
|
+
/** @type {string[]} */
|
|
650
761
|
const out = [];
|
|
651
762
|
const entries = await fs.promises.readdir(dir).catch(() => []);
|
|
652
763
|
for (const name of entries) {
|
|
@@ -668,6 +779,17 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
668
779
|
return out;
|
|
669
780
|
}
|
|
670
781
|
|
|
782
|
+
/**
|
|
783
|
+
* @param {string} inputDir
|
|
784
|
+
* @param {string} outputDir
|
|
785
|
+
* @param {string | string[]} extensions
|
|
786
|
+
* @param {boolean} [isDryRun]
|
|
787
|
+
* @param {boolean} [isVerbose]
|
|
788
|
+
* @param {string} [skipRootAbs]
|
|
789
|
+
* @param {Progress | null} [progress]
|
|
790
|
+
* @param {string[]} [ignorePatterns]
|
|
791
|
+
* @param {string | null} [baseDir]
|
|
792
|
+
*/
|
|
671
793
|
async function processDirectory(inputDir, outputDir, extensions, isDryRun = false, isVerbose = false, skipRootAbs, progress = null, ignorePatterns = [], baseDir = null) {
|
|
672
794
|
// If first call provided a string, normalize once; otherwise assume pre-parsed array
|
|
673
795
|
if (typeof extensions === 'string') {
|
|
@@ -689,7 +811,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
689
811
|
const outDir = path.dirname(outFile);
|
|
690
812
|
if (!isDryRun) {
|
|
691
813
|
await fs.promises.mkdir(outDir, { recursive: true }).catch(err => {
|
|
692
|
-
fatal('Cannot create directory ' + outDir + '\n' + err
|
|
814
|
+
fatal('Cannot create directory ' + outDir + '\n' + errorMessage(err));
|
|
693
815
|
});
|
|
694
816
|
}
|
|
695
817
|
const stats = await processFile(inputFile, outFile, isDryRun, isVerbose);
|
|
@@ -716,7 +838,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
716
838
|
const minify = await getMinify();
|
|
717
839
|
minified = await minify(content, minifierOptions);
|
|
718
840
|
} catch (err) {
|
|
719
|
-
fatal('Minification error:\n' + err
|
|
841
|
+
fatal('Minification error:\n' + errorMessage(err));
|
|
720
842
|
}
|
|
721
843
|
|
|
722
844
|
const stats = calculateStats(content, minified);
|
|
@@ -745,7 +867,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
745
867
|
await fs.promises.mkdir(path.dirname(programOptions.output), { recursive: true });
|
|
746
868
|
await fs.promises.writeFile(programOptions.output, minified, { encoding: 'utf8' });
|
|
747
869
|
} catch (err) {
|
|
748
|
-
fatal('Cannot write ' + programOptions.output + '\n' + err
|
|
870
|
+
fatal('Cannot write ' + programOptions.output + '\n' + errorMessage(err));
|
|
749
871
|
}
|
|
750
872
|
return;
|
|
751
873
|
}
|
|
@@ -810,6 +932,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
810
932
|
|
|
811
933
|
// Set up progress indicator (only in TTY and when not verbose/dry)
|
|
812
934
|
const showProgress = process.stderr.isTTY && !isVerbose;
|
|
935
|
+
/** @type {Progress | null} */
|
|
813
936
|
let progress = null;
|
|
814
937
|
|
|
815
938
|
// Parse ignore patterns
|
|
@@ -822,7 +945,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
822
945
|
fatal(`${inputDir} is not a directory—to minify a single file, use \`--input\`/\`--output\`: html-minifier-next [options] -i ${inputDir} -o <output-file>`);
|
|
823
946
|
}
|
|
824
947
|
} catch (err) {
|
|
825
|
-
fatal('Cannot read directory ' + inputDir + '\n' + err
|
|
948
|
+
fatal('Cannot read directory ' + inputDir + '\n' + errorMessage(err));
|
|
826
949
|
}
|
|
827
950
|
|
|
828
951
|
// Resolve base directory for consistent path comparisons
|
|
@@ -881,7 +1004,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
881
1004
|
for (const file of capturedFiles) {
|
|
882
1005
|
const ext = path.extname(file).replace(/^\./, '').toLowerCase();
|
|
883
1006
|
if (EXTENSIONS_NON_HTML.has(ext)) {
|
|
884
|
-
console.error(`${MARK_WARNING}Warning:
|
|
1007
|
+
console.error(`${MARK_WARNING}Warning: ${path.basename(file)} does not appear to be an HTML file—HTML Minifier Next processes CSS, JavaScript, and SVG only when embedded in HTML. The output may be incomplete or broken.${MARK_RESET}`);
|
|
885
1008
|
}
|
|
886
1009
|
}
|
|
887
1010
|
|
|
@@ -893,13 +1016,13 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
893
1016
|
const outputs = new Array(inputs.length);
|
|
894
1017
|
|
|
895
1018
|
await runWithConcurrency(inputs, concurrency, async (file, idx) => {
|
|
896
|
-
const data = await fs.promises.readFile(file, 'utf8').catch(err => fatal('Cannot read ' + file + '\n' + err
|
|
1019
|
+
const data = await fs.promises.readFile(file, 'utf8').catch(err => fatal('Cannot read ' + file + '\n' + errorMessage(err)));
|
|
897
1020
|
const minify = await getMinify();
|
|
898
1021
|
let out;
|
|
899
1022
|
try {
|
|
900
1023
|
out = await minify(data, minifierOptions);
|
|
901
1024
|
} catch (err) {
|
|
902
|
-
fatal('Minification error on ' + file + '\n' + err
|
|
1025
|
+
fatal('Minification error on ' + file + '\n' + errorMessage(err));
|
|
903
1026
|
}
|
|
904
1027
|
originals[idx] = data;
|
|
905
1028
|
outputs[idx] = out;
|
|
@@ -932,7 +1055,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
932
1055
|
await fs.promises.mkdir(path.dirname(programOptions.output), { recursive: true });
|
|
933
1056
|
await fs.promises.writeFile(programOptions.output, minifiedCombined, 'utf8');
|
|
934
1057
|
} catch (err) {
|
|
935
|
-
fatal('Cannot write ' + programOptions.output + '\n' + err
|
|
1058
|
+
fatal('Cannot write ' + programOptions.output + '\n' + errorMessage(err));
|
|
936
1059
|
}
|
|
937
1060
|
} else {
|
|
938
1061
|
process.stdout.write(minifiedCombined);
|
|
@@ -941,7 +1064,7 @@ program.helpOption('-h, --help', 'Display help for command');
|
|
|
941
1064
|
} else { // Minifying input coming from STDIN
|
|
942
1065
|
content = '';
|
|
943
1066
|
process.stdin.setEncoding('utf8');
|
|
944
|
-
process.stdin.on('data', function (data) {
|
|
1067
|
+
process.stdin.on('data', function (/** @type {string} */ data) {
|
|
945
1068
|
content += data;
|
|
946
1069
|
}).on('end', async function() {
|
|
947
1070
|
await writeMinify();
|
|
@@ -144,12 +144,14 @@ export type MinifierOptions = {
|
|
|
144
144
|
*/
|
|
145
145
|
customEventAttributes?: RegExp[];
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
148
|
-
*
|
|
147
|
+
* Rejects `ignoreCustomFragments` patterns that risk catastrophic backtracking,
|
|
148
|
+
* rather than warning about them: those that compound quantifiers or alternation
|
|
149
|
+
* (`(a+)+`, `(a|b)*`), those that repeat the same unbounded atom twice in a row
|
|
150
|
+
* (`.*.*`), and those too long or too deeply nested to analyze at all.
|
|
149
151
|
*
|
|
150
|
-
* Default: `
|
|
152
|
+
* Default: `false`
|
|
151
153
|
*/
|
|
152
|
-
|
|
154
|
+
strictCustomFragments?: boolean;
|
|
153
155
|
/**
|
|
154
156
|
* When true, decodes HTML entities in text and attributes before
|
|
155
157
|
* processing, and re-encodes ambiguous ampersands when outputting.
|
|
@@ -421,6 +423,24 @@ export type MinifierOptions = {
|
|
|
421
423
|
* Default: `false`
|
|
422
424
|
*/
|
|
423
425
|
removeTagWhitespace?: boolean;
|
|
426
|
+
/**
|
|
427
|
+
* **Note that this can change how a document renders!**
|
|
428
|
+
*
|
|
429
|
+
* Remove rules from `style` elements whose class or ID selectors the document
|
|
430
|
+
* never references. Requires `minifyCSS` (removal runs through Lightning CSS)
|
|
431
|
+
* and has no effect on `style` or `media` attributes.
|
|
432
|
+
*
|
|
433
|
+
* Class names and IDs are collected from the markup, from `data-*` attributes,
|
|
434
|
+
* and—unless `scripts` is set to `false`—from inline `script` elements. Names
|
|
435
|
+
* that only ever appear in external scripts cannot be detected; list those
|
|
436
|
+
* under `safelist` (strings or regular expressions) to keep them.
|
|
437
|
+
*
|
|
438
|
+
* Default: `false`
|
|
439
|
+
*/
|
|
440
|
+
removeUnusedCSS?: boolean | {
|
|
441
|
+
safelist?: Array<string | RegExp>;
|
|
442
|
+
scripts?: boolean;
|
|
443
|
+
};
|
|
424
444
|
/**
|
|
425
445
|
* When true, enables sorting of attributes. If a function is provided it
|
|
426
446
|
* will be used as a custom attribute sorter, which should mutate `attrs`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}
|