terser 5.21.0 → 5.23.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 CHANGED
@@ -1,6 +1,15 @@
1
1
  # Changelog
2
2
 
3
- ## v5.20.1
3
+ ## v5.23.0
4
+ - When top_retain will keep a variable assignment around, inline the assignee when it's shorter than the name (#1434)
5
+ - Remove empty class `static {}` blocks.
6
+
7
+ ## v5.22.0
8
+ - Do not `unsafe`ly shorten expressions like a?.toString() when they're conditional.
9
+ - Avoid running drop_unused in nodes that aren't scopes. Fixes a rare crash.
10
+ - When 'module' is enabled, assume strict mode when figuring out scopes.
11
+
12
+ ## v5.21.0
4
13
  - Do not inline functions that would be retained in the toplevel (as this would cause code duplication).
5
14
  - Fix precedence of arrow function and ternary operator when formatting output.
6
15
 
package/README.md CHANGED
@@ -154,7 +154,8 @@ a double dash to prevent input files being used as option arguments:
154
154
  --keep-fnames Do not mangle/drop function names. Useful for
155
155
  code relying on Function.prototype.name.
156
156
  --module Input is an ES6 module. If `compress` or `mangle` is
157
- enabled then the `toplevel` option will be enabled.
157
+ enabled then the `toplevel` option, as well as strict mode,
158
+ will be enabled.
158
159
  --name-cache <file> File to hold mangled name mappings.
159
160
  --safari10 Support non-standard Safari 10/11.
160
161
  Equivalent to setting `safari10: true` in `minify()`
@@ -912,7 +913,7 @@ If you happen to need the source map as a raw object, set `sourceMap.asObject` t
912
913
  [compress option](#compress-options).
913
914
 
914
915
  - `module` (default `false`) -- Pass `true` an ES6 modules, where the toplevel
915
- scope is not the global scope. Implies `toplevel`.
916
+ scope is not the global scope. Implies `toplevel` and assumes input code is strict mode JS.
916
917
 
917
918
  - `nth_identifier` (default: an internal mangler that weights based on character
918
919
  frequency analysis) -- Pass an object with a `get(n)` function that converts an
@@ -2,7 +2,7 @@
2
2
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@jridgewell/source-map')) :
3
3
  typeof define === 'function' && define.amd ? define(['exports', '@jridgewell/source-map'], factory) :
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Terser = {}, global.sourceMap));
5
- }(this, (function (exports, sourceMap) { 'use strict';
5
+ })(this, (function (exports, sourceMap) { 'use strict';
6
6
 
7
7
  /***********************************************************************
8
8
 
@@ -11573,6 +11573,7 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null,
11573
11573
  cache: null,
11574
11574
  ie8: false,
11575
11575
  safari10: false,
11576
+ module: false,
11576
11577
  });
11577
11578
 
11578
11579
  if (!(toplevel instanceof AST_Toplevel)) {
@@ -11740,6 +11741,11 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null,
11740
11741
  );
11741
11742
  }
11742
11743
  });
11744
+
11745
+ if (options.module) {
11746
+ tw.directives["use strict"] = true;
11747
+ }
11748
+
11743
11749
  this.walk(tw);
11744
11750
 
11745
11751
  function mark_export(def, level) {
@@ -12424,7 +12430,7 @@ const base54 = (() => {
12424
12430
 
12425
12431
  let mangle_options = undefined;
12426
12432
  AST_Node.prototype.size = function (compressor, stack) {
12427
- mangle_options = compressor && compressor.mangle_options;
12433
+ mangle_options = compressor && compressor._mangle_options;
12428
12434
 
12429
12435
  let size = 0;
12430
12436
  walk_parent(this, (node, info) => {
@@ -15077,6 +15083,8 @@ const r_keep_assign = /keep_assign/;
15077
15083
  AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
15078
15084
  if (!compressor.option("unused")) return;
15079
15085
  if (compressor.has_directive("use asm")) return;
15086
+ if (!this.variables) return; // not really a scope (eg: AST_Class)
15087
+
15080
15088
  var self = this;
15081
15089
  if (self.pinned()) return;
15082
15090
  var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
@@ -17688,18 +17696,43 @@ function scope_encloses_variables_in_this_scope(scope, pulled_scope) {
17688
17696
  return false;
17689
17697
  }
17690
17698
 
17699
+ /**
17700
+ * An extra check function for `top_retain` option, compare the length of const identifier
17701
+ * and init value length and return true if init value is longer than identifier. for example:
17702
+ * ```
17703
+ * // top_retain: ["example"]
17704
+ * const example = 100
17705
+ * ```
17706
+ * it will return false because length of "100" is short than identifier "example".
17707
+ */
17708
+ function is_const_symbol_short_than_init_value(def, fixed_value) {
17709
+ if (def.orig.length === 1 && fixed_value) {
17710
+ const init_value_length = fixed_value.size();
17711
+ const identifer_length = def.name.length;
17712
+ return init_value_length > identifer_length;
17713
+ }
17714
+ return true;
17715
+ }
17716
+
17691
17717
  function inline_into_symbolref(self, compressor) {
17692
17718
  const parent = compressor.parent();
17693
-
17694
17719
  const def = self.definition();
17695
17720
  const nearest_scope = compressor.find_scope();
17696
- if (compressor.top_retain && def.global && compressor.top_retain(def)) {
17721
+ let fixed = self.fixed_value();
17722
+ if (
17723
+ compressor.top_retain &&
17724
+ def.global &&
17725
+ compressor.top_retain(def) &&
17726
+ // when identifier is in top_retain option dose not mean we can always inline it.
17727
+ // if identifier name is longer then init value, we can replace it.
17728
+ is_const_symbol_short_than_init_value(def, fixed)
17729
+ ) {
17730
+ // keep it
17697
17731
  def.fixed = false;
17698
17732
  def.single_use = false;
17699
17733
  return self;
17700
17734
  }
17701
17735
 
17702
- let fixed = self.fixed_value();
17703
17736
  let single_use = def.single_use
17704
17737
  && !(parent instanceof AST_Call
17705
17738
  && (parent.is_callee_pure(compressor))
@@ -18406,11 +18439,17 @@ class Compressor extends TreeWalker {
18406
18439
  this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
18407
18440
  this.evaluated_regexps = new Map();
18408
18441
  this._toplevel = undefined;
18409
- this.mangle_options = mangle_options
18442
+ this._mangle_options = mangle_options
18410
18443
  ? format_mangler_options(mangle_options)
18411
18444
  : mangle_options;
18412
18445
  }
18413
18446
 
18447
+ mangle_options() {
18448
+ var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
18449
+ var module = this._mangle_options && this._mangle_options.module || this.option("module");
18450
+ return { ie8: this.option("ie8"), nth_identifier, module };
18451
+ }
18452
+
18414
18453
  option(key) {
18415
18454
  return this.options[key];
18416
18455
  }
@@ -18465,8 +18504,7 @@ class Compressor extends TreeWalker {
18465
18504
  var passes = +this.options.passes || 1;
18466
18505
  var min_count = 1 / 0;
18467
18506
  var stopping = false;
18468
- var nth_identifier = this.mangle_options && this.mangle_options.nth_identifier || base54;
18469
- var mangle = { ie8: this.option("ie8"), nth_identifier: nth_identifier };
18507
+ var mangle = this.mangle_options();
18470
18508
  for (var pass = 0; pass < passes; pass++) {
18471
18509
  this._toplevel.figure_out_scope(mangle);
18472
18510
  if (pass === 0 && this.option("drop_console")) {
@@ -19742,7 +19780,7 @@ def_optimize(AST_Call, function(self, compressor) {
19742
19780
  self.args.length = last;
19743
19781
  }
19744
19782
 
19745
- if (compressor.option("unsafe")) {
19783
+ if (compressor.option("unsafe") && !exp.contains_optional()) {
19746
19784
  if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
19747
19785
  const [argument] = self.args;
19748
19786
  if (argument instanceof AST_Array) {
@@ -19957,7 +19995,6 @@ def_optimize(AST_Call, function(self, compressor) {
19957
19995
  argnames: [],
19958
19996
  body: []
19959
19997
  }).optimize(compressor);
19960
- var nth_identifier = compressor.mangle_options && compressor.mangle_options.nth_identifier || base54;
19961
19998
  if (self.args.every((x) => x instanceof AST_String)) {
19962
19999
  // quite a corner-case, but we can handle it:
19963
20000
  // https://github.com/mishoo/UglifyJS2/issues/203
@@ -19967,10 +20004,10 @@ def_optimize(AST_Call, function(self, compressor) {
19967
20004
  return arg.value;
19968
20005
  }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
19969
20006
  var ast = parse(code);
19970
- var mangle = { ie8: compressor.option("ie8"), nth_identifier: nth_identifier };
20007
+ var mangle = compressor.mangle_options();
19971
20008
  ast.figure_out_scope(mangle);
19972
20009
  var comp = new Compressor(compressor.options, {
19973
- mangle_options: compressor.mangle_options
20010
+ mangle_options: compressor._mangle_options
19974
20011
  });
19975
20012
  ast = ast.transform(comp);
19976
20013
  ast.figure_out_scope(mangle);
@@ -20009,6 +20046,23 @@ def_optimize(AST_Call, function(self, compressor) {
20009
20046
  return inline_into_call(self, compressor);
20010
20047
  });
20011
20048
 
20049
+ /** Does this node contain optional property access or optional call? */
20050
+ AST_Node.DEFMETHOD("contains_optional", function() {
20051
+ if (
20052
+ this instanceof AST_PropAccess
20053
+ || this instanceof AST_Call
20054
+ || this instanceof AST_Chain
20055
+ ) {
20056
+ if (this.optional) {
20057
+ return true;
20058
+ } else {
20059
+ return this.expression.contains_optional();
20060
+ }
20061
+ } else {
20062
+ return false;
20063
+ }
20064
+ });
20065
+
20012
20066
  def_optimize(AST_New, function(self, compressor) {
20013
20067
  if (
20014
20068
  compressor.option("unsafe") &&
@@ -21614,8 +21668,14 @@ def_optimize(AST_Function, function(self, compressor) {
21614
21668
  });
21615
21669
 
21616
21670
  def_optimize(AST_Class, function(self) {
21617
- // HACK to avoid compress failure.
21618
- // AST_Class is not really an AST_Scope/AST_Block as it lacks a body.
21671
+ for (let i = 0; i < self.properties.length; i++) {
21672
+ const prop = self.properties[i];
21673
+ if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
21674
+ self.properties.splice(i, 1);
21675
+ i--;
21676
+ }
21677
+ }
21678
+
21619
21679
  return self;
21620
21680
  });
21621
21681
 
@@ -31235,4 +31295,4 @@ exports._default_options = _default_options;
31235
31295
  exports._run_cli = run_cli;
31236
31296
  exports.minify = minify;
31237
31297
 
31238
- })));
31298
+ }));
@@ -109,6 +109,8 @@ const r_keep_assign = /keep_assign/;
109
109
  AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
110
110
  if (!compressor.option("unused")) return;
111
111
  if (compressor.has_directive("use asm")) return;
112
+ if (!this.variables) return; // not really a scope (eg: AST_Class)
113
+
112
114
  var self = this;
113
115
  if (self.pinned()) return;
114
116
  var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
@@ -320,11 +320,17 @@ class Compressor extends TreeWalker {
320
320
  this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
321
321
  this.evaluated_regexps = new Map();
322
322
  this._toplevel = undefined;
323
- this.mangle_options = mangle_options
323
+ this._mangle_options = mangle_options
324
324
  ? format_mangler_options(mangle_options)
325
325
  : mangle_options;
326
326
  }
327
327
 
328
+ mangle_options() {
329
+ var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
330
+ var module = this._mangle_options && this._mangle_options.module || this.option("module");
331
+ return { ie8: this.option("ie8"), nth_identifier, module };
332
+ }
333
+
328
334
  option(key) {
329
335
  return this.options[key];
330
336
  }
@@ -379,8 +385,7 @@ class Compressor extends TreeWalker {
379
385
  var passes = +this.options.passes || 1;
380
386
  var min_count = 1 / 0;
381
387
  var stopping = false;
382
- var nth_identifier = this.mangle_options && this.mangle_options.nth_identifier || base54;
383
- var mangle = { ie8: this.option("ie8"), nth_identifier: nth_identifier };
388
+ var mangle = this.mangle_options();
384
389
  for (var pass = 0; pass < passes; pass++) {
385
390
  this._toplevel.figure_out_scope(mangle);
386
391
  if (pass === 0 && this.option("drop_console")) {
@@ -1656,7 +1661,7 @@ def_optimize(AST_Call, function(self, compressor) {
1656
1661
  self.args.length = last;
1657
1662
  }
1658
1663
 
1659
- if (compressor.option("unsafe")) {
1664
+ if (compressor.option("unsafe") && !exp.contains_optional()) {
1660
1665
  if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
1661
1666
  const [argument] = self.args;
1662
1667
  if (argument instanceof AST_Array) {
@@ -1871,7 +1876,6 @@ def_optimize(AST_Call, function(self, compressor) {
1871
1876
  argnames: [],
1872
1877
  body: []
1873
1878
  }).optimize(compressor);
1874
- var nth_identifier = compressor.mangle_options && compressor.mangle_options.nth_identifier || base54;
1875
1879
  if (self.args.every((x) => x instanceof AST_String)) {
1876
1880
  // quite a corner-case, but we can handle it:
1877
1881
  // https://github.com/mishoo/UglifyJS2/issues/203
@@ -1881,10 +1885,10 @@ def_optimize(AST_Call, function(self, compressor) {
1881
1885
  return arg.value;
1882
1886
  }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
1883
1887
  var ast = parse(code);
1884
- var mangle = { ie8: compressor.option("ie8"), nth_identifier: nth_identifier };
1888
+ var mangle = compressor.mangle_options();
1885
1889
  ast.figure_out_scope(mangle);
1886
1890
  var comp = new Compressor(compressor.options, {
1887
- mangle_options: compressor.mangle_options
1891
+ mangle_options: compressor._mangle_options
1888
1892
  });
1889
1893
  ast = ast.transform(comp);
1890
1894
  ast.figure_out_scope(mangle);
@@ -1923,6 +1927,23 @@ def_optimize(AST_Call, function(self, compressor) {
1923
1927
  return inline_into_call(self, compressor);
1924
1928
  });
1925
1929
 
1930
+ /** Does this node contain optional property access or optional call? */
1931
+ AST_Node.DEFMETHOD("contains_optional", function() {
1932
+ if (
1933
+ this instanceof AST_PropAccess
1934
+ || this instanceof AST_Call
1935
+ || this instanceof AST_Chain
1936
+ ) {
1937
+ if (this.optional) {
1938
+ return true;
1939
+ } else {
1940
+ return this.expression.contains_optional();
1941
+ }
1942
+ } else {
1943
+ return false;
1944
+ }
1945
+ });
1946
+
1926
1947
  def_optimize(AST_New, function(self, compressor) {
1927
1948
  if (
1928
1949
  compressor.option("unsafe") &&
@@ -3528,8 +3549,14 @@ def_optimize(AST_Function, function(self, compressor) {
3528
3549
  });
3529
3550
 
3530
3551
  def_optimize(AST_Class, function(self) {
3531
- // HACK to avoid compress failure.
3532
- // AST_Class is not really an AST_Scope/AST_Block as it lacks a body.
3552
+ for (let i = 0; i < self.properties.length; i++) {
3553
+ const prop = self.properties[i];
3554
+ if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
3555
+ self.properties.splice(i, 1);
3556
+ i--;
3557
+ }
3558
+ }
3559
+
3533
3560
  return self;
3534
3561
  });
3535
3562
 
@@ -84,7 +84,7 @@ import {
84
84
 
85
85
  _INLINE,
86
86
  _NOINLINE,
87
- _PURE
87
+ _PURE,
88
88
  } from "../ast.js";
89
89
  import { make_node, has_annotation } from "../utils/index.js";
90
90
  import "../size.js";
@@ -148,18 +148,43 @@ function scope_encloses_variables_in_this_scope(scope, pulled_scope) {
148
148
  return false;
149
149
  }
150
150
 
151
+ /**
152
+ * An extra check function for `top_retain` option, compare the length of const identifier
153
+ * and init value length and return true if init value is longer than identifier. for example:
154
+ * ```
155
+ * // top_retain: ["example"]
156
+ * const example = 100
157
+ * ```
158
+ * it will return false because length of "100" is short than identifier "example".
159
+ */
160
+ function is_const_symbol_short_than_init_value(def, fixed_value) {
161
+ if (def.orig.length === 1 && fixed_value) {
162
+ const init_value_length = fixed_value.size();
163
+ const identifer_length = def.name.length;
164
+ return init_value_length > identifer_length;
165
+ }
166
+ return true;
167
+ }
168
+
151
169
  export function inline_into_symbolref(self, compressor) {
152
170
  const parent = compressor.parent();
153
-
154
171
  const def = self.definition();
155
172
  const nearest_scope = compressor.find_scope();
156
- if (compressor.top_retain && def.global && compressor.top_retain(def)) {
173
+ let fixed = self.fixed_value();
174
+ if (
175
+ compressor.top_retain &&
176
+ def.global &&
177
+ compressor.top_retain(def) &&
178
+ // when identifier is in top_retain option dose not mean we can always inline it.
179
+ // if identifier name is longer then init value, we can replace it.
180
+ is_const_symbol_short_than_init_value(def, fixed)
181
+ ) {
182
+ // keep it
157
183
  def.fixed = false;
158
184
  def.single_use = false;
159
185
  return self;
160
186
  }
161
187
 
162
- let fixed = self.fixed_value();
163
188
  let single_use = def.single_use
164
189
  && !(parent instanceof AST_Call
165
190
  && (parent.is_callee_pure(compressor))
package/lib/scope.js CHANGED
@@ -205,6 +205,7 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null,
205
205
  cache: null,
206
206
  ie8: false,
207
207
  safari10: false,
208
+ module: false,
208
209
  });
209
210
 
210
211
  if (!(toplevel instanceof AST_Toplevel)) {
@@ -372,6 +373,11 @@ AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = null,
372
373
  );
373
374
  }
374
375
  });
376
+
377
+ if (options.module) {
378
+ tw.directives["use strict"] = true;
379
+ }
380
+
375
381
  this.walk(tw);
376
382
 
377
383
  function mark_export(def, level) {
package/lib/size.js CHANGED
@@ -89,7 +89,7 @@ import { first_in_statement } from "./utils/first_in_statement.js";
89
89
 
90
90
  let mangle_options = undefined;
91
91
  AST_Node.prototype.size = function (compressor, stack) {
92
- mangle_options = compressor && compressor.mangle_options;
92
+ mangle_options = compressor && compressor._mangle_options;
93
93
 
94
94
  let size = 0;
95
95
  walk_parent(this, (node, info) => {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "homepage": "https://terser.org",
5
5
  "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
6
6
  "license": "BSD-2-Clause",
7
- "version": "5.21.0",
7
+ "version": "5.23.0",
8
8
  "engines": {
9
9
  "node": ">=10"
10
10
  },