html-minifier-next 4.1.1 → 4.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 +1 -1
- package/cli.js +10 -10
- package/dist/htmlminifier.cjs +2 -2
- package/dist/htmlminifier.esm.bundle.js +31 -19
- package/package.json +2 -2
- package/src/htmlminifier.js +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
HTML Minifier Next (HMN) is a highly **configurable, well-tested, JavaScript-based HTML minifier**.
|
|
6
6
|
|
|
7
|
-
The project has been based on [Terser
|
|
7
|
+
The project has been based on [HTML Minifier Terser](https://github.com/terser/html-minifier-terser), which in turn had been based on [Juriy Zaytsev’s HTML Minifier](https://github.com/kangax/html-minifier) (HMN offers additional features, but is compatible with both). It was set up because as of 2025, both HTML Minifier Terser and HTML Minifier have been unmaintained for some time. As the project seems maintainable [to me, [Jens](https://meiert.com/)]—even more so with community support—, it’s being updated and documented further in this place.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
package/cli.js
CHANGED
|
@@ -180,8 +180,8 @@ program.option('-d --dry', 'Dry run: process and report statistics without writi
|
|
|
180
180
|
function readFile(file) {
|
|
181
181
|
try {
|
|
182
182
|
return fs.readFileSync(file, { encoding: 'utf8' });
|
|
183
|
-
} catch (
|
|
184
|
-
fatal('Cannot read ' + file + '\n' +
|
|
183
|
+
} catch (err) {
|
|
184
|
+
fatal('Cannot read ' + file + '\n' + err.message);
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -196,7 +196,7 @@ async function loadConfigFromPath(configPath) {
|
|
|
196
196
|
// Try JSON first
|
|
197
197
|
try {
|
|
198
198
|
return JSON.parse(data);
|
|
199
|
-
} catch (
|
|
199
|
+
} catch (jsonErr) {
|
|
200
200
|
const abs = path.resolve(configPath);
|
|
201
201
|
|
|
202
202
|
// Try CJS require
|
|
@@ -204,13 +204,13 @@ async function loadConfigFromPath(configPath) {
|
|
|
204
204
|
const result = require(abs);
|
|
205
205
|
// Handle ESM interop: if `require()` loads an ESM file, it may return `{__esModule: true, default: …}`
|
|
206
206
|
return (result && result.__esModule && result.default) ? result.default : result;
|
|
207
|
-
} catch (
|
|
207
|
+
} catch (cjsErr) {
|
|
208
208
|
// Try ESM import
|
|
209
209
|
try {
|
|
210
210
|
const mod = await import(pathToFileURL(abs).href);
|
|
211
211
|
return mod.default || mod;
|
|
212
|
-
} catch (
|
|
213
|
-
fatal('Cannot read the specified config file.\nAs JSON: ' +
|
|
212
|
+
} catch (esmErr) {
|
|
213
|
+
fatal('Cannot read the specified config file.\nAs JSON: ' + jsonErr.message + '\nAs CJS: ' + cjsErr.message + '\nAs ESM: ' + esmErr.message);
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
}
|
|
@@ -307,8 +307,8 @@ program.option('--file-ext <extensions>', 'Specify file extension(s) to process
|
|
|
307
307
|
let minified;
|
|
308
308
|
try {
|
|
309
309
|
minified = await minify(data, createOptions());
|
|
310
|
-
} catch (
|
|
311
|
-
fatal('Minification error on ' + inputFile + '\n' +
|
|
310
|
+
} catch (err) {
|
|
311
|
+
fatal('Minification error on ' + inputFile + '\n' + err.message);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
const stats = calculateStats(data, minified);
|
|
@@ -466,8 +466,8 @@ program.option('--file-ext <extensions>', 'Specify file extension(s) to process
|
|
|
466
466
|
|
|
467
467
|
try {
|
|
468
468
|
minified = await minify(content, minifierOptions);
|
|
469
|
-
} catch (
|
|
470
|
-
fatal('Minification error:\n' +
|
|
469
|
+
} catch (err) {
|
|
470
|
+
fatal('Minification error:\n' + err.message);
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
const stats = calculateStats(content, minified);
|
package/dist/htmlminifier.cjs
CHANGED
|
@@ -1328,8 +1328,8 @@ const processOptions = (inputOptions) => {
|
|
|
1328
1328
|
try {
|
|
1329
1329
|
const result = await terser.minify(code, terserOptions);
|
|
1330
1330
|
return result.code.replace(/;$/, '');
|
|
1331
|
-
} catch (
|
|
1332
|
-
options.log(
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
options.log(err);
|
|
1333
1333
|
return text;
|
|
1334
1334
|
}
|
|
1335
1335
|
};
|
|
@@ -22332,8 +22332,8 @@ function remove_initializers(var_statement) {
|
|
|
22332
22332
|
return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
|
|
22333
22333
|
}
|
|
22334
22334
|
|
|
22335
|
-
/** Called on code which
|
|
22336
|
-
function
|
|
22335
|
+
/** Called on code which won't be executed but has an effect outside of itself: `var`, `function` statements, `export`, `import`. */
|
|
22336
|
+
function extract_from_unreachable_code(compressor, stat, target) {
|
|
22337
22337
|
walk(stat, node => {
|
|
22338
22338
|
if (node instanceof AST_Var) {
|
|
22339
22339
|
const no_initializers = remove_initializers(node);
|
|
@@ -22358,7 +22358,8 @@ function trim_unreachable_code(compressor, stat, target) {
|
|
|
22358
22358
|
target.push(node);
|
|
22359
22359
|
return true;
|
|
22360
22360
|
}
|
|
22361
|
-
if (node instanceof AST_Scope) {
|
|
22361
|
+
if (node instanceof AST_Scope || node instanceof AST_Class) {
|
|
22362
|
+
// Do not go into nested scopes
|
|
22362
22363
|
return true;
|
|
22363
22364
|
}
|
|
22364
22365
|
});
|
|
@@ -23382,7 +23383,7 @@ function tighten_body(statements, compressor) {
|
|
|
23382
23383
|
CHANGED = n != len;
|
|
23383
23384
|
if (has_quit)
|
|
23384
23385
|
has_quit.forEach(function (stat) {
|
|
23385
|
-
|
|
23386
|
+
extract_from_unreachable_code(compressor, stat, statements);
|
|
23386
23387
|
});
|
|
23387
23388
|
}
|
|
23388
23389
|
|
|
@@ -24565,6 +24566,11 @@ class Compressor extends TreeWalker {
|
|
|
24565
24566
|
}
|
|
24566
24567
|
}
|
|
24567
24568
|
|
|
24569
|
+
/** True if compressor.self()'s result will be turned into a 32-bit integer.
|
|
24570
|
+
* ex:
|
|
24571
|
+
* ~{expr}
|
|
24572
|
+
* (1, 2, {expr}) | 0
|
|
24573
|
+
**/
|
|
24568
24574
|
in_32_bit_context(other_operand_must_be_number) {
|
|
24569
24575
|
if (!this.option("evaluate")) return false;
|
|
24570
24576
|
var self = this.self();
|
|
@@ -24582,9 +24588,10 @@ class Compressor extends TreeWalker {
|
|
|
24582
24588
|
if (
|
|
24583
24589
|
p instanceof AST_Binary
|
|
24584
24590
|
&& (
|
|
24585
|
-
p.
|
|
24586
|
-
|
|
24587
|
-
|| p.operator == "
|
|
24591
|
+
// Don't talk about p.left. Can change branch taken
|
|
24592
|
+
p.operator == "&&" && p.right === self
|
|
24593
|
+
|| p.operator == "||" && p.right === self
|
|
24594
|
+
|| p.operator == "??" && p.right === self
|
|
24588
24595
|
)
|
|
24589
24596
|
|| p instanceof AST_Conditional && p.condition !== self
|
|
24590
24597
|
|| p.tail_node() === self
|
|
@@ -25187,7 +25194,7 @@ function if_break_in_loop(self, compressor) {
|
|
|
25187
25194
|
body: self.condition
|
|
25188
25195
|
}));
|
|
25189
25196
|
}
|
|
25190
|
-
|
|
25197
|
+
extract_from_unreachable_code(compressor, self.body, body);
|
|
25191
25198
|
return make_node(AST_BlockStatement, self, {
|
|
25192
25199
|
body: body
|
|
25193
25200
|
});
|
|
@@ -25258,7 +25265,7 @@ def_optimize(AST_For, function(self, compressor) {
|
|
|
25258
25265
|
if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
|
25259
25266
|
if (!cond) {
|
|
25260
25267
|
var body = [];
|
|
25261
|
-
|
|
25268
|
+
extract_from_unreachable_code(compressor, self.body, body);
|
|
25262
25269
|
if (self.init instanceof AST_Statement) {
|
|
25263
25270
|
body.push(self.init);
|
|
25264
25271
|
} else if (self.init) {
|
|
@@ -25294,7 +25301,7 @@ def_optimize(AST_If, function(self, compressor) {
|
|
|
25294
25301
|
if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
|
25295
25302
|
if (!cond) {
|
|
25296
25303
|
var body = [];
|
|
25297
|
-
|
|
25304
|
+
extract_from_unreachable_code(compressor, self.body, body);
|
|
25298
25305
|
body.push(make_node(AST_SimpleStatement, self.condition, {
|
|
25299
25306
|
body: self.condition
|
|
25300
25307
|
}));
|
|
@@ -25307,7 +25314,7 @@ def_optimize(AST_If, function(self, compressor) {
|
|
|
25307
25314
|
}));
|
|
25308
25315
|
body.push(self.body);
|
|
25309
25316
|
if (self.alternative) {
|
|
25310
|
-
|
|
25317
|
+
extract_from_unreachable_code(compressor, self.alternative, body);
|
|
25311
25318
|
}
|
|
25312
25319
|
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
|
25313
25320
|
}
|
|
@@ -25435,6 +25442,9 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25435
25442
|
var body = [];
|
|
25436
25443
|
var default_branch;
|
|
25437
25444
|
var exact_match;
|
|
25445
|
+
// - compress self.body into `body`
|
|
25446
|
+
// - find and deduplicate default branch
|
|
25447
|
+
// - find the exact match (`case 1234` inside `switch(1234)`)
|
|
25438
25448
|
for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
|
|
25439
25449
|
branch = self.body[i];
|
|
25440
25450
|
if (branch instanceof AST_Default) {
|
|
@@ -25464,6 +25474,7 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25464
25474
|
}
|
|
25465
25475
|
body.push(branch);
|
|
25466
25476
|
}
|
|
25477
|
+
// i < len if we found an exact_match. eliminate the rest
|
|
25467
25478
|
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
|
|
25468
25479
|
self.body = body;
|
|
25469
25480
|
|
|
@@ -25535,7 +25546,7 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25535
25546
|
let i = body.length - 1;
|
|
25536
25547
|
for (; i >= 0; i--) {
|
|
25537
25548
|
let bbody = body[i].body;
|
|
25538
|
-
|
|
25549
|
+
while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
|
|
25539
25550
|
if (!is_inert_body(body[i])) break;
|
|
25540
25551
|
}
|
|
25541
25552
|
// i now points to the index of a branch that contains a body. By incrementing, it's
|
|
@@ -25551,9 +25562,9 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25551
25562
|
let branch = body[j];
|
|
25552
25563
|
if (branch === default_or_exact) {
|
|
25553
25564
|
default_or_exact = null;
|
|
25554
|
-
body.pop();
|
|
25565
|
+
eliminate_branch(body.pop());
|
|
25555
25566
|
} else if (!branch.expression.has_side_effects(compressor)) {
|
|
25556
|
-
body.pop();
|
|
25567
|
+
eliminate_branch(body.pop());
|
|
25557
25568
|
} else {
|
|
25558
25569
|
break;
|
|
25559
25570
|
}
|
|
@@ -25667,15 +25678,16 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25667
25678
|
// and there's a side-effect somewhere. Just let the below paths take care of it.
|
|
25668
25679
|
}
|
|
25669
25680
|
|
|
25681
|
+
// Reintegrate `decl` (var statements)
|
|
25670
25682
|
if (body.length > 0) {
|
|
25671
25683
|
body[0].body = decl.concat(body[0].body);
|
|
25672
25684
|
}
|
|
25673
|
-
|
|
25674
25685
|
if (body.length == 0) {
|
|
25675
25686
|
return make_node(AST_BlockStatement, self, {
|
|
25676
25687
|
body: decl.concat(statement(self.expression))
|
|
25677
25688
|
}).optimize(compressor);
|
|
25678
25689
|
}
|
|
25690
|
+
|
|
25679
25691
|
if (body.length == 1 && !has_nested_break(self)) {
|
|
25680
25692
|
// This is the last case body, and we've already pruned any breaks, so it's
|
|
25681
25693
|
// safe to hoist.
|
|
@@ -25755,7 +25767,7 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
|
25755
25767
|
if (prev && !aborts(prev)) {
|
|
25756
25768
|
prev.body = prev.body.concat(branch.body);
|
|
25757
25769
|
} else {
|
|
25758
|
-
|
|
25770
|
+
extract_from_unreachable_code(compressor, branch, decl);
|
|
25759
25771
|
}
|
|
25760
25772
|
}
|
|
25761
25773
|
function branches_equivalent(branch, prev, insertBreak) {
|
|
@@ -25809,7 +25821,7 @@ def_optimize(AST_Try, function(self, compressor) {
|
|
|
25809
25821
|
if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
|
|
25810
25822
|
var body = [];
|
|
25811
25823
|
if (self.bcatch) {
|
|
25812
|
-
|
|
25824
|
+
extract_from_unreachable_code(compressor, self.bcatch, body);
|
|
25813
25825
|
}
|
|
25814
25826
|
if (self.bfinally) body.push(...self.bfinally.body);
|
|
25815
25827
|
return make_node(AST_BlockStatement, self, {
|
|
@@ -40369,8 +40381,8 @@ const processOptions = (inputOptions) => {
|
|
|
40369
40381
|
try {
|
|
40370
40382
|
const result = await minify$1(code, terserOptions);
|
|
40371
40383
|
return result.code.replace(/;$/, '');
|
|
40372
|
-
} catch (
|
|
40373
|
-
options.log(
|
|
40384
|
+
} catch (err) {
|
|
40385
|
+
options.log(err);
|
|
40374
40386
|
return text;
|
|
40375
40387
|
}
|
|
40376
40388
|
};
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"entities": "^7.0.0",
|
|
12
12
|
"lightningcss": "^1.28.2",
|
|
13
13
|
"relateurl": "^0.2.7",
|
|
14
|
-
"terser": "^5.44.
|
|
14
|
+
"terser": "^5.44.1"
|
|
15
15
|
},
|
|
16
16
|
"description": "Highly configurable, well-tested, JavaScript-based HTML minifier",
|
|
17
17
|
"devDependencies": {
|
|
@@ -84,5 +84,5 @@
|
|
|
84
84
|
"test:watch": "node --test --watch tests/*.spec.js"
|
|
85
85
|
},
|
|
86
86
|
"type": "module",
|
|
87
|
-
"version": "4.
|
|
87
|
+
"version": "4.2.2"
|
|
88
88
|
}
|
package/src/htmlminifier.js
CHANGED
|
@@ -798,8 +798,8 @@ const processOptions = (inputOptions) => {
|
|
|
798
798
|
try {
|
|
799
799
|
const result = await terser(code, terserOptions);
|
|
800
800
|
return result.code.replace(/;$/, '');
|
|
801
|
-
} catch (
|
|
802
|
-
options.log(
|
|
801
|
+
} catch (err) {
|
|
802
|
+
options.log(err);
|
|
803
803
|
return text;
|
|
804
804
|
}
|
|
805
805
|
};
|