terser 5.8.0 → 5.9.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/CHANGELOG.md +9 -0
- package/dist/bundle.min.js +101 -53
- package/lib/compress/drop-side-effect-free.js +9 -12
- package/lib/compress/evaluate.js +15 -12
- package/lib/compress/index.js +47 -15
- package/lib/compress/inference.js +27 -13
- package/lib/minify.js +2 -0
- package/lib/propmangle.js +3 -3
- package/package.json +1 -1
- package/bin/terser.mjs +0 -21
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## v5.9.0
|
4
|
+
|
5
|
+
- Collapsing switch cases with the same bodies (even if they're not next to each other) (#1070).
|
6
|
+
- Fix evaluation of optional chain expressions (#1062)
|
7
|
+
- Fix mangling collision in ESM exports (#1063)
|
8
|
+
- Fix issue with mutating function objects after a second pass (#1047)
|
9
|
+
- Fix for inlining object spread `{ ...obj }` (#1071)
|
10
|
+
- Typescript typings fix (#1069)
|
11
|
+
|
3
12
|
## v5.8.0
|
4
13
|
|
5
14
|
- Fixed shadowing variables while moving code in some cases (#1065)
|
package/dist/bundle.min.js
CHANGED
@@ -11277,9 +11277,8 @@ function is_undefined(node, compressor) {
|
|
11277
11277
|
);
|
11278
11278
|
}
|
11279
11279
|
|
11280
|
-
//
|
11281
|
-
|
11282
|
-
function is_nullish(node, compressor) {
|
11280
|
+
// Is the node explicitly null or undefined.
|
11281
|
+
function is_null_or_undefined(node, compressor) {
|
11283
11282
|
let fixed;
|
11284
11283
|
return (
|
11285
11284
|
node instanceof AST_Null
|
@@ -11289,13 +11288,29 @@ function is_nullish(node, compressor) {
|
|
11289
11288
|
&& (fixed = node.definition().fixed) instanceof AST_Node
|
11290
11289
|
&& is_nullish(fixed, compressor)
|
11291
11290
|
)
|
11292
|
-
// Recurse into those optional chains!
|
11293
|
-
|| node instanceof AST_PropAccess && node.optional && is_nullish(node.expression, compressor)
|
11294
|
-
|| node instanceof AST_Call && node.optional && is_nullish(node.expression, compressor)
|
11295
|
-
|| node instanceof AST_Chain && is_nullish(node.expression, compressor)
|
11296
11291
|
);
|
11297
11292
|
}
|
11298
11293
|
|
11294
|
+
// Find out if this expression is optionally chained from a base-point that we
|
11295
|
+
// can statically analyze as null or undefined.
|
11296
|
+
function is_nullish_shortcircuited(node, compressor) {
|
11297
|
+
if (node instanceof AST_PropAccess || node instanceof AST_Call) {
|
11298
|
+
return (
|
11299
|
+
(node.optional && is_null_or_undefined(node.expression, compressor))
|
11300
|
+
|| is_nullish_shortcircuited(node.expression, compressor)
|
11301
|
+
);
|
11302
|
+
}
|
11303
|
+
if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
|
11304
|
+
return false;
|
11305
|
+
}
|
11306
|
+
|
11307
|
+
// Find out if something is == null, or can short circuit into nullish.
|
11308
|
+
// Used to optimize ?. and ??
|
11309
|
+
function is_nullish(node, compressor) {
|
11310
|
+
if (is_null_or_undefined(node, compressor)) return true;
|
11311
|
+
return is_nullish_shortcircuited(node, compressor);
|
11312
|
+
}
|
11313
|
+
|
11299
11314
|
// Determine if expression might cause side effects
|
11300
11315
|
// If there's a possibility that a node may change something when it's executed, this returns true
|
11301
11316
|
(function(def_has_side_effects) {
|
@@ -11403,13 +11418,12 @@ function is_nullish(node, compressor) {
|
|
11403
11418
|
return any(this.elements, compressor);
|
11404
11419
|
});
|
11405
11420
|
def_has_side_effects(AST_Dot, function(compressor) {
|
11421
|
+
if (is_nullish(this, compressor)) return false;
|
11406
11422
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
11407
11423
|
|| this.expression.has_side_effects(compressor);
|
11408
11424
|
});
|
11409
11425
|
def_has_side_effects(AST_Sub, function(compressor) {
|
11410
|
-
if (
|
11411
|
-
return false;
|
11412
|
-
}
|
11426
|
+
if (is_nullish(this, compressor)) return false;
|
11413
11427
|
|
11414
11428
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
11415
11429
|
|| this.expression.has_side_effects(compressor)
|
@@ -11477,7 +11491,7 @@ function is_nullish(node, compressor) {
|
|
11477
11491
|
return any(this.body, compressor);
|
11478
11492
|
});
|
11479
11493
|
def_may_throw(AST_Call, function(compressor) {
|
11480
|
-
if (
|
11494
|
+
if (is_nullish(this, compressor)) return false;
|
11481
11495
|
if (any(this.args, compressor)) return true;
|
11482
11496
|
if (this.is_callee_pure(compressor)) return false;
|
11483
11497
|
if (this.expression.may_throw(compressor)) return true;
|
@@ -11536,12 +11550,12 @@ function is_nullish(node, compressor) {
|
|
11536
11550
|
return this.body.may_throw(compressor);
|
11537
11551
|
});
|
11538
11552
|
def_may_throw(AST_Dot, function(compressor) {
|
11553
|
+
if (is_nullish(this, compressor)) return false;
|
11539
11554
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
11540
11555
|
|| this.expression.may_throw(compressor);
|
11541
11556
|
});
|
11542
11557
|
def_may_throw(AST_Sub, function(compressor) {
|
11543
|
-
if (
|
11544
|
-
|
11558
|
+
if (is_nullish(this, compressor)) return false;
|
11545
11559
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
11546
11560
|
|| this.expression.may_throw(compressor)
|
11547
11561
|
|| this.property.may_throw(compressor);
|
@@ -12033,6 +12047,9 @@ function def_eval(node, func) {
|
|
12033
12047
|
node.DEFMETHOD("_eval", func);
|
12034
12048
|
}
|
12035
12049
|
|
12050
|
+
// Used to propagate a nullish short-circuit signal upwards through the chain.
|
12051
|
+
const nullish = Symbol("This AST_Chain is nullish");
|
12052
|
+
|
12036
12053
|
// If the node has been successfully reduced to a constant,
|
12037
12054
|
// then its value is returned; otherwise the element itself
|
12038
12055
|
// is returned.
|
@@ -12044,7 +12061,7 @@ AST_Node.DEFMETHOD("evaluate", function (compressor) {
|
|
12044
12061
|
var val = this._eval(compressor, 1);
|
12045
12062
|
if (!val || val instanceof RegExp)
|
12046
12063
|
return val;
|
12047
|
-
if (typeof val == "function" || typeof val == "object")
|
12064
|
+
if (typeof val == "function" || typeof val == "object" || val == nullish)
|
12048
12065
|
return this;
|
12049
12066
|
return val;
|
12050
12067
|
});
|
@@ -12289,11 +12306,8 @@ const regexp_flags = new Set([
|
|
12289
12306
|
]);
|
12290
12307
|
|
12291
12308
|
def_eval(AST_PropAccess, function (compressor, depth) {
|
12292
|
-
|
12293
|
-
|
12294
|
-
if (obj == null)
|
12295
|
-
return undefined;
|
12296
|
-
}
|
12309
|
+
const obj = this.expression._eval(compressor, depth);
|
12310
|
+
if (obj === nullish || (this.optional && obj == null)) return nullish;
|
12297
12311
|
if (compressor.option("unsafe")) {
|
12298
12312
|
var key = this.property;
|
12299
12313
|
if (key instanceof AST_Node) {
|
@@ -12348,16 +12362,19 @@ def_eval(AST_PropAccess, function (compressor, depth) {
|
|
12348
12362
|
|
12349
12363
|
def_eval(AST_Chain, function (compressor, depth) {
|
12350
12364
|
const evaluated = this.expression._eval(compressor, depth);
|
12351
|
-
return evaluated ===
|
12365
|
+
return evaluated === nullish
|
12366
|
+
? undefined
|
12367
|
+
: evaluated === this.expression
|
12368
|
+
? this
|
12369
|
+
: evaluated;
|
12352
12370
|
});
|
12353
12371
|
|
12354
12372
|
def_eval(AST_Call, function (compressor, depth) {
|
12355
12373
|
var exp = this.expression;
|
12356
|
-
|
12357
|
-
|
12358
|
-
|
12359
|
-
|
12360
|
-
}
|
12374
|
+
|
12375
|
+
const callee = exp._eval(compressor, depth);
|
12376
|
+
if (callee === nullish || (this.optional && callee == null)) return nullish;
|
12377
|
+
|
12361
12378
|
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
12362
12379
|
var key = exp.property;
|
12363
12380
|
if (key instanceof AST_Node) {
|
@@ -12489,8 +12506,8 @@ def_drop_side_effect_free(AST_Constant, return_null);
|
|
12489
12506
|
def_drop_side_effect_free(AST_This, return_null);
|
12490
12507
|
|
12491
12508
|
def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
|
12492
|
-
if (
|
12493
|
-
return
|
12509
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
12510
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
12494
12511
|
}
|
12495
12512
|
|
12496
12513
|
if (!this.is_callee_pure(compressor)) {
|
@@ -12666,21 +12683,19 @@ def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
|
|
12666
12683
|
});
|
12667
12684
|
|
12668
12685
|
def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
|
12669
|
-
if (this
|
12670
|
-
return
|
12686
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
12687
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
12671
12688
|
}
|
12672
|
-
if (this.expression.may_throw_on_access(compressor))
|
12673
|
-
return this;
|
12689
|
+
if (this.expression.may_throw_on_access(compressor)) return this;
|
12674
12690
|
|
12675
12691
|
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
12676
12692
|
});
|
12677
12693
|
|
12678
12694
|
def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
|
12679
|
-
if (this
|
12680
|
-
return
|
12695
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
12696
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
12681
12697
|
}
|
12682
|
-
if (this.expression.may_throw_on_access(compressor))
|
12683
|
-
return this;
|
12698
|
+
if (this.expression.may_throw_on_access(compressor)) return this;
|
12684
12699
|
|
12685
12700
|
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
|
12686
12701
|
if (!expression)
|
@@ -16108,6 +16123,49 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
16108
16123
|
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
|
16109
16124
|
self.body = body;
|
16110
16125
|
|
16126
|
+
let default_or_exact = default_branch || exact_match;
|
16127
|
+
default_branch = null;
|
16128
|
+
exact_match = null;
|
16129
|
+
|
16130
|
+
// group equivalent branches so they will be located next to each other,
|
16131
|
+
// that way the next micro-optimization will merge them.
|
16132
|
+
// ** bail micro-optimization if not a simple switch case with breaks
|
16133
|
+
if (body.every((branch, i) =>
|
16134
|
+
(branch === default_or_exact || !branch.expression.has_side_effects(compressor))
|
16135
|
+
&& (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
|
16136
|
+
) {
|
16137
|
+
for (let i = 0; i < body.length; i++) {
|
16138
|
+
const branch = body[i];
|
16139
|
+
for (let j = i + 1; j < body.length; j++) {
|
16140
|
+
const next = body[j];
|
16141
|
+
if (next.body.length === 0) continue;
|
16142
|
+
const last_branch = j === (body.length - 1);
|
16143
|
+
const equivalentBranch = branches_equivalent(next, branch, false);
|
16144
|
+
if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
|
16145
|
+
if (!equivalentBranch && last_branch) {
|
16146
|
+
next.body.push(make_node(AST_Break));
|
16147
|
+
}
|
16148
|
+
|
16149
|
+
// let's find previous siblings with inert fallthrough...
|
16150
|
+
let x = j - 1;
|
16151
|
+
let fallthroughDepth = 0;
|
16152
|
+
while (x > i) {
|
16153
|
+
if (is_inert_body(body[x--])) {
|
16154
|
+
fallthroughDepth++;
|
16155
|
+
} else {
|
16156
|
+
break;
|
16157
|
+
}
|
16158
|
+
}
|
16159
|
+
|
16160
|
+
const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
|
16161
|
+
body.splice(i + 1, 0, ...plucked);
|
16162
|
+
i += plucked.length;
|
16163
|
+
}
|
16164
|
+
}
|
16165
|
+
}
|
16166
|
+
}
|
16167
|
+
|
16168
|
+
// merge equivalent branches in a row
|
16111
16169
|
for (let i = 0; i < body.length; i++) {
|
16112
16170
|
let branch = body[i];
|
16113
16171
|
if (branch.body.length === 0) continue;
|
@@ -16128,10 +16186,6 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
16128
16186
|
}
|
16129
16187
|
}
|
16130
16188
|
|
16131
|
-
let default_or_exact = default_branch || exact_match;
|
16132
|
-
default_branch = null;
|
16133
|
-
exact_match = null;
|
16134
|
-
|
16135
16189
|
// Prune any empty branches at the end of the switch statement.
|
16136
16190
|
{
|
16137
16191
|
let i = body.length - 1;
|
@@ -16522,10 +16576,6 @@ def_optimize(AST_Call, function(self, compressor) {
|
|
16522
16576
|
}
|
16523
16577
|
}
|
16524
16578
|
|
16525
|
-
if (self.optional && is_nullish(fn, compressor)) {
|
16526
|
-
return make_node(AST_Undefined, self);
|
16527
|
-
}
|
16528
|
-
|
16529
16579
|
var is_func = fn instanceof AST_Lambda;
|
16530
16580
|
|
16531
16581
|
if (is_func && fn.pinned()) return self;
|
@@ -16922,6 +16972,7 @@ def_optimize(AST_Call, function(self, compressor) {
|
|
16922
16972
|
if (can_inline && has_annotation(self, _INLINE)) {
|
16923
16973
|
set_flag(fn, SQUEEZED);
|
16924
16974
|
fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
|
16975
|
+
fn = fn.clone(true);
|
16925
16976
|
fn.figure_out_scope({}, {
|
16926
16977
|
parent_scope: find_scope(compressor),
|
16927
16978
|
toplevel: compressor.get_toplevel()
|
@@ -18709,14 +18760,11 @@ def_optimize(AST_Sub, function(self, compressor) {
|
|
18709
18760
|
ev = make_node_from_constant(ev, self).optimize(compressor);
|
18710
18761
|
return best_of(compressor, ev, self);
|
18711
18762
|
}
|
18712
|
-
if (self.optional && is_nullish(self.expression, compressor)) {
|
18713
|
-
return make_node(AST_Undefined, self);
|
18714
|
-
}
|
18715
18763
|
return self;
|
18716
18764
|
});
|
18717
18765
|
|
18718
18766
|
def_optimize(AST_Chain, function (self, compressor) {
|
18719
|
-
self.expression
|
18767
|
+
if (is_nullish(self.expression, compressor)) return make_node(AST_Undefined, self);
|
18720
18768
|
return self;
|
18721
18769
|
});
|
18722
18770
|
|
@@ -18783,9 +18831,6 @@ def_optimize(AST_Dot, function(self, compressor) {
|
|
18783
18831
|
ev = make_node_from_constant(ev, self).optimize(compressor);
|
18784
18832
|
return best_of(compressor, ev, self);
|
18785
18833
|
}
|
18786
|
-
if (self.optional && is_nullish(self.expression, compressor)) {
|
18787
|
-
return make_node(AST_Undefined, self);
|
18788
|
-
}
|
18789
18834
|
return self;
|
18790
18835
|
});
|
18791
18836
|
|
@@ -18845,9 +18890,11 @@ function inline_object_prop_spread(props, compressor) {
|
|
18845
18890
|
// non-iterable value silently does nothing; it is thus safe
|
18846
18891
|
// to remove. AST_String is the only iterable AST_Constant.
|
18847
18892
|
props.splice(i, 1);
|
18893
|
+
i--;
|
18848
18894
|
} else if (is_nullish(expr, compressor)) {
|
18849
18895
|
// Likewise, null and undefined can be silently removed.
|
18850
18896
|
props.splice(i, 1);
|
18897
|
+
i--;
|
18851
18898
|
}
|
18852
18899
|
}
|
18853
18900
|
}
|
@@ -27166,9 +27213,6 @@ function mangle_properties(ast, options) {
|
|
27166
27213
|
var cache;
|
27167
27214
|
if (options.cache) {
|
27168
27215
|
cache = options.cache.props;
|
27169
|
-
cache.forEach(function(mangled_name) {
|
27170
|
-
reserved.add(mangled_name);
|
27171
|
-
});
|
27172
27216
|
} else {
|
27173
27217
|
cache = new Map();
|
27174
27218
|
}
|
@@ -27186,6 +27230,9 @@ function mangle_properties(ast, options) {
|
|
27186
27230
|
|
27187
27231
|
var names_to_mangle = new Set();
|
27188
27232
|
var unmangleable = new Set();
|
27233
|
+
// Track each already-mangled name to prevent nth_identifier from generating
|
27234
|
+
// the same name.
|
27235
|
+
cache.forEach((mangled_name) => unmangleable.add(mangled_name));
|
27189
27236
|
|
27190
27237
|
var keep_quoted = !!options.keep_quoted;
|
27191
27238
|
|
@@ -27429,6 +27476,7 @@ async function minify(files, options) {
|
|
27429
27476
|
keep_classnames: false,
|
27430
27477
|
keep_fnames: false,
|
27431
27478
|
module: false,
|
27479
|
+
nth_identifier: base54,
|
27432
27480
|
properties: false,
|
27433
27481
|
reserved: [],
|
27434
27482
|
safari10: false,
|
@@ -73,13 +73,12 @@ import {
|
|
73
73
|
AST_TemplateString,
|
74
74
|
AST_This,
|
75
75
|
AST_Unary,
|
76
|
-
AST_Undefined,
|
77
76
|
} from "../ast.js";
|
78
77
|
import { make_node, return_null, return_this } from "../utils/index.js";
|
79
78
|
import { first_in_statement } from "../utils/first_in_statement.js";
|
80
79
|
|
81
80
|
import { pure_prop_access_globals } from "./native-objects.js";
|
82
|
-
import { lazy_op, unary_side_effects,
|
81
|
+
import { lazy_op, unary_side_effects, is_nullish_shortcircuited } from "./inference.js";
|
83
82
|
import { WRITE_ONLY, set_flag, clear_flag } from "./compressor-flags.js";
|
84
83
|
import { make_sequence, is_func_expr, is_iife_call } from "./common.js";
|
85
84
|
|
@@ -121,8 +120,8 @@ def_drop_side_effect_free(AST_Constant, return_null);
|
|
121
120
|
def_drop_side_effect_free(AST_This, return_null);
|
122
121
|
|
123
122
|
def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
|
124
|
-
if (
|
125
|
-
return
|
123
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
124
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
126
125
|
}
|
127
126
|
|
128
127
|
if (!this.is_callee_pure(compressor)) {
|
@@ -298,21 +297,19 @@ def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
|
|
298
297
|
});
|
299
298
|
|
300
299
|
def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
|
301
|
-
if (this
|
302
|
-
return
|
300
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
301
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
303
302
|
}
|
304
|
-
if (this.expression.may_throw_on_access(compressor))
|
305
|
-
return this;
|
303
|
+
if (this.expression.may_throw_on_access(compressor)) return this;
|
306
304
|
|
307
305
|
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
308
306
|
});
|
309
307
|
|
310
308
|
def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
|
311
|
-
if (this
|
312
|
-
return
|
309
|
+
if (is_nullish_shortcircuited(this, compressor)) {
|
310
|
+
return this.expression.drop_side_effect_free(compressor, first_in_statement);
|
313
311
|
}
|
314
|
-
if (this.expression.may_throw_on_access(compressor))
|
315
|
-
return this;
|
312
|
+
if (this.expression.may_throw_on_access(compressor)) return this;
|
316
313
|
|
317
314
|
var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
|
318
315
|
if (!expression)
|
package/lib/compress/evaluate.js
CHANGED
@@ -82,6 +82,9 @@ function def_eval(node, func) {
|
|
82
82
|
node.DEFMETHOD("_eval", func);
|
83
83
|
}
|
84
84
|
|
85
|
+
// Used to propagate a nullish short-circuit signal upwards through the chain.
|
86
|
+
export const nullish = Symbol("This AST_Chain is nullish");
|
87
|
+
|
85
88
|
// If the node has been successfully reduced to a constant,
|
86
89
|
// then its value is returned; otherwise the element itself
|
87
90
|
// is returned.
|
@@ -93,7 +96,7 @@ AST_Node.DEFMETHOD("evaluate", function (compressor) {
|
|
93
96
|
var val = this._eval(compressor, 1);
|
94
97
|
if (!val || val instanceof RegExp)
|
95
98
|
return val;
|
96
|
-
if (typeof val == "function" || typeof val == "object")
|
99
|
+
if (typeof val == "function" || typeof val == "object" || val == nullish)
|
97
100
|
return this;
|
98
101
|
return val;
|
99
102
|
});
|
@@ -338,11 +341,8 @@ const regexp_flags = new Set([
|
|
338
341
|
]);
|
339
342
|
|
340
343
|
def_eval(AST_PropAccess, function (compressor, depth) {
|
341
|
-
|
342
|
-
|
343
|
-
if (obj == null)
|
344
|
-
return undefined;
|
345
|
-
}
|
344
|
+
const obj = this.expression._eval(compressor, depth);
|
345
|
+
if (obj === nullish || (this.optional && obj == null)) return nullish;
|
346
346
|
if (compressor.option("unsafe")) {
|
347
347
|
var key = this.property;
|
348
348
|
if (key instanceof AST_Node) {
|
@@ -397,16 +397,19 @@ def_eval(AST_PropAccess, function (compressor, depth) {
|
|
397
397
|
|
398
398
|
def_eval(AST_Chain, function (compressor, depth) {
|
399
399
|
const evaluated = this.expression._eval(compressor, depth);
|
400
|
-
return evaluated ===
|
400
|
+
return evaluated === nullish
|
401
|
+
? undefined
|
402
|
+
: evaluated === this.expression
|
403
|
+
? this
|
404
|
+
: evaluated;
|
401
405
|
});
|
402
406
|
|
403
407
|
def_eval(AST_Call, function (compressor, depth) {
|
404
408
|
var exp = this.expression;
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
}
|
409
|
+
|
410
|
+
const callee = exp._eval(compressor, depth);
|
411
|
+
if (callee === nullish || (this.optional && callee == null)) return nullish;
|
412
|
+
|
410
413
|
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
411
414
|
var key = exp.property;
|
412
415
|
if (key instanceof AST_Node) {
|
package/lib/compress/index.js
CHANGED
@@ -1598,6 +1598,49 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
1598
1598
|
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
|
1599
1599
|
self.body = body;
|
1600
1600
|
|
1601
|
+
let default_or_exact = default_branch || exact_match;
|
1602
|
+
default_branch = null;
|
1603
|
+
exact_match = null;
|
1604
|
+
|
1605
|
+
// group equivalent branches so they will be located next to each other,
|
1606
|
+
// that way the next micro-optimization will merge them.
|
1607
|
+
// ** bail micro-optimization if not a simple switch case with breaks
|
1608
|
+
if (body.every((branch, i) =>
|
1609
|
+
(branch === default_or_exact || !branch.expression.has_side_effects(compressor))
|
1610
|
+
&& (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
|
1611
|
+
) {
|
1612
|
+
for (let i = 0; i < body.length; i++) {
|
1613
|
+
const branch = body[i];
|
1614
|
+
for (let j = i + 1; j < body.length; j++) {
|
1615
|
+
const next = body[j];
|
1616
|
+
if (next.body.length === 0) continue;
|
1617
|
+
const last_branch = j === (body.length - 1);
|
1618
|
+
const equivalentBranch = branches_equivalent(next, branch, false);
|
1619
|
+
if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
|
1620
|
+
if (!equivalentBranch && last_branch) {
|
1621
|
+
next.body.push(make_node(AST_Break));
|
1622
|
+
}
|
1623
|
+
|
1624
|
+
// let's find previous siblings with inert fallthrough...
|
1625
|
+
let x = j - 1;
|
1626
|
+
let fallthroughDepth = 0;
|
1627
|
+
while (x > i) {
|
1628
|
+
if (is_inert_body(body[x--])) {
|
1629
|
+
fallthroughDepth++;
|
1630
|
+
} else {
|
1631
|
+
break;
|
1632
|
+
}
|
1633
|
+
}
|
1634
|
+
|
1635
|
+
const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
|
1636
|
+
body.splice(i + 1, 0, ...plucked);
|
1637
|
+
i += plucked.length;
|
1638
|
+
}
|
1639
|
+
}
|
1640
|
+
}
|
1641
|
+
}
|
1642
|
+
|
1643
|
+
// merge equivalent branches in a row
|
1601
1644
|
for (let i = 0; i < body.length; i++) {
|
1602
1645
|
let branch = body[i];
|
1603
1646
|
if (branch.body.length === 0) continue;
|
@@ -1618,10 +1661,6 @@ def_optimize(AST_Switch, function(self, compressor) {
|
|
1618
1661
|
}
|
1619
1662
|
}
|
1620
1663
|
|
1621
|
-
let default_or_exact = default_branch || exact_match;
|
1622
|
-
default_branch = null;
|
1623
|
-
exact_match = null;
|
1624
|
-
|
1625
1664
|
// Prune any empty branches at the end of the switch statement.
|
1626
1665
|
{
|
1627
1666
|
let i = body.length - 1;
|
@@ -2012,10 +2051,6 @@ def_optimize(AST_Call, function(self, compressor) {
|
|
2012
2051
|
}
|
2013
2052
|
}
|
2014
2053
|
|
2015
|
-
if (self.optional && is_nullish(fn, compressor)) {
|
2016
|
-
return make_node(AST_Undefined, self);
|
2017
|
-
}
|
2018
|
-
|
2019
2054
|
var is_func = fn instanceof AST_Lambda;
|
2020
2055
|
|
2021
2056
|
if (is_func && fn.pinned()) return self;
|
@@ -2412,6 +2447,7 @@ def_optimize(AST_Call, function(self, compressor) {
|
|
2412
2447
|
if (can_inline && has_annotation(self, _INLINE)) {
|
2413
2448
|
set_flag(fn, SQUEEZED);
|
2414
2449
|
fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
|
2450
|
+
fn = fn.clone(true);
|
2415
2451
|
fn.figure_out_scope({}, {
|
2416
2452
|
parent_scope: find_scope(compressor),
|
2417
2453
|
toplevel: compressor.get_toplevel()
|
@@ -4199,14 +4235,11 @@ def_optimize(AST_Sub, function(self, compressor) {
|
|
4199
4235
|
ev = make_node_from_constant(ev, self).optimize(compressor);
|
4200
4236
|
return best_of(compressor, ev, self);
|
4201
4237
|
}
|
4202
|
-
if (self.optional && is_nullish(self.expression, compressor)) {
|
4203
|
-
return make_node(AST_Undefined, self);
|
4204
|
-
}
|
4205
4238
|
return self;
|
4206
4239
|
});
|
4207
4240
|
|
4208
4241
|
def_optimize(AST_Chain, function (self, compressor) {
|
4209
|
-
self.expression
|
4242
|
+
if (is_nullish(self.expression, compressor)) return make_node(AST_Undefined, self);
|
4210
4243
|
return self;
|
4211
4244
|
});
|
4212
4245
|
|
@@ -4273,9 +4306,6 @@ def_optimize(AST_Dot, function(self, compressor) {
|
|
4273
4306
|
ev = make_node_from_constant(ev, self).optimize(compressor);
|
4274
4307
|
return best_of(compressor, ev, self);
|
4275
4308
|
}
|
4276
|
-
if (self.optional && is_nullish(self.expression, compressor)) {
|
4277
|
-
return make_node(AST_Undefined, self);
|
4278
|
-
}
|
4279
4309
|
return self;
|
4280
4310
|
});
|
4281
4311
|
|
@@ -4335,9 +4365,11 @@ function inline_object_prop_spread(props, compressor) {
|
|
4335
4365
|
// non-iterable value silently does nothing; it is thus safe
|
4336
4366
|
// to remove. AST_String is the only iterable AST_Constant.
|
4337
4367
|
props.splice(i, 1);
|
4368
|
+
i--;
|
4338
4369
|
} else if (is_nullish(expr, compressor)) {
|
4339
4370
|
// Likewise, null and undefined can be silently removed.
|
4340
4371
|
props.splice(i, 1);
|
4372
|
+
i--;
|
4341
4373
|
}
|
4342
4374
|
}
|
4343
4375
|
}
|
@@ -226,9 +226,8 @@ export function is_undefined(node, compressor) {
|
|
226
226
|
);
|
227
227
|
}
|
228
228
|
|
229
|
-
//
|
230
|
-
|
231
|
-
export function is_nullish(node, compressor) {
|
229
|
+
// Is the node explicitly null or undefined.
|
230
|
+
function is_null_or_undefined(node, compressor) {
|
232
231
|
let fixed;
|
233
232
|
return (
|
234
233
|
node instanceof AST_Null
|
@@ -238,13 +237,29 @@ export function is_nullish(node, compressor) {
|
|
238
237
|
&& (fixed = node.definition().fixed) instanceof AST_Node
|
239
238
|
&& is_nullish(fixed, compressor)
|
240
239
|
)
|
241
|
-
// Recurse into those optional chains!
|
242
|
-
|| node instanceof AST_PropAccess && node.optional && is_nullish(node.expression, compressor)
|
243
|
-
|| node instanceof AST_Call && node.optional && is_nullish(node.expression, compressor)
|
244
|
-
|| node instanceof AST_Chain && is_nullish(node.expression, compressor)
|
245
240
|
);
|
246
241
|
}
|
247
242
|
|
243
|
+
// Find out if this expression is optionally chained from a base-point that we
|
244
|
+
// can statically analyze as null or undefined.
|
245
|
+
export function is_nullish_shortcircuited(node, compressor) {
|
246
|
+
if (node instanceof AST_PropAccess || node instanceof AST_Call) {
|
247
|
+
return (
|
248
|
+
(node.optional && is_null_or_undefined(node.expression, compressor))
|
249
|
+
|| is_nullish_shortcircuited(node.expression, compressor)
|
250
|
+
);
|
251
|
+
}
|
252
|
+
if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
|
253
|
+
return false;
|
254
|
+
}
|
255
|
+
|
256
|
+
// Find out if something is == null, or can short circuit into nullish.
|
257
|
+
// Used to optimize ?. and ??
|
258
|
+
export function is_nullish(node, compressor) {
|
259
|
+
if (is_null_or_undefined(node, compressor)) return true;
|
260
|
+
return is_nullish_shortcircuited(node, compressor);
|
261
|
+
}
|
262
|
+
|
248
263
|
// Determine if expression might cause side effects
|
249
264
|
// If there's a possibility that a node may change something when it's executed, this returns true
|
250
265
|
(function(def_has_side_effects) {
|
@@ -352,13 +367,12 @@ export function is_nullish(node, compressor) {
|
|
352
367
|
return any(this.elements, compressor);
|
353
368
|
});
|
354
369
|
def_has_side_effects(AST_Dot, function(compressor) {
|
370
|
+
if (is_nullish(this, compressor)) return false;
|
355
371
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
356
372
|
|| this.expression.has_side_effects(compressor);
|
357
373
|
});
|
358
374
|
def_has_side_effects(AST_Sub, function(compressor) {
|
359
|
-
if (
|
360
|
-
return false;
|
361
|
-
}
|
375
|
+
if (is_nullish(this, compressor)) return false;
|
362
376
|
|
363
377
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
364
378
|
|| this.expression.has_side_effects(compressor)
|
@@ -426,7 +440,7 @@ export function is_nullish(node, compressor) {
|
|
426
440
|
return any(this.body, compressor);
|
427
441
|
});
|
428
442
|
def_may_throw(AST_Call, function(compressor) {
|
429
|
-
if (
|
443
|
+
if (is_nullish(this, compressor)) return false;
|
430
444
|
if (any(this.args, compressor)) return true;
|
431
445
|
if (this.is_callee_pure(compressor)) return false;
|
432
446
|
if (this.expression.may_throw(compressor)) return true;
|
@@ -485,12 +499,12 @@ export function is_nullish(node, compressor) {
|
|
485
499
|
return this.body.may_throw(compressor);
|
486
500
|
});
|
487
501
|
def_may_throw(AST_Dot, function(compressor) {
|
502
|
+
if (is_nullish(this, compressor)) return false;
|
488
503
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
489
504
|
|| this.expression.may_throw(compressor);
|
490
505
|
});
|
491
506
|
def_may_throw(AST_Sub, function(compressor) {
|
492
|
-
if (
|
493
|
-
|
507
|
+
if (is_nullish(this, compressor)) return false;
|
494
508
|
return !this.optional && this.expression.may_throw_on_access(compressor)
|
495
509
|
|| this.expression.may_throw(compressor)
|
496
510
|
|| this.property.may_throw(compressor);
|
package/lib/minify.js
CHANGED
@@ -11,6 +11,7 @@ import { AST_Toplevel, AST_Node } from "./ast.js";
|
|
11
11
|
import { parse } from "./parse.js";
|
12
12
|
import { OutputStream } from "./output.js";
|
13
13
|
import { Compressor } from "./compress/index.js";
|
14
|
+
import { base54 } from "./scope.js";
|
14
15
|
import { SourceMap } from "./sourcemap.js";
|
15
16
|
import {
|
16
17
|
mangle_properties,
|
@@ -113,6 +114,7 @@ async function minify(files, options) {
|
|
113
114
|
keep_classnames: false,
|
114
115
|
keep_fnames: false,
|
115
116
|
module: false,
|
117
|
+
nth_identifier: base54,
|
116
118
|
properties: false,
|
117
119
|
reserved: [],
|
118
120
|
safari10: false,
|
package/lib/propmangle.js
CHANGED
@@ -197,9 +197,6 @@ function mangle_properties(ast, options) {
|
|
197
197
|
var cache;
|
198
198
|
if (options.cache) {
|
199
199
|
cache = options.cache.props;
|
200
|
-
cache.forEach(function(mangled_name) {
|
201
|
-
reserved.add(mangled_name);
|
202
|
-
});
|
203
200
|
} else {
|
204
201
|
cache = new Map();
|
205
202
|
}
|
@@ -217,6 +214,9 @@ function mangle_properties(ast, options) {
|
|
217
214
|
|
218
215
|
var names_to_mangle = new Set();
|
219
216
|
var unmangleable = new Set();
|
217
|
+
// Track each already-mangled name to prevent nth_identifier from generating
|
218
|
+
// the same name.
|
219
|
+
cache.forEach((mangled_name) => unmangleable.add(mangled_name));
|
220
220
|
|
221
221
|
var keep_quoted = !!options.keep_quoted;
|
222
222
|
|
package/package.json
CHANGED
package/bin/terser.mjs
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
#!/usr/bin/env node
|
2
|
-
|
3
|
-
"use strict";
|
4
|
-
|
5
|
-
import "../tools/exit.cjs";
|
6
|
-
|
7
|
-
import fs from "fs"
|
8
|
-
import path from "path"
|
9
|
-
import program from "commander"
|
10
|
-
|
11
|
-
import { run_cli } from "../lib/cli.js"
|
12
|
-
|
13
|
-
const packageJson = {
|
14
|
-
name: "terser",
|
15
|
-
version: "experimental module CLI"
|
16
|
-
}
|
17
|
-
|
18
|
-
run_cli({ program, packageJson, fs, path }).catch((error) => {
|
19
|
-
console.error(error);
|
20
|
-
process.exitCode = 1;
|
21
|
-
});
|