html-minifier-next 6.2.0 → 6.2.2

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 CHANGED
@@ -603,7 +603,7 @@ Parameters:
603
603
 
604
604
  ## Acknowledgements
605
605
 
606
- With many thanks to all the previous authors of HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf) and [Jonas Geiler](https://github.com/jonasgeiler).
606
+ With many thanks to the previous authors of and contributors to HTML Minifier, especially [Juriy “kangax” Zaytsev](https://github.com/kangax), and to everyone who helped make this new edition better, particularly [Daniel Ruf](https://github.com/DanielRuf), [Jonas Geiler](https://github.com/jonasgeiler), and [Chris Morgan](https://github.com/chris-morgan)!
607
607
 
608
608
  ***
609
609
 
@@ -43,6 +43,17 @@ class LRU {
43
43
  delete(key) { this.map.delete(key); }
44
44
  }
45
45
 
46
+ // FNV-1a 32-bit hash for large-input cache keys
47
+
48
+ function hashContent(str) {
49
+ let hash = 2166136261;
50
+ for (let i = 0; i < str.length; i++) {
51
+ hash ^= str.charCodeAt(i);
52
+ hash = Math.imul(hash, 16777619);
53
+ }
54
+ return (hash >>> 0).toString(36);
55
+ }
56
+
46
57
  // Unique ID generator
47
58
 
48
59
  function uniqueId(value) {
@@ -1429,15 +1440,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
1429
1440
 
1430
1441
  // Collapse/trim whitespace for given tag
1431
1442
 
1432
- const noCollapseWsTags = new Set(['script', 'style', 'pre', 'textarea']);
1433
- const noTrimWsTags = new Set(['pre', 'textarea']);
1443
+ const noCollapseWhitespaceTags = new Set(['script', 'style', 'pre', 'textarea']);
1444
+ const noTrimWhitespaceTags = new Set(['pre', 'textarea']);
1434
1445
 
1435
1446
  function canCollapseWhitespace(tag) {
1436
- return !noCollapseWsTags.has(tag);
1447
+ return !noCollapseWhitespaceTags.has(tag);
1437
1448
  }
1438
1449
 
1439
1450
  function canTrimWhitespace(tag) {
1440
- return !noTrimWsTags.has(tag);
1451
+ return !noTrimWhitespaceTags.has(tag);
1441
1452
  }
1442
1453
 
1443
1454
  /**
@@ -1816,12 +1827,11 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
1816
1827
  );
1817
1828
  }
1818
1829
 
1819
- // Cache key: Wrapped content, type, options signature
1830
+ // Cache key: Content + type + options signature; large inputs are hashed to avoid huge Map keys
1820
1831
  const inputCSS = wrapCSS(text, type);
1821
1832
  const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
1822
- // For large inputs, use length and content fingerprint (first/last 50 chars) to prevent collisions
1823
1833
  const cssKey = inputCSS.length > 2048
1824
- ? (inputCSS.length + '|' + inputCSS.slice(0, 50) + inputCSS.slice(-50) + '|' + type + '|' + cssSig)
1834
+ ? (hashContent(inputCSS) + '|' + type + '|' + cssSig)
1825
1835
  : (inputCSS + '|' + type + '|' + cssSig);
1826
1836
 
1827
1837
  try {
@@ -1931,8 +1941,8 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
1931
1941
  // Select pre-computed signature based on engine
1932
1942
  const optsSig = useEngine === 'terser' ? terserSig : swcSig;
1933
1943
 
1934
- // For large inputs, use length and content fingerprint to prevent collisions
1935
- jsKey = (code.length > 2048 ? (code.length + '|' + code.slice(0, 50) + code.slice(-50) + '|') : (code + '|'))
1944
+ // For large inputs, hash the full content to avoid storing huge strings as Map keys
1945
+ jsKey = (code.length > 2048 ? (hashContent(code) + '|') : (code + '|'))
1936
1946
  + (inline ? '1' : '0') + '|' + (isModule ? 'm' : '') + '|' + useEngine + '|' + optsSig;
1937
1947
 
1938
1948
  const cached = jsMinifyCache.get(jsKey);
@@ -2044,9 +2054,9 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
2044
2054
  return svgContent;
2045
2055
  }
2046
2056
 
2047
- // Cache key
2057
+ // Cache key: Large inputs are hashed to avoid huge Map keys
2048
2058
  const svgKey = svgContent.length > 2048
2049
- ? (svgContent.length + '|' + svgContent.slice(0, 50) + svgContent.slice(-50) + '|' + svgSig)
2059
+ ? (hashContent(svgContent) + '|' + svgSig)
2050
2060
  : (svgContent + '|' + svgSig);
2051
2061
 
2052
2062
  try {
@@ -2366,12 +2376,18 @@ function hasAttrName(name, attrs) {
2366
2376
 
2367
2377
  // Cleaners
2368
2378
 
2379
+ const collapseAttributeWhitespaceExempt = new Set(['pattern', 'placeholder', 'title']);
2380
+ // `value` whitespace matters only on form-submission and machine-readable elements
2381
+ const valueWhitespaceExemptElements = new Set(['button', 'data', 'input', 'option', 'param']);
2382
+
2369
2383
  // Returns the cleaned attribute value directly (sync) or as a Promise (async);
2370
2384
  // callers must handle both cases—use `isThenable()` to distinguish
2371
2385
  function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTMLSelf) {
2386
+ const isEventAttr = isEventAttribute(attrName, options);
2387
+
2372
2388
  // Apply early whitespace normalization if enabled
2373
2389
  // Preserves special spaces (no-break space, hair space, etc.) for consistency with `collapseWhitespace`
2374
- if (options.collapseAttributeWhitespace) {
2390
+ if (options.collapseAttributeWhitespace && !collapseAttributeWhitespaceExempt.has(attrName) && !(attrName === 'value' && valueWhitespaceExemptElements.has(tag)) && !isEventAttr) {
2375
2391
  // Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
2376
2392
  if (RE_ATTR_WS_CHECK.test(attrValue)) {
2377
2393
  // Two-pass approach (faster than single-pass with callback)
@@ -2381,7 +2397,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTM
2381
2397
  }
2382
2398
  }
2383
2399
 
2384
- if (isEventAttribute(attrName, options)) {
2400
+ if (isEventAttr) {
2385
2401
  attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
2386
2402
  const result = options.minifyJS(attrValue, true);
2387
2403
  if (isThenable(result)) {
@@ -1 +1 @@
1
- {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAmBD,qEAGC;AAgBD,wEAGC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAMD,iIA0KC;AAwBD,mGAYC;AA0CD,6GAuHC;AAllBD;;;;;;;GAOG;AACH,mEAHW,OAAO,SAuBjB"}
1
+ {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AAmCA,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAgCD,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAmBD,qEAGC;AAgBD,wEAGC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAUD,iIA4KC;AAwBD,mGAYC;AA0CD,6GAuHC;AAxlBD;;;;;;;GAOG;AACH,mEAHW,OAAO,SAuBjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAYA,6DAUC;AAID;;;;;;;;;;;GAWG;AACH,6CAXW,OAAO,CAAC,eAAe,CAAC,mGAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAK9C,eAAe,CAiX3B"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAYA,6DAUC;AAID;;;;;;;;;;;GAWG;AACH,6CAXW,OAAO,CAAC,eAAe,CAAC,mGAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAK9C,eAAe,CAgX3B"}
@@ -7,6 +7,7 @@ export class LRU {
7
7
  set(key: any, value: any): void;
8
8
  delete(key: any): void;
9
9
  }
10
+ export function hashContent(str: any): string;
10
11
  export function uniqueId(value: any): string;
11
12
  export function identity(value: any): any;
12
13
  export function isThenable(value: any): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA,+CAUC;AAID;IACE,4BAGC;IAFC,cAAkB;IAClB,mBAAoB;IAEtB,mBAQC;IACD,gCAOC;IACD,uBAAqC;CACtC;AAID,6CAMC;AAID,0CAEC;AAED,gDAEC;AAED,2CAEC;AAID;;;;;;GAMG;AACH,kCALW,MAAM,SACN,MAAM,sBAEJ,OAAO,CAAC,MAAM,CAAC,CAY3B;AAID,6CAUC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/lib/utils.js"],"names":[],"mappings":"AAEA,+CAUC;AAID;IACE,4BAGC;IAFC,cAAkB;IAClB,mBAAoB;IAEtB,mBAQC;IACD,gCAOC;IACD,uBAAqC;CACtC;AAID,8CAOC;AAID,6CAMC;AAID,0CAEC;AAED,gDAEC;AAED,2CAEC;AAID;;;;;;GAMG;AACH,kCALW,MAAM,SACN,MAAM,sBAEJ,OAAO,CAAC,MAAM,CAAC,CAY3B;AAID,6CAUC"}
package/package.json CHANGED
@@ -18,11 +18,11 @@
18
18
  "@rollup/plugin-commonjs": "^29.0.2",
19
19
  "@rollup/plugin-json": "^6.1.0",
20
20
  "@rollup/plugin-node-resolve": "^16.0.3",
21
- "@swc/core": "^1.15.21",
21
+ "@swc/core": "^1.15.30",
22
22
  "eslint": "^10.2.0",
23
- "rollup": "^4.60.0",
23
+ "rollup": "^4.60.2",
24
24
  "rollup-plugin-polyfill-node": "^0.13.0",
25
- "typescript": "^6.0.2",
25
+ "typescript": "^6.0.3",
26
26
  "vite": "^8.0.8"
27
27
  },
28
28
  "exports": {
@@ -96,5 +96,5 @@
96
96
  },
97
97
  "type": "module",
98
98
  "types": "./dist/types/htmlminifier.d.ts",
99
- "version": "6.2.0"
99
+ "version": "6.2.2"
100
100
  }
@@ -294,12 +294,18 @@ function hasAttrName(name, attrs) {
294
294
 
295
295
  // Cleaners
296
296
 
297
+ const collapseAttributeWhitespaceExempt = new Set(['pattern', 'placeholder', 'title']);
298
+ // `value` whitespace matters only on form-submission and machine-readable elements
299
+ const valueWhitespaceExemptElements = new Set(['button', 'data', 'input', 'option', 'param']);
300
+
297
301
  // Returns the cleaned attribute value directly (sync) or as a Promise (async);
298
302
  // callers must handle both cases—use `isThenable()` to distinguish
299
303
  function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTMLSelf) {
304
+ const isEventAttr = isEventAttribute(attrName, options);
305
+
300
306
  // Apply early whitespace normalization if enabled
301
307
  // Preserves special spaces (no-break space, hair space, etc.) for consistency with `collapseWhitespace`
302
- if (options.collapseAttributeWhitespace) {
308
+ if (options.collapseAttributeWhitespace && !collapseAttributeWhitespaceExempt.has(attrName) && !(attrName === 'value' && valueWhitespaceExemptElements.has(tag)) && !isEventAttr) {
303
309
  // Fast path: Only process if whitespace exists (avoids regex overhead on clean values)
304
310
  if (RE_ATTR_WS_CHECK.test(attrValue)) {
305
311
  // Two-pass approach (faster than single-pass with callback)
@@ -309,7 +315,7 @@ function cleanAttributeValue(tag, attrName, attrValue, options, attrs, minifyHTM
309
315
  }
310
316
  }
311
317
 
312
- if (isEventAttribute(attrName, options)) {
318
+ if (isEventAttr) {
313
319
  attrValue = trimWhitespace(attrValue).replace(/^javascript:\s*/i, '');
314
320
  const result = options.minifyJS(attrValue, true);
315
321
  if (isThenable(result)) {
@@ -1,7 +1,7 @@
1
1
  // Imports
2
2
 
3
3
  import { createUrlMinifier } from './urls.js';
4
- import { LRU, stableStringify, identity, lowercase, replaceAsync, parseRegExp } from './utils.js';
4
+ import { LRU, stableStringify, hashContent, identity, lowercase, replaceAsync, parseRegExp } from './utils.js';
5
5
  import { RE_TRAILING_SEMICOLON } from './constants.js';
6
6
  import { canCollapseWhitespace, canTrimWhitespace } from './whitespace.js';
7
7
  import { wrapCSS, unwrapCSS } from './content.js';
@@ -131,12 +131,11 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
131
131
  );
132
132
  }
133
133
 
134
- // Cache key: Wrapped content, type, options signature
134
+ // Cache key: Content + type + options signature; large inputs are hashed to avoid huge Map keys
135
135
  const inputCSS = wrapCSS(text, type);
136
136
  const cssSig = stableStringify({ type, opts: lightningCssOptions, cont: !!options.continueOnMinifyError });
137
- // For large inputs, use length and content fingerprint (first/last 50 chars) to prevent collisions
138
137
  const cssKey = inputCSS.length > 2048
139
- ? (inputCSS.length + '|' + inputCSS.slice(0, 50) + inputCSS.slice(-50) + '|' + type + '|' + cssSig)
138
+ ? (hashContent(inputCSS) + '|' + type + '|' + cssSig)
140
139
  : (inputCSS + '|' + type + '|' + cssSig);
141
140
 
142
141
  try {
@@ -246,8 +245,8 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
246
245
  // Select pre-computed signature based on engine
247
246
  const optsSig = useEngine === 'terser' ? terserSig : swcSig;
248
247
 
249
- // For large inputs, use length and content fingerprint to prevent collisions
250
- jsKey = (code.length > 2048 ? (code.length + '|' + code.slice(0, 50) + code.slice(-50) + '|') : (code + '|'))
248
+ // For large inputs, hash the full content to avoid storing huge strings as Map keys
249
+ jsKey = (code.length > 2048 ? (hashContent(code) + '|') : (code + '|'))
251
250
  + (inline ? '1' : '0') + '|' + (isModule ? 'm' : '') + '|' + useEngine + '|' + optsSig;
252
251
 
253
252
  const cached = jsMinifyCache.get(jsKey);
@@ -359,9 +358,9 @@ const processOptions = (inputOptions, { getLightningCSS, getTerser, getSwc, getS
359
358
  return svgContent;
360
359
  }
361
360
 
362
- // Cache key
361
+ // Cache key: Large inputs are hashed to avoid huge Map keys
363
362
  const svgKey = svgContent.length > 2048
364
- ? (svgContent.length + '|' + svgContent.slice(0, 50) + svgContent.slice(-50) + '|' + svgSig)
363
+ ? (hashContent(svgContent) + '|' + svgSig)
365
364
  : (svgContent + '|' + svgSig);
366
365
 
367
366
  try {
package/src/lib/utils.js CHANGED
@@ -39,6 +39,17 @@ class LRU {
39
39
  delete(key) { this.map.delete(key); }
40
40
  }
41
41
 
42
+ // FNV-1a 32-bit hash for large-input cache keys
43
+
44
+ function hashContent(str) {
45
+ let hash = 2166136261;
46
+ for (let i = 0; i < str.length; i++) {
47
+ hash ^= str.charCodeAt(i);
48
+ hash = Math.imul(hash, 16777619);
49
+ }
50
+ return (hash >>> 0).toString(36);
51
+ }
52
+
42
53
  // Unique ID generator
43
54
 
44
55
  function uniqueId(value) {
@@ -102,6 +113,7 @@ function parseRegExp(value) {
102
113
 
103
114
  export { stableStringify };
104
115
  export { LRU };
116
+ export { hashContent };
105
117
  export { uniqueId };
106
118
  export { identity };
107
119
  export { isThenable };
@@ -211,15 +211,15 @@ function collapseWhitespaceSmart(str, prevTag, nextTag, prevAttrs, nextAttrs, op
211
211
 
212
212
  // Collapse/trim whitespace for given tag
213
213
 
214
- const noCollapseWsTags = new Set(['script', 'style', 'pre', 'textarea']);
215
- const noTrimWsTags = new Set(['pre', 'textarea']);
214
+ const noCollapseWhitespaceTags = new Set(['script', 'style', 'pre', 'textarea']);
215
+ const noTrimWhitespaceTags = new Set(['pre', 'textarea']);
216
216
 
217
217
  function canCollapseWhitespace(tag) {
218
- return !noCollapseWsTags.has(tag);
218
+ return !noCollapseWhitespaceTags.has(tag);
219
219
  }
220
220
 
221
221
  function canTrimWhitespace(tag) {
222
- return !noTrimWsTags.has(tag);
222
+ return !noTrimWhitespaceTags.has(tag);
223
223
  }
224
224
 
225
225
  // Exports