html-minifier-next 6.2.10 → 7.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.
@@ -4,19 +4,39 @@ import { RE_TRAILING_SEMICOLON } from './constants.js';
4
4
  import { canCollapseWhitespace, canTrimWhitespace } from './whitespace.js';
5
5
  import { wrapCSS, unwrapCSS } from './content.js';
6
6
  import { getPreset, getPresetNames } from '../presets.js';
7
- import { optionDefaults } from './option-definitions.js';
7
+ import { optionDefinitions, optionDefaults } from './option-definitions.js';
8
+
9
+ /** @import { MinifierOptions, HTMLAttribute } from '../htmlminifier.js' */
8
10
 
9
11
  // Type definitions
10
12
 
11
13
  /**
12
- * @typedef {Record<string, any>} MinifierOptions
14
+ * Options object produced by `processOptions` and consumed by `minifyHTML` and
15
+ * the `lib/` helpers; normalization guarantees that the function-valued options
16
+ * below are always present (defaulting to identity/built-in functions), and
17
+ * minification adds writable internal state on top of the public options
18
+ * (set on prototype-chain forks during SVG/MathML namespace transitions)
19
+ *
20
+ * @typedef {Omit<MinifierOptions, 'preset' | 'canCollapseWhitespace' | 'canTrimWhitespace' | 'ignoreCustomComments' | 'log' | 'minifyCSS' | 'minifyJS' | 'minifyURLs' | 'minifySVG'> & {
21
+ * name: (name: string) => string,
22
+ * log: (message: any) => unknown,
23
+ * ignoreCustomComments: RegExp[],
24
+ * canCollapseWhitespace: (tag: string, attrs: HTMLAttribute[], defaultFn: (tag: string) => boolean) => boolean,
25
+ * canTrimWhitespace: (tag: string, attrs: HTMLAttribute[], defaultFn: (tag: string) => boolean) => boolean,
26
+ * minifyCSS: (text: string, type?: string) => string | Promise<string>,
27
+ * minifyJS: (text: string, inline?: boolean, isModule?: boolean) => string | Promise<string>,
28
+ * minifyURLs: (text: string) => string | Promise<string>,
29
+ * minifySVG: ((svgContent: string) => string | Promise<string>) | null,
30
+ * nameParent?: (name: string) => string,
31
+ * nameHTML?: (name: string) => string,
32
+ * insideSVG?: boolean,
33
+ * insideForeignContent?: boolean
34
+ * }} ProcessedOptions
13
35
  */
14
36
 
15
- // @@ Extract a `ProcessedOptions` typedef (with `name`, `log`, `canCollapseWhitespace`, `canTrimWhitespace`,`minifyCSS`, `minifyJS`, `minifyURLs` typed as required, non-optional) to replace the current `Record<string, any>` escape hatch and eliminate the `/** @type {Function} */` casts in htmlminifier.js
16
-
17
37
  // Helper functions
18
38
 
19
- /** @param {MinifierOptions} options */
39
+ /** @param {ProcessedOptions} options */
20
40
  function shouldMinifyInnerHTML(options) {
21
41
  return Boolean(
22
42
  options.collapseWhitespace ||
@@ -29,15 +49,23 @@ function shouldMinifyInnerHTML(options) {
29
49
  );
30
50
  }
31
51
 
52
+ // User-facing option keys that are valid but not listed in `optionDefinitions`
53
+ const optionKeysExtra = new Set(['preset', 'log', 'canCollapseWhitespace', 'canTrimWhitespace', 'cacheCSS', 'cacheJS', 'cacheSVG']);
54
+
55
+ // Unknown option keys and preset names already warned about—warn once per
56
+ // key per process, so repeated `minify` calls (e.g., batch runs) don’t flood STDERR
57
+ const optionKeysWarned = new Set();
58
+ const presetNamesWarned = new Set();
59
+
32
60
  // Main options processor
33
61
 
34
62
  /**
35
63
  * @param {MinifierOptions} inputOptions - User-provided options
36
64
  * @param {{getLightningCSS?: Function | undefined, getTerser?: Function | undefined, getSwc?: Function | undefined, getSvgo?: Function | undefined, cssMinifyCache?: LRU | undefined, jsMinifyCache?: LRU | undefined, svgMinifyCache?: LRU | undefined}} [deps] - Dependencies from htmlminifier.js
37
- * @returns {MinifierOptions} Normalized options with defaults applied
65
+ * @returns {ProcessedOptions} Normalized options with defaults applied
38
66
  */
39
67
  const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getSvgo, cssMinifyCache, jsMinifyCache, svgMinifyCache } = {}) => {
40
- /** @type {MinifierOptions} */
68
+ /** @type {ProcessedOptions} */
41
69
  const options = {
42
70
  name: lowercase,
43
71
  canCollapseWhitespace,
@@ -67,6 +95,18 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
67
95
  });
68
96
  };
69
97
 
98
+ // Route warnings through the user-provided `log` hook so API consumers can
99
+ // capture or suppress them consistently; fall back to `console.warn`
100
+ const warn = typeof inputOptions.log === 'function' ? inputOptions.log : console.warn;
101
+
102
+ // Warn about unrecognized options—catches typos as well as options removed in earlier versions
103
+ Object.keys(inputOptions).forEach(function (key) {
104
+ if (!Object.hasOwn(optionDefinitions, key) && !optionKeysExtra.has(key) && !optionKeysWarned.has(key)) {
105
+ optionKeysWarned.add(key);
106
+ warn(`HTML Minifier Next: Ignoring unknown or deprecated option “${key}” (see README for available options)`);
107
+ }
108
+ });
109
+
70
110
  // Merge preset with user options so all values go through normalization
71
111
  // User options take precedence over preset values
72
112
  let effectiveInput = inputOptions;
@@ -74,17 +114,22 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
74
114
  const preset = getPreset(inputOptions.preset);
75
115
  if (preset) {
76
116
  effectiveInput = { ...preset, ...inputOptions };
77
- } else {
117
+ } else if (!presetNamesWarned.has(inputOptions.preset)) {
118
+ presetNamesWarned.add(inputOptions.preset);
78
119
  const available = getPresetNames().join(', ');
79
- console.warn(`HTML Minifier Next: Unknown preset “${inputOptions.preset}”. Available presets: ${available}`);
120
+ warn(`HTML Minifier Next: Unknown preset “${inputOptions.preset}”; available presets: ${available}`);
80
121
  }
81
122
  }
82
123
 
124
+ // Escape hatch for the loop below, which reads and assigns user-provided values by dynamic key
125
+ const optionsDynamic = /** @type {Record<string, any>} */ (options);
126
+
83
127
  Object.keys(effectiveInput).forEach(function (key) {
84
- const option = effectiveInput[key];
128
+ const option = /** @type {Record<string, any>} */ (effectiveInput)[key];
85
129
 
86
- // Skip preset key—it’s already been processed
87
- if (key === 'preset') {
130
+ // Skip `preset` (already processed) and unrecognized keys (warned about above)—
131
+ // the latter also keeps internal keys from being overridden
132
+ if (key === 'preset' || (!Object.hasOwn(optionDefinitions, key) && !optionKeysExtra.has(key))) {
88
133
  return;
89
134
  }
90
135
 
@@ -106,7 +151,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
106
151
  const cssLoader = getLightningCSS;
107
152
  const cssCache = cssMinifyCache;
108
153
 
109
- options.minifyCSS = async function (/** @type {string} */ text, /** @type {string} */ type) {
154
+ options.minifyCSS = async function (/** @type {string} */ text, /** @type {string | undefined} */ type) {
110
155
  // Fast path: Nothing to minify
111
156
  if (!text || !text.trim()) {
112
157
  return text;
@@ -143,7 +188,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
143
188
  : (inputCSS + '|' + type + '|' + cssSig);
144
189
 
145
190
  try {
146
- const cached = cssCache.get(cssKey);
191
+ const cached = /** @type {string | Promise<string> | undefined} */ (cssCache.get(cssKey));
147
192
  if (cached !== undefined) {
148
193
  // Support both resolved values and in-flight promises
149
194
  return await cached;
@@ -236,7 +281,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
236
281
  cont: !!options.continueOnMinifyError
237
282
  });
238
283
 
239
- options.minifyJS = async function (/** @type {string} */ text, /** @type {boolean} */ inline, /** @type {boolean} */ isModule) {
284
+ options.minifyJS = async function (/** @type {string} */ text, /** @type {boolean | undefined} */ inline, /** @type {boolean | undefined} */ isModule) {
240
285
  const start = text.match(/^\s*<!--.*/);
241
286
  const code = start ? text.slice(start[0].length).replace(/\n\s*-->\s*$/, '') : text;
242
287
 
@@ -258,7 +303,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
258
303
  jsKey = (code.length > 2048 ? (hashContent(code) + '|') : (code + '|'))
259
304
  + (inline ? '1' : '0') + '|' + (isModule ? 'm' : '') + '|' + useEngine + '|' + optsSig;
260
305
 
261
- const cached = jsCache.get(jsKey);
306
+ const cached = /** @type {string | Promise<string> | undefined} */ (jsCache.get(jsKey));
262
307
  if (cached !== undefined) {
263
308
  return await cached;
264
309
  }
@@ -331,7 +376,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
331
376
  }
332
377
 
333
378
  // Check cache
334
- const cached = instanceCache.get(text);
379
+ const cached = /** @type {string | undefined} */ (instanceCache.get(text));
335
380
  if (cached !== undefined) {
336
381
  return cached;
337
382
  }
@@ -377,7 +422,7 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
377
422
  : (svgContent + '|' + svgSig);
378
423
 
379
424
  try {
380
- const cached = svgCache.get(svgKey);
425
+ const cached = /** @type {string | Promise<string> | undefined} */ (svgCache.get(svgKey));
381
426
  if (cached !== undefined) {
382
427
  return await cached;
383
428
  }
@@ -403,15 +448,15 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
403
448
  };
404
449
  } else if (key === 'customAttrCollapse') {
405
450
  // Single regex pattern
406
- options[key] = parseRegExp(option);
451
+ optionsDynamic[key] = parseRegExp(option);
407
452
  } else if (key === 'customAttrSurround') {
408
453
  // Nested array of RegExp pairs: `[[openRegExp, closeRegExp], …]`
409
- options[key] = parseNestedRegExpArray(option);
454
+ optionsDynamic[key] = parseNestedRegExpArray(option);
410
455
  } else if (['customAttrAssign', 'customEventAttributes', 'ignoreCustomComments', 'ignoreCustomFragments'].includes(key)) {
411
456
  // Array of regex patterns
412
- options[key] = parseRegExpArray(option);
457
+ optionsDynamic[key] = parseRegExpArray(option);
413
458
  } else {
414
- options[key] = option;
459
+ optionsDynamic[key] = option;
415
460
  }
416
461
  });
417
462
  return options;
package/src/lib/utils.js CHANGED
@@ -80,7 +80,10 @@ function identity(value) {
80
80
  return value;
81
81
  }
82
82
 
83
- /** @param {unknown} value */
83
+ /**
84
+ * @param {unknown} value
85
+ * @returns {value is PromiseLike<any>}
86
+ */
84
87
  function isThenable(value) {
85
88
  return value != null && typeof value === 'object' && typeof /** @type {any} */ (value).then === 'function';
86
89
  }
@@ -39,7 +39,7 @@ function collapseWhitespaceAll(str) {
39
39
  // Fast path: No no-break space, common case—just collapse to single space
40
40
  // This avoids the nested regex for the majority of cases
41
41
  if (spaces.indexOf('\xA0') === -1) return ' ';
42
- // For no-break space handling, use the original regex approach
42
+ // For no-break space handling, use the nested regex
43
43
  return spaces.replace(RE_NBSP_LEADING_GROUP, '$1 ');
44
44
  });
45
45
  }
package/src/presets.js CHANGED
@@ -14,8 +14,7 @@ export const presets = {
14
14
  conservativeCollapse: true,
15
15
  preserveLineBreaks: true,
16
16
  removeComments: true,
17
- removeScriptTypeAttributes: true,
18
- removeStyleLinkTypeAttributes: true,
17
+ removeDefaultTypeAttributes: true,
19
18
  useShortDoctype: true
20
19
  },
21
20
  comprehensive: {
@@ -30,11 +29,10 @@ export const presets = {
30
29
  minifyURLs: true,
31
30
  removeAttributeQuotes: true,
32
31
  removeComments: true,
32
+ removeDefaultTypeAttributes: true,
33
33
  removeEmptyAttributes: true,
34
34
  removeOptionalTags: true,
35
35
  removeRedundantAttributes: true,
36
- removeScriptTypeAttributes: true,
37
- removeStyleLinkTypeAttributes: true,
38
36
  useShortDoctype: true
39
37
  }
40
38
  };