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/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
- value = parseJSON(value);
118
- return Array.isArray(value) ? value : [value];
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
- value = parseJSONArray(value);
124
- return value && value.map(parseRegExp);
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
- const parseValidInt = (optionName) => (value) => {
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
- mainOptionKeys.forEach(function (key) {
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
- const parser = type === 'int' ? typeParsers.int(key) : typeParsers[type];
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.message);
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<object>} Loaded config object
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.message}`); }
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.message}`); }
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.message}\nAs module: ${esmErr.message}`);
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 {object} config - Raw config object
239
- * @returns {object} Normalized config object
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 “${key}” (see \`--help\` or README for available options)`);
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
- mainOptionKeys.forEach(function (key) {
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] = parser(typeof value === 'string' ? value : JSON.stringify(value));
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., “libs” or “libs,vendor,node_modules”');
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 “html,htm,shtml,shtm”; use “*” for all files');
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
- : process.argv[1].endsWith('.js')
334
- ? `${path.basename(process.argv[0])} ${process.argv[1]}`
335
- : path.basename(process.argv[1]);
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, output: null });
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 “${configFileDefault}”`);
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
- mainOptionKeys.forEach(function (key) {
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
- const lines = Object.keys(labels).reduce((acc, key) => {
474
- const { gets, hits, size, limit } = cacheStats[key];
475
- if (gets === 0) return acc;
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
- acc.push(` ${labels[key]} cache: ${hits.toLocaleString()} hit${hits === 1 ? '' : 's'}, ${misses.toLocaleString()} miss${misses === 1 ? '' : 'es'}, ${size.toLocaleString()}/${limit.toLocaleString()} entries`);
478
- return acc;
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.message);
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.message);
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(items[current], current))
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.message);
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.message);
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.message);
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.message);
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: “${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}`);
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.message));
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.message);
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.message);
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
- * Limits the quantifier used when building a safe regex for custom
148
- * fragments to avoid ReDoS. See source use for details.
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: `200`
152
+ * Default: `false`
151
153
  */
152
- customFragmentQuantifierLimit?: number;
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":"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"}
1
+ {"version":3,"file":"htmlminifier.d.ts","sourceRoot":"","sources":["../../src/htmlminifier.js"],"names":[],"mappings":"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"}