terser 5.16.1 → 5.17.7

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.
@@ -71,11 +71,13 @@ import {
71
71
  AST_Number,
72
72
  AST_ObjectKeyVal,
73
73
  AST_PropAccess,
74
+ AST_Scope,
74
75
  AST_Sequence,
75
76
  AST_SimpleStatement,
76
77
  AST_Symbol,
77
78
  AST_SymbolCatch,
78
79
  AST_SymbolConst,
80
+ AST_SymbolDeclaration,
79
81
  AST_SymbolDefun,
80
82
  AST_SymbolFunarg,
81
83
  AST_SymbolLambda,
@@ -91,6 +93,8 @@ import {
91
93
  AST_Yield,
92
94
 
93
95
  walk,
96
+ walk_parent,
97
+ walk_abort,
94
98
  walk_body,
95
99
 
96
100
  _INLINE,
@@ -99,19 +103,21 @@ import {
99
103
  } from "../ast.js";
100
104
  import { HOP, make_node, noop } from "../utils/index.js";
101
105
 
102
- import { lazy_op, is_modified } from "./inference.js";
106
+ import { lazy_op, is_modified, is_lhs } from "./inference.js";
103
107
  import { INLINED, clear_flag } from "./compressor-flags.js";
104
108
  import { read_property, has_break_or_continue, is_recursive_ref } from "./common.js";
105
109
 
106
- // Define the method AST_Node#reduce_vars, which goes through the AST in
107
- // execution order to perform basic flow analysis
108
-
110
+ /**
111
+ * Define the method AST_Node#reduce_vars, which goes through the AST in
112
+ * execution order to perform basic flow analysis
113
+ */
109
114
  function def_reduce_vars(node, func) {
110
115
  node.DEFMETHOD("reduce_vars", func);
111
116
  }
112
117
 
113
118
  def_reduce_vars(AST_Node, noop);
114
119
 
120
+ /** Clear definition properties */
115
121
  function reset_def(compressor, def) {
116
122
  def.assignments = 0;
117
123
  def.chained = false;
@@ -120,7 +126,10 @@ function reset_def(compressor, def) {
120
126
  def.recursive_refs = 0;
121
127
  def.references = [];
122
128
  def.single_use = undefined;
123
- if (def.scope.pinned()) {
129
+ if (
130
+ def.scope.pinned()
131
+ || (def.orig[0] instanceof AST_SymbolFunarg && def.scope.uses_arguments)
132
+ ) {
124
133
  def.fixed = false;
125
134
  } else if (def.orig[0] instanceof AST_SymbolConst || !compressor.exposed(def)) {
126
135
  def.fixed = def.init;
@@ -444,13 +453,11 @@ function mark_lambda(tw, descend, compressor) {
444
453
  clear_flag(this, INLINED);
445
454
  push(tw);
446
455
  reset_variables(tw, compressor, this);
447
- if (this.uses_arguments) {
448
- descend();
449
- pop(tw);
450
- return;
451
- }
456
+
452
457
  var iife;
453
458
  if (!this.name
459
+ && !this.uses_arguments
460
+ && !this.pinned()
454
461
  && (iife = tw.parent()) instanceof AST_Call
455
462
  && iife.expression === this
456
463
  && !iife.args.some(arg => arg instanceof AST_Expansion)
@@ -475,11 +482,101 @@ function mark_lambda(tw, descend, compressor) {
475
482
  }
476
483
  });
477
484
  }
485
+
478
486
  descend();
479
487
  pop(tw);
488
+
489
+ handle_defined_after_hoist(this);
490
+
480
491
  return true;
481
492
  }
482
493
 
494
+ /**
495
+ * It's possible for a hoisted function to use something that's not defined yet. Example:
496
+ *
497
+ * hoisted();
498
+ * var defined_after = true;
499
+ * function hoisted() {
500
+ * // use defined_after
501
+ * }
502
+ *
503
+ * This function is called on the parent to handle this issue.
504
+ */
505
+ function handle_defined_after_hoist(parent) {
506
+ const defuns = [];
507
+ walk(parent, node => {
508
+ if (node === parent) return;
509
+ if (node instanceof AST_Defun) defuns.push(node);
510
+ if (
511
+ node instanceof AST_Scope
512
+ || node instanceof AST_SimpleStatement
513
+ ) return true;
514
+ });
515
+
516
+ for (const defun of defuns) {
517
+ const fname_def = defun.name.definition();
518
+ const found_self_ref_in_other_defuns = defuns.some(
519
+ d => d !== defun && d.enclosed.indexOf(fname_def) !== -1
520
+ );
521
+
522
+ for (const def of defun.enclosed) {
523
+ if (
524
+ def.fixed === false
525
+ || def === fname_def
526
+ || def.scope.get_defun_scope() !== parent
527
+ ) {
528
+ continue;
529
+ }
530
+
531
+ // defun is hoisted, so always safe
532
+ if (
533
+ def.assignments === 0
534
+ && def.orig.length === 1
535
+ && def.orig[0] instanceof AST_SymbolDefun
536
+ ) {
537
+ continue;
538
+ }
539
+
540
+ if (found_self_ref_in_other_defuns) {
541
+ def.fixed = false;
542
+ continue;
543
+ }
544
+
545
+ // Detect `call_defun(); var used_in_defun = X`
546
+ // Because `used_in_defun` is not certainly X when it's defined after.
547
+ let found_defun_ref = false;
548
+ let found_def_after_defun = false;
549
+ walk_parent(parent, (node, info) => {
550
+ if (node === defun) return true;
551
+
552
+ // Step 1: find `call_defun()` or other refs to the defun
553
+ if (
554
+ !found_defun_ref
555
+ && node.thedef === fname_def
556
+ && node instanceof AST_Symbol
557
+ ) {
558
+ found_defun_ref = true;
559
+ }
560
+
561
+ // Step 2: if Step 1 occurred, find a var the defun uses
562
+ if (
563
+ found_defun_ref
564
+ && node.thedef === def
565
+ && (node instanceof AST_SymbolDeclaration
566
+ || is_lhs(node, info))
567
+ ) {
568
+ found_def_after_defun = true;
569
+ return walk_abort;
570
+ }
571
+ });
572
+
573
+ if (found_def_after_defun) {
574
+ def.fixed = false;
575
+ }
576
+ }
577
+ }
578
+ }
579
+
483
580
  def_reduce_vars(AST_Lambda, mark_lambda);
484
581
 
485
582
  def_reduce_vars(AST_Do, function(tw, descend, compressor) {
@@ -600,12 +697,15 @@ def_reduce_vars(AST_Toplevel, function(tw, descend, compressor) {
600
697
  reset_def(compressor, def);
601
698
  });
602
699
  reset_variables(tw, compressor, this);
700
+ descend();
701
+ handle_defined_after_hoist(this);
702
+ return true;
603
703
  });
604
704
 
605
705
  def_reduce_vars(AST_Try, function(tw, descend, compressor) {
606
706
  reset_block_variables(compressor, this);
607
707
  push(tw);
608
- walk_body(this, tw);
708
+ this.body.walk(tw);
609
709
  pop(tw);
610
710
  if (this.bcatch) {
611
711
  push(tw);
@@ -52,7 +52,6 @@ import {
52
52
  AST_Break,
53
53
  AST_Call,
54
54
  AST_Case,
55
- AST_Catch,
56
55
  AST_Chain,
57
56
  AST_Class,
58
57
  AST_Conditional,
@@ -71,7 +70,6 @@ import {
71
70
  AST_Exit,
72
71
  AST_Expansion,
73
72
  AST_Export,
74
- AST_Finally,
75
73
  AST_For,
76
74
  AST_ForIn,
77
75
  AST_If,
@@ -103,6 +101,7 @@ import {
103
101
  AST_SymbolVar,
104
102
  AST_This,
105
103
  AST_Try,
104
+ AST_TryBlock,
106
105
  AST_Unary,
107
106
  AST_UnaryPostfix,
108
107
  AST_UnaryPrefix,
@@ -171,12 +170,31 @@ function is_lhs_read_only(lhs) {
171
170
  return false;
172
171
  }
173
172
 
174
- // Remove code which we know is unreachable.
173
+ /** var a = 1 --> var a*/
174
+ function remove_initializers(var_statement) {
175
+ var decls = [];
176
+ var_statement.definitions.forEach(function(def) {
177
+ if (def.name instanceof AST_SymbolDeclaration) {
178
+ def.value = null;
179
+ decls.push(def);
180
+ } else {
181
+ def.declarations_as_names().forEach(name => {
182
+ decls.push(make_node(AST_VarDef, def, {
183
+ name,
184
+ value: null
185
+ }));
186
+ });
187
+ }
188
+ });
189
+ return decls.length ? make_node(AST_Var, var_statement, { definitions: decls }) : null;
190
+ }
191
+
192
+ /** Called on code which we know is unreachable, to keep elements that affect outside of it. */
175
193
  export function trim_unreachable_code(compressor, stat, target) {
176
194
  walk(stat, node => {
177
195
  if (node instanceof AST_Var) {
178
- node.remove_initializers();
179
- target.push(node);
196
+ const no_initializers = remove_initializers(node);
197
+ if (no_initializers) target.push(no_initializers);
180
198
  return true;
181
199
  }
182
200
  if (
@@ -234,13 +252,11 @@ export function tighten_body(statements, compressor) {
234
252
  function find_loop_scope_try() {
235
253
  var node = compressor.self(), level = 0, in_loop = false, in_try = false;
236
254
  do {
237
- if (node instanceof AST_Catch || node instanceof AST_Finally) {
238
- level++;
239
- } else if (node instanceof AST_IterationStatement) {
255
+ if (node instanceof AST_IterationStatement) {
240
256
  in_loop = true;
241
257
  } else if (node instanceof AST_Scope) {
242
258
  break;
243
- } else if (node instanceof AST_Try) {
259
+ } else if (node instanceof AST_TryBlock) {
244
260
  in_try = true;
245
261
  }
246
262
  } while (node = compressor.parent(level++));
@@ -284,6 +300,9 @@ export function tighten_body(statements, compressor) {
284
300
  && (node.logical || node.operator != "=" && lhs.equivalent_to(node.left))
285
301
  || node instanceof AST_Await
286
302
  || node instanceof AST_Call && lhs instanceof AST_PropAccess && lhs.equivalent_to(node.expression)
303
+ ||
304
+ (node instanceof AST_Call || node instanceof AST_PropAccess)
305
+ && node.optional
287
306
  || node instanceof AST_Debugger
288
307
  || node instanceof AST_Destructuring
289
308
  || node instanceof AST_Expansion
@@ -457,7 +476,11 @@ export function tighten_body(statements, compressor) {
457
476
  var hit = funarg;
458
477
  var abort = false, replaced = 0, can_replace = !args || !hit;
459
478
  if (!can_replace) {
460
- for (var j = compressor.self().argnames.lastIndexOf(candidate.name) + 1; !abort && j < args.length; j++) {
479
+ for (
480
+ let j = compressor.self().argnames.lastIndexOf(candidate.name) + 1;
481
+ !abort && j < args.length;
482
+ j++
483
+ ) {
461
484
  args[j].transform(scanner);
462
485
  }
463
486
  can_replace = true;
@@ -979,27 +1002,34 @@ export function tighten_body(statements, compressor) {
979
1002
  }
980
1003
 
981
1004
  if (stat instanceof AST_If) {
982
- var ab = aborts(stat.body);
983
- if (can_merge_flow(ab)) {
1005
+ let ab, new_else;
1006
+
1007
+ ab = aborts(stat.body);
1008
+ if (
1009
+ can_merge_flow(ab)
1010
+ && (new_else = as_statement_array_with_return(stat.body, ab))
1011
+ ) {
984
1012
  if (ab.label) {
985
1013
  remove(ab.label.thedef.references, ab);
986
1014
  }
987
1015
  CHANGED = true;
988
1016
  stat = stat.clone();
989
1017
  stat.condition = stat.condition.negate(compressor);
990
- var body = as_statement_array_with_return(stat.body, ab);
991
1018
  stat.body = make_node(AST_BlockStatement, stat, {
992
1019
  body: as_statement_array(stat.alternative).concat(extract_functions())
993
1020
  });
994
1021
  stat.alternative = make_node(AST_BlockStatement, stat, {
995
- body: body
1022
+ body: new_else
996
1023
  });
997
1024
  statements[i] = stat.transform(compressor);
998
1025
  continue;
999
1026
  }
1000
1027
 
1001
- var ab = aborts(stat.alternative);
1002
- if (can_merge_flow(ab)) {
1028
+ ab = aborts(stat.alternative);
1029
+ if (
1030
+ can_merge_flow(ab)
1031
+ && (new_else = as_statement_array_with_return(stat.alternative, ab))
1032
+ ) {
1003
1033
  if (ab.label) {
1004
1034
  remove(ab.label.thedef.references, ab);
1005
1035
  }
@@ -1008,9 +1038,8 @@ export function tighten_body(statements, compressor) {
1008
1038
  stat.body = make_node(AST_BlockStatement, stat.body, {
1009
1039
  body: as_statement_array(stat.body).concat(extract_functions())
1010
1040
  });
1011
- var body = as_statement_array_with_return(stat.alternative, ab);
1012
1041
  stat.alternative = make_node(AST_BlockStatement, stat.alternative, {
1013
- body: body
1042
+ body: new_else
1014
1043
  });
1015
1044
  statements[i] = stat.transform(compressor);
1016
1045
  continue;
@@ -1125,7 +1154,11 @@ export function tighten_body(statements, compressor) {
1125
1154
  }
1126
1155
 
1127
1156
  function as_statement_array_with_return(node, ab) {
1128
- var body = as_statement_array(node).slice(0, -1);
1157
+ var body = as_statement_array(node);
1158
+ if (ab !== body[body.length - 1]) {
1159
+ return undefined;
1160
+ }
1161
+ body = body.slice(0, -1);
1129
1162
  if (ab.value) {
1130
1163
  body.push(make_node(AST_SimpleStatement, ab.value, {
1131
1164
  body: ab.value.expression
@@ -1404,14 +1437,21 @@ export function tighten_body(statements, compressor) {
1404
1437
  CHANGED = true;
1405
1438
  stat.init = exprs.length ? make_sequence(stat.init, exprs) : null;
1406
1439
  statements[++j] = stat;
1407
- } else if (prev instanceof AST_Var && (!stat.init || stat.init.TYPE == prev.TYPE)) {
1440
+ } else if (
1441
+ prev instanceof AST_Var
1442
+ && (!stat.init || stat.init.TYPE == prev.TYPE)
1443
+ ) {
1408
1444
  if (stat.init) {
1409
1445
  prev.definitions = prev.definitions.concat(stat.init.definitions);
1410
1446
  }
1411
1447
  stat.init = prev;
1412
1448
  statements[j] = stat;
1413
1449
  CHANGED = true;
1414
- } else if (defs && stat.init && defs.TYPE == stat.init.TYPE && declarations_only(stat.init)) {
1450
+ } else if (
1451
+ defs instanceof AST_Var
1452
+ && stat.init instanceof AST_Var
1453
+ && declarations_only(stat.init)
1454
+ ) {
1415
1455
  defs.definitions = defs.definitions.concat(stat.init.definitions);
1416
1456
  stat.init = null;
1417
1457
  statements[++j] = stat;
@@ -172,7 +172,7 @@ AST_Switch.prototype.shallow_cmp = pass_through;
172
172
  AST_SwitchBranch.prototype.shallow_cmp = pass_through;
173
173
 
174
174
  AST_Try.prototype.shallow_cmp = function(other) {
175
- return (this.bcatch == null ? other.bcatch == null : this.bcatch === other.bcatch) && (this.bfinally == null ? other.bfinally == null : this.bfinally === other.bfinally);
175
+ return (this.body === other.body) && (this.bcatch == null ? other.bcatch == null : this.bcatch === other.bcatch) && (this.bfinally == null ? other.bfinally == null : this.bfinally === other.bfinally);
176
176
  };
177
177
 
178
178
  AST_Catch.prototype.shallow_cmp = function(other) {
package/lib/minify.js CHANGED
@@ -19,12 +19,15 @@ import {
19
19
  reserve_quoted_keys,
20
20
  } from "./propmangle.js";
21
21
 
22
- var to_ascii = typeof atob == "undefined" ? function(b64) {
23
- return Buffer.from(b64, "base64").toString();
24
- } : atob;
25
- var to_base64 = typeof btoa == "undefined" ? function(str) {
26
- return Buffer.from(str).toString("base64");
27
- } : btoa;
22
+ // to/from base64 functions
23
+ // Prefer built-in Buffer, if available, then use hack
24
+ // https://developer.mozilla.org/en-US/docs/Glossary/Base64#The_Unicode_Problem
25
+ var to_ascii = typeof Buffer !== "undefined"
26
+ ? (b64) => Buffer.from(b64, "base64").toString()
27
+ : (b64) => decodeURIComponent(escape(atob(b64)));
28
+ var to_base64 = typeof Buffer !== "undefined"
29
+ ? (str) => Buffer.from(str).toString("base64")
30
+ : (str) => btoa(unescape(encodeURIComponent(str)));
28
31
 
29
32
  function read_source_map(code) {
30
33
  var match = /(?:^|[^.])\/\/# sourceMappingURL=data:application\/json(;[\w=-]*)?;base64,([+/0-9A-Za-z]*=*)\s*$/.exec(code);
@@ -277,10 +280,13 @@ async function minify(files, options, _fs_module) {
277
280
  if (options.format.spidermonkey) {
278
281
  result.ast = toplevel.to_mozilla_ast();
279
282
  }
283
+ let format_options;
280
284
  if (!HOP(options.format, "code") || options.format.code) {
281
- if (!options.format.ast) {
285
+ // Make a shallow copy so that we can modify without mutating the user's input.
286
+ format_options = {...options.format};
287
+ if (!format_options.ast) {
282
288
  // Destroy stuff to save RAM. (unless the deprecated `ast` option is on)
283
- options.format._destroy_ast = true;
289
+ format_options._destroy_ast = true;
284
290
 
285
291
  walk(toplevel, node => {
286
292
  if (node instanceof AST_Scope) {
@@ -300,17 +306,17 @@ async function minify(files, options, _fs_module) {
300
306
  if (options.sourceMap.includeSources && files instanceof AST_Toplevel) {
301
307
  throw new Error("original source content unavailable");
302
308
  }
303
- options.format.source_map = await SourceMap({
309
+ format_options.source_map = await SourceMap({
304
310
  file: options.sourceMap.filename,
305
311
  orig: options.sourceMap.content,
306
312
  root: options.sourceMap.root,
307
313
  files: options.sourceMap.includeSources ? files : null,
308
314
  });
309
315
  }
310
- delete options.format.ast;
311
- delete options.format.code;
312
- delete options.format.spidermonkey;
313
- var stream = OutputStream(options.format);
316
+ delete format_options.ast;
317
+ delete format_options.code;
318
+ delete format_options.spidermonkey;
319
+ var stream = OutputStream(format_options);
314
320
  toplevel.print(stream);
315
321
  result.code = stream.get();
316
322
  if (options.sourceMap) {
@@ -318,7 +324,7 @@ async function minify(files, options, _fs_module) {
318
324
  configurable: true,
319
325
  enumerable: true,
320
326
  get() {
321
- const map = options.format.source_map.getEncoded();
327
+ const map = format_options.source_map.getEncoded();
322
328
  return (result.map = options.sourceMap.asObject ? map : JSON.stringify(map));
323
329
  },
324
330
  set(value) {
@@ -328,7 +334,7 @@ async function minify(files, options, _fs_module) {
328
334
  });
329
335
  }
330
336
  });
331
- result.decoded_map = options.format.source_map.getDecoded();
337
+ result.decoded_map = format_options.source_map.getDecoded();
332
338
  if (options.sourceMap.url == "inline") {
333
339
  var sourceMap = typeof result.map === "object" ? JSON.stringify(result.map) : result.map;
334
340
  result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(sourceMap);
@@ -343,8 +349,8 @@ async function minify(files, options, _fs_module) {
343
349
  options.nameCache.props = cache_to_json(options.mangle.properties.cache);
344
350
  }
345
351
  }
346
- if (options.format && options.format.source_map) {
347
- options.format.source_map.destroy();
352
+ if (format_options && format_options.source_map) {
353
+ format_options.source_map.destroy();
348
354
  }
349
355
  if (timings) {
350
356
  timings.end = Date.now();