html-minifier-next 4.17.2 → 4.19.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.
@@ -36,6 +36,26 @@ export type MinifierOptions = {
36
36
  * Default: Built-in `canTrimWhitespace` function
37
37
  */
38
38
  canTrimWhitespace?: (tag: string | null, attrs: HTMLAttribute[] | undefined, canTrimWhitespace: (tag: string) => boolean) => boolean;
39
+ /**
40
+ * The maximum number of entries for the CSS minification cache. Higher values
41
+ * improve performance for inputs with repeated CSS (e.g., batch processing).
42
+ * - Cache is created on first `minify()` call and persists for the process lifetime
43
+ * - Cache size is locked after first call—subsequent calls reuse the same cache
44
+ * - Explicit `0` values are coerced to `1` (minimum functional cache size)
45
+ *
46
+ * Default: `500` (or `1000` when `CI=true` environment variable is set)
47
+ */
48
+ cacheCSS?: number;
49
+ /**
50
+ * The maximum number of entries for the JavaScript minification cache. Higher
51
+ * values improve performance for inputs with repeated JavaScript.
52
+ * - Cache is created on first `minify()` call and persists for the process lifetime
53
+ * - Cache size is locked after first call—subsequent calls reuse the same cache
54
+ * - Explicit `0` values are coerced to `1` (minimum functional cache size)
55
+ *
56
+ * Default: `500` (or `1000` when `CI=true` environment variable is set)
57
+ */
58
+ cacheJS?: number;
39
59
  /**
40
60
  * When true, tag and attribute names are treated as case-sensitive.
41
61
  * Useful for custom HTML tags.
@@ -208,6 +228,14 @@ export type MinifierOptions = {
208
228
  * Default: No limit
209
229
  */
210
230
  maxLineLength?: number;
231
+ /**
232
+ * When true, consecutive inline `<script>` elements are merged into one.
233
+ * Only merges compatible scripts (same `type`, matching `async`/`defer`/
234
+ * `nomodule`/`nonce` attributes). Does not merge external scripts (with `src`).
235
+ *
236
+ * Default: `false`
237
+ */
238
+ mergeScripts?: boolean;
211
239
  /**
212
240
  * When true, enables CSS minification for inline `<style>` tags or
213
241
  * `style` attributes. If an object is provided, it is passed to
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AA+CA,4BAAoE;AA8EpE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,uBA+fC;CACF"}
1
+ {"version":3,"file":"htmlparser.d.ts","sourceRoot":"","sources":["../../src/htmlparser.js"],"names":[],"mappings":"AA+CA,4BAAkE;AA8ElE;IACE,qCAGC;IAFC,UAAgB;IAChB,aAAsB;IAGxB,uBA+iBC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AA0BA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAEC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAgJC;AAsBD;;;;GAwCC;AAED,6GAuHC"}
1
+ {"version":3,"file":"attributes.d.ts","sourceRoot":"","sources":["../../../src/lib/attributes.js"],"names":[],"mappings":"AA2BA,yDAEC;AAED,mEAOC;AAED,uEAWC;AAED,8DAGC;AAED,4EAOC;AAED,mGAuCC;AAED,mEAGC;AAED,qEAGC;AAED,kEAWC;AAED,sEAGC;AAED,8DAWC;AAED,2EAIC;AAED,qEAaC;AAED,wEAUC;AAED,sEAUC;AAED,2EAEC;AAED,2DAEC;AAED,8DAUC;AAED,uEAUC;AAED,oGASC;AAED,4DAOC;AAID,0IAgJC;AAsBD;;;;GAwCC;AAED,6GAuHC"}
@@ -79,6 +79,7 @@ export const keepScriptsMimetypes: Set<string>;
79
79
  export const jsonScriptTypes: Set<string>;
80
80
  export const isSimpleBoolean: Set<string>;
81
81
  export const isBooleanValue: Set<string>;
82
+ export const emptyCollapsible: Set<string>;
82
83
  export const srcsetElements: Set<string>;
83
84
  export const optionalStartTags: Set<string>;
84
85
  export const optionalEndTags: Set<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"AAEA,iCAAoC;AACpC,+BAAkC;AAClC,oCAA2C;AAC3C,2CAAmD;AACnD,wCAA8C;AAC9C,4CAAkD;AAClD,4CAA2C;AAC3C,4CAA0D;AAC1D,2CAA8C;AAC9C,+CAA0D;AAC1D,2CAAmC;AACnC,mCAA4C;AAC5C,wCAAwqB;AACxqB,kCAA0B;AAC1B,sCAAuC;AACvC,yCAA4C;AAC5C,qCAAuD;AACvD,sCAAmE;AAKnE,+DAAgb;AAGhb,+DAA6O;AAG7O,yDAAmF;AAGnF,8CAA8G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0C9G,qDAWG;AAEH,+CAEG;AAcH,0CAUG;AApBH,0CAAwhB;AAExhB,yCAAkD;AAIlD,yCAAkD;AAuBlD,4CAAiF;AAEjF,0CAAoM;AAEpM,yCAA4F;AAE5F,8CAAkD;AAElD,yCAAiT;AAEjT,0CAA0F;AAE1F,6CAA8D;AAE9D,gDAAqD;AAErD,yCAAuD;AAEvD,+CAAyD;AAEzD,+CAAkE;AAElE,uCAA2C;AAE3C,2CAA2D;AAE3D,0CAAkD;AAElD,wCAA+D;AAE/D,2CAAkD;AAElD,uCAAmxC;AAInxC,sCAEsD;AAItD,iDAA4D"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.js"],"names":[],"mappings":"AAEA,iCAAoC;AACpC,+BAAkC;AAClC,oCAA2C;AAC3C,2CAAmD;AACnD,wCAA8C;AAC9C,4CAAkD;AAClD,4CAA2C;AAC3C,4CAA0D;AAC1D,2CAA8C;AAC9C,+CAA0D;AAC1D,2CAAmC;AACnC,mCAA4C;AAC5C,wCAAwqB;AACxqB,kCAA0B;AAC1B,sCAAuC;AACvC,yCAA4C;AAC5C,qCAAuD;AACvD,sCAAmE;AAKnE,+DAAgb;AAGhb,+DAA6O;AAG7O,yDAAmF;AAGnF,8CAA8G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0C9G,qDAWG;AAEH,+CAEG;AAmBH,0CAUG;AAzBH,0CAAwhB;AAExhB,yCAAkD;AAKlD,2CAAqE;AAIrE,yCAAkD;AAuBlD,4CAAiF;AAEjF,0CAAoM;AAEpM,yCAA4F;AAE5F,8CAAkD;AAElD,yCAAiT;AAEjT,0CAA0F;AAE1F,6CAA8D;AAE9D,gDAAqD;AAErD,yCAAuD;AAEvD,+CAAyD;AAEzD,+CAAkE;AAElE,uCAA2C;AAE3C,2CAA2D;AAE3D,0CAAkD;AAElD,wCAA+D;AAE/D,2CAAkD;AAElD,uCAAmxC;AAInxC,sCAEsD;AAItD,iDAA4D"}
@@ -7,10 +7,9 @@ export function shouldMinifyInnerHTML(options: any): boolean;
7
7
  * @param {Function} deps.getSwc - Function to lazily load @swc/core
8
8
  * @param {LRU} deps.cssMinifyCache - CSS minification cache
9
9
  * @param {LRU} deps.jsMinifyCache - JS minification cache
10
- * @param {LRU} deps.urlMinifyCache - URL minification cache
11
10
  * @returns {MinifierOptions} Normalized options with defaults applied
12
11
  */
13
- export function processOptions(inputOptions: Partial<MinifierOptions>, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache, urlMinifyCache }?: {
12
+ export function processOptions(inputOptions: Partial<MinifierOptions>, { getLightningCSS, getTerser, getSwc, cssMinifyCache, jsMinifyCache }?: {
14
13
  getLightningCSS: Function;
15
14
  getTerser: Function;
16
15
  getSwc: Function;
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAYA,6DAUC;AAID;;;;;;;;;;GAUG;AACH,6CAVW,OAAO,CAAC,eAAe,CAAC,0FAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAI9C,eAAe,CA4V3B"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.js"],"names":[],"mappings":"AAYA,6DAUC;AAID;;;;;;;;;GASG;AACH,6CATW,OAAO,CAAC,eAAe,CAAC,0EAEhC;IAAuB,eAAe;IACf,SAAS;IACT,MAAM;CAA2B,GAG9C,eAAe,CAuV3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../src/lib/svg.js"],"names":[],"mappings":"AAmVA;;;;;;GAMG;AACH,8CALW,MAAM,SACN,MAAM,kBAEJ,MAAM,CA0BlB;AAED;;;;;;;GAOG;AACH,8CANW,MAAM,QACN,MAAM,SACN,MAAM,kBAEJ,OAAO,CAanB;AAED;;;;GAIG;AACH,6DAkBC"}
1
+ {"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../../../src/lib/svg.js"],"names":[],"mappings":"AA0VA;;;;;;GAMG;AACH,8CALW,MAAM,SACN,MAAM,kBAEJ,MAAM,CA0BlB;AAED;;;;;;;GAOG;AACH,8CANW,MAAM,QACN,MAAM,SACN,MAAM,kBAEJ,OAAO,CAanB;AAED;;;;GAIG;AACH,6DAkBC"}
@@ -34,6 +34,7 @@ export namespace presets {
34
34
  export { continueOnParseError_1 as continueOnParseError };
35
35
  let decodeEntities_1: boolean;
36
36
  export { decodeEntities_1 as decodeEntities };
37
+ export let mergeScripts: boolean;
37
38
  export let minifyCSS: boolean;
38
39
  export let minifyJS: boolean;
39
40
  export let minifySVG: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.js"],"names":[],"mappings":"AAgDA;;;;GAIG;AACH,gCAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAMvB;AAED;;;GAGG;AACH,kCAFa,MAAM,EAAE,CAIpB"}
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.js"],"names":[],"mappings":"AAiDA;;;;GAIG;AACH,gCAHW,MAAM,GACJ,MAAM,GAAC,IAAI,CAMvB;AAED;;;GAGG;AACH,kCAFa,MAAM,EAAE,CAIpB"}
package/package.json CHANGED
@@ -98,5 +98,5 @@
98
98
  },
99
99
  "type": "module",
100
100
  "types": "./dist/types/htmlminifier.d.ts",
101
- "version": "4.17.2"
101
+ "version": "4.19.0"
102
102
  }
@@ -91,11 +91,129 @@ async function getSwc() {
91
91
  return swcPromise;
92
92
  }
93
93
 
94
- // Minification caches
94
+ // Minification caches (initialized on first use with configurable sizes)
95
+ let cssMinifyCache = null;
96
+ let jsMinifyCache = null;
95
97
 
96
- const cssMinifyCache = new LRU(500);
97
- const jsMinifyCache = new LRU(500);
98
- const urlMinifyCache = new LRU(500);
98
+ // Pre-compiled patterns for script merging (avoid repeated allocation in hot path)
99
+ const RE_SCRIPT_ATTRS = /([^\s=]+)(?:=(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g;
100
+ const SCRIPT_BOOL_ATTRS = new Set(['async', 'defer', 'nomodule']);
101
+ const DEFAULT_JS_TYPES = new Set(['', 'text/javascript', 'application/javascript']);
102
+
103
+ // Pre-compiled patterns for buffer scanning
104
+ const RE_START_TAG = /^<[^/!]/;
105
+ const RE_END_TAG = /^<\//;
106
+
107
+ // Script merging
108
+
109
+ /**
110
+ * Merge consecutive inline script tags into one (`mergeConsecutiveScripts`).
111
+ * Only merges scripts that are compatible:
112
+ * - Both inline (no `src` attribute)
113
+ * - Same `type` (or both default JavaScript)
114
+ * - No conflicting attributes (`async`, `defer`, `nomodule`, different `nonce`)
115
+ *
116
+ * Limitation: This function uses regex-based matching (`pattern` variable below),
117
+ * which can produce incorrect results if a script’s content contains a literal
118
+ * `</script>` string (e.g., `document.write('<script>…</script>')`). In valid
119
+ * HTML, such strings should be escaped as `<\/script>` or split like
120
+ * `'</scr' + 'ipt>'`, so this limitation rarely affects real-world code. The
121
+ * earlier `minifyJS` step (if enabled) typically handles this escaping already.
122
+ *
123
+ * @param {string} html - The HTML string to process
124
+ * @returns {string} HTML with consecutive scripts merged
125
+ */
126
+ function mergeConsecutiveScripts(html) {
127
+ // `pattern`: Regex to match consecutive `</script>` followed by `<script…>`.
128
+ // See function JSDoc above for known limitations with literal `</script>` in content.
129
+ // Captures:
130
+ // 1. first script attrs
131
+ // 2. first script content
132
+ // 3. whitespace between
133
+ // 4. second script attrs
134
+ // 5. second script content
135
+ const pattern = /<script([^>]*)>([\s\S]*?)<\/script>([\s]*)<script([^>]*)>([\s\S]*?)<\/script>/gi;
136
+
137
+ let result = html;
138
+ let changed = true;
139
+
140
+ // Keep merging until no more changes (handles chains of 3+ scripts)
141
+ while (changed) {
142
+ changed = false;
143
+ result = result.replace(pattern, (match, attrs1, content1, whitespace, attrs2, content2) => {
144
+ // Parse attributes from both script tags (uses pre-compiled RE_SCRIPT_ATTRS)
145
+ const parseAttrs = (attrStr) => {
146
+ const attrs = {};
147
+ RE_SCRIPT_ATTRS.lastIndex = 0; // Reset for reuse
148
+ let m;
149
+ while ((m = RE_SCRIPT_ATTRS.exec(attrStr)) !== null) {
150
+ const name = m[1].toLowerCase();
151
+ const value = m[2] ?? m[3] ?? m[4] ?? '';
152
+ attrs[name] = value;
153
+ }
154
+ return attrs;
155
+ };
156
+
157
+ const a1 = parseAttrs(attrs1);
158
+ const a2 = parseAttrs(attrs2);
159
+
160
+ // Check for `src`—cannot merge external scripts
161
+ if ('src' in a1 || 'src' in a2) {
162
+ return match;
163
+ }
164
+
165
+ // Check `type` compatibility (both must be same, or both default JS)
166
+ const type1 = a1.type || '';
167
+ const type2 = a2.type || '';
168
+
169
+ if (DEFAULT_JS_TYPES.has(type1) && DEFAULT_JS_TYPES.has(type2)) {
170
+ // Both are default JavaScript—compatible
171
+ } else if (type1 === type2) {
172
+ // Same explicit type—compatible
173
+ } else {
174
+ // Incompatible types
175
+ return match;
176
+ }
177
+
178
+ // Check for conflicting boolean attributes (uses pre-compiled SCRIPT_BOOL_ATTRS)
179
+ for (const attr of SCRIPT_BOOL_ATTRS) {
180
+ const has1 = attr in a1;
181
+ const has2 = attr in a2;
182
+ if (has1 !== has2) {
183
+ // One has it, one doesn't - incompatible
184
+ return match;
185
+ }
186
+ }
187
+
188
+ // Check `nonce`—must be same or both absent
189
+ if (a1.nonce !== a2.nonce) {
190
+ return match;
191
+ }
192
+
193
+ // Scripts are compatible—merge them
194
+ changed = true;
195
+
196
+ // Combine content—use semicolon normally, newline only for trailing `//` comments
197
+ const c1 = content1.trim();
198
+ const c2 = content2.trim();
199
+ let mergedContent;
200
+ if (c1 && c2) {
201
+ // Check if last line of c1 contains `//` (single-line comment)
202
+ // If so, use newline to terminate it; otherwise use semicolon (if not already present)
203
+ const lastLine = c1.slice(c1.lastIndexOf('\n') + 1);
204
+ const separator = lastLine.includes('//') ? '\n' : (c1.endsWith(';') ? '' : ';');
205
+ mergedContent = c1 + separator + c2;
206
+ } else {
207
+ mergedContent = c1 || c2;
208
+ }
209
+
210
+ // Use first script’s attributes (they should be compatible)
211
+ return `<script${attrs1}>${mergedContent}</script>`;
212
+ });
213
+ }
214
+
215
+ return result;
216
+ }
99
217
 
100
218
  // Type definitions
101
219
 
@@ -128,6 +246,24 @@ const urlMinifyCache = new LRU(500);
128
246
  *
129
247
  * Default: Built-in `canTrimWhitespace` function
130
248
  *
249
+ * @prop {number} [cacheCSS]
250
+ * The maximum number of entries for the CSS minification cache. Higher values
251
+ * improve performance for inputs with repeated CSS (e.g., batch processing).
252
+ * - Cache is created on first `minify()` call and persists for the process lifetime
253
+ * - Cache size is locked after first call—subsequent calls reuse the same cache
254
+ * - Explicit `0` values are coerced to `1` (minimum functional cache size)
255
+ *
256
+ * Default: `500` (or `1000` when `CI=true` environment variable is set)
257
+ *
258
+ * @prop {number} [cacheJS]
259
+ * The maximum number of entries for the JavaScript minification cache. Higher
260
+ * values improve performance for inputs with repeated JavaScript.
261
+ * - Cache is created on first `minify()` call and persists for the process lifetime
262
+ * - Cache size is locked after first call—subsequent calls reuse the same cache
263
+ * - Explicit `0` values are coerced to `1` (minimum functional cache size)
264
+ *
265
+ * Default: `500` (or `1000` when `CI=true` environment variable is set)
266
+ *
131
267
  * @prop {boolean} [caseSensitive]
132
268
  * When true, tag and attribute names are treated as case-sensitive.
133
269
  * Useful for custom HTML tags.
@@ -277,6 +413,13 @@ const urlMinifyCache = new LRU(500);
277
413
  *
278
414
  * Default: No limit
279
415
  *
416
+ * @prop {boolean} [mergeScripts]
417
+ * When true, consecutive inline `<script>` elements are merged into one.
418
+ * Only merges compatible scripts (same `type`, matching `async`/`defer`/
419
+ * `nomodule`/`nonce` attributes). Does not merge external scripts (with `src`).
420
+ *
421
+ * Default: `false`
422
+ *
280
423
  * @prop {boolean | Partial<import("lightningcss").TransformOptions<import("lightningcss").CustomAtRules>> | ((text: string, type?: string) => Promise<string> | string)} [minifyCSS]
281
424
  * When true, enables CSS minification for inline `<style>` tags or
282
425
  * `style` attributes. If an object is provided, it is passed to
@@ -855,7 +998,7 @@ async function minifyHTML(value, options, partialMarkup) {
855
998
 
856
999
  function removeStartTag() {
857
1000
  let index = buffer.length - 1;
858
- while (index > 0 && !/^<[^/!]/.test(buffer[index])) {
1001
+ while (index > 0 && !RE_START_TAG.test(buffer[index])) {
859
1002
  index--;
860
1003
  }
861
1004
  buffer.length = Math.max(0, index);
@@ -863,7 +1006,7 @@ async function minifyHTML(value, options, partialMarkup) {
863
1006
 
864
1007
  function removeEndTag() {
865
1008
  let index = buffer.length - 1;
866
- while (index > 0 && !/^<\//.test(buffer[index])) {
1009
+ while (index > 0 && !RE_END_TAG.test(buffer[index])) {
867
1010
  index--;
868
1011
  }
869
1012
  buffer.length = Math.max(0, index);
@@ -1174,8 +1317,8 @@ async function minifyHTML(value, options, partialMarkup) {
1174
1317
  charsPrevTag = /^\s*$/.test(text) ? prevTag : 'comment';
1175
1318
  if (options.decodeEntities && text && !specialContentElements.has(currentTag)) {
1176
1319
  // Escape any `&` symbols that start either:
1177
- // 1) a legacy-named character reference (i.e., one that doesn’t end with `;`)
1178
- // 2) or any other character reference (i.e., one that does end with `;`)
1320
+ // 1. a legacy-named character reference (i.e., one that doesn’t end with `;`)
1321
+ // 2. or any other character reference (i.e., one that does end with `;`)
1179
1322
  // Note that `&` can be escaped as `&amp`, without the semicolon.
1180
1323
  // https://mathiasbynens.be/notes/ambiguous-ampersands
1181
1324
  if (text.indexOf('&') !== -1) {
@@ -1379,6 +1522,49 @@ function joinResultSegments(results, options, restoreCustom, restoreIgnore) {
1379
1522
  return options.collapseWhitespace ? collapseWhitespace(str, options, true, true) : str;
1380
1523
  }
1381
1524
 
1525
+ /**
1526
+ * Initialize minification caches with configurable sizes.
1527
+ *
1528
+ * Important behavior notes:
1529
+ * - Caches are created on the first `minify()` call and persist for the lifetime of the process
1530
+ * - Cache sizes are locked after first initialization—subsequent calls use the same caches
1531
+ * even if different `cacheCSS`/`cacheJS` options are provided
1532
+ * - The first call’s options determine the cache sizes for subsequent calls
1533
+ * - Explicit `0` values are coerced to `1` (minimum functional cache size)
1534
+ */
1535
+ function initCaches(options) {
1536
+ // Only create caches once (on first call)—sizes are locked after this
1537
+ if (!cssMinifyCache) {
1538
+ // Determine default size based on environment
1539
+ const defaultSize = process.env.CI === 'true' ? 1000 : 500;
1540
+
1541
+ // Helper to parse env var—returns parsed number (including 0) or undefined if absent, invalid, or negative
1542
+ const parseEnvCacheSize = (envVar) => {
1543
+ if (envVar === undefined) return undefined;
1544
+ const parsed = Number(envVar);
1545
+ if (Number.isNaN(parsed) || !Number.isFinite(parsed) || parsed < 0) {
1546
+ return undefined;
1547
+ }
1548
+ return parsed;
1549
+ };
1550
+
1551
+ // Get cache sizes with precedence: Options > env > default
1552
+ const cssSize = options.cacheCSS !== undefined ? options.cacheCSS
1553
+ : (parseEnvCacheSize(process.env.HMN_CACHE_CSS) ?? defaultSize);
1554
+ const jsSize = options.cacheJS !== undefined ? options.cacheJS
1555
+ : (parseEnvCacheSize(process.env.HMN_CACHE_JS) ?? defaultSize);
1556
+
1557
+ // Coerce `0` to `1` (minimum functional cache size) to avoid immediate eviction
1558
+ const cssFinalSize = cssSize === 0 ? 1 : cssSize;
1559
+ const jsFinalSize = jsSize === 0 ? 1 : jsSize;
1560
+
1561
+ cssMinifyCache = new LRU(cssFinalSize);
1562
+ jsMinifyCache = new LRU(jsFinalSize);
1563
+ }
1564
+
1565
+ return { cssMinifyCache, jsMinifyCache };
1566
+ }
1567
+
1382
1568
  /**
1383
1569
  * @param {string} value
1384
1570
  * @param {MinifierOptions} [options]
@@ -1386,15 +1572,23 @@ function joinResultSegments(results, options, restoreCustom, restoreIgnore) {
1386
1572
  */
1387
1573
  export const minify = async function (value, options) {
1388
1574
  const start = Date.now();
1575
+
1576
+ // Initialize caches on first use with configurable sizes
1577
+ const caches = initCaches(options || {});
1578
+
1389
1579
  options = processOptions(options || {}, {
1390
1580
  getLightningCSS,
1391
1581
  getTerser,
1392
1582
  getSwc,
1393
- cssMinifyCache,
1394
- jsMinifyCache,
1395
- urlMinifyCache
1583
+ ...caches
1396
1584
  });
1397
- const result = await minifyHTML(value, options);
1585
+ let result = await minifyHTML(value, options);
1586
+
1587
+ // Post-processing: Merge consecutive inline scripts if enabled
1588
+ if (options.mergeScripts) {
1589
+ result = mergeConsecutiveScripts(result);
1590
+ }
1591
+
1398
1592
  options.log('minified in: ' + (Date.now() - start) + 'ms');
1399
1593
  return result;
1400
1594
  };