Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/respect-ecma-iife-temp-vars.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
swc: patch
swc_core: patch
swc_ecma_minifier: patch
---

fix(es/minifier): respect ecma when emitting IIFE temp vars
Original file line number Diff line number Diff line change
@@ -1 +1 @@
let a;v=(a=r,b=>a.map(t=>{if(t)return t.foo}));
var a;v=(a=r,b=>a.map(t=>{if(t)return t.foo}));
8 changes: 4 additions & 4 deletions crates/swc/tests/fixture/issues-8xxx/8246/output/input.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
function withLog(t) {
let e = {};
for(let o in t){
let n;
e[o] = (n = o, function() {
return console.log(n + ' invoked'), t[n].apply(this, arguments);
for(let n in t){
var o;
e[n] = (o = n, function() {
return console.log(o + ' invoked'), t[o].apply(this, arguments);
});
}
return e;
Expand Down
6 changes: 5 additions & 1 deletion crates/swc_ecma_minifier/src/compress/optimize/iife.rs
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,11 @@ impl Optimizer<'_> {
self.prepend_stmts.push(
VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Let,
kind: if self.options.ecma >= EsVersion::Es2015 {
VarDeclKind::Let
} else {
VarDeclKind::Var
},
Comment on lines +689 to +693
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve per-iteration captures when lowering IIFE temp vars

Switching synthesized temp declarations from let to var in invoke_iife breaks closure semantics for arrow-IIFE inlining inside loops when compress.ecma < 2015. In patterns like for (const k in obj) { out[k] = ((k)=>()=>k)(k) }, the extracted temp now becomes a function-scoped var, so all generated closures share one binding and observe the last iteration’s value instead of each iteration’s value. This is a runtime behavior regression (not just formatting) and is visible in the updated 8246 fixture shape where the captured temp is now var inside the loop body.

Useful? React with 👍 / 👎.

Comment on lines +689 to +693
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid leaking inlined arrow params into outer function scope

Using var here also removes block scoping for synthesized parameter temporaries, so an inlined arrow-IIFE inside a nested block can unexpectedly alias an existing outer binding with the same name. For example, var value = 1; { ((value)=>value)(2); } now lowers through inline_fn_param into assignments against a function-scoped var value, mutating the outer value (returning 2) even though the original arrow parameter should have remained block-local shadowing.

Useful? React with 👍 / 👎.

Comment on lines +689 to +693
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent var/lexical redeclaration syntax errors after inlining

Lowering these synthesized temporaries to function-scoped var can make previously valid code fail to parse when the enclosing function already has a lexical binding with the same name. A valid case like function f(){ let x=0; { ((x)=>x)(1) } } can be rewritten with an injected var x, which conflicts with the existing let x in the same function scope and triggers Identifier 'x' has already been declared.

Useful? React with 👍 / 👎.

declare: Default::default(),
decls: vars,
..Default::default()
Expand Down
6 changes: 3 additions & 3 deletions crates/swc_ecma_minifier/tests/benches-full/terser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7449,7 +7449,8 @@
def_eval(AST_BigInt, function() {
return supports_bigint ? BigInt(this.value) : this;
}), def_eval(AST_RegExp, function(compressor) {
let source, evaluated = compressor.evaluated_regexps.get(this.value);
var source;
let evaluated = compressor.evaluated_regexps.get(this.value);
if (void 0 === evaluated && (source = this.value.source, re_safe_regexp.test(source))) {
try {
let { source, flags } = this.value;
Expand Down Expand Up @@ -10108,8 +10109,7 @@
}).optimize(compressor);
break;
case "RegExp":
let source;
var params = [];
var source, params = [];
if (self1.args.length >= 1 && self1.args.length <= 2 && self1.args.every((arg)=>{
var value = arg.evaluate(compressor);
return params.push(value), arg !== value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"defaults": true,
"ecma": 5,
"toplevel": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function run() {
return ((value) => value + value)(Math.random());
}

console.log(run());
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function run() {
var value;
return (value = Math.random()) + value;
}
console.log(run());
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ let isMultiIndexContext = (widget)=>hasMultipleIndices({
return {
...request,
params: Object.keys(parameters = request.params).map((key)=>{
let value;
var value;
return ((format, ...args)=>{
let i = 0;
return format.replace(/%s/g, ()=>encodeURIComponent(args[i++]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ let isMultiIndexContext = (widget)=>hasMultipleIndices({
return {
...request,
params: Object.keys(parameters = request.params).map((key)=>{
let value;
var value;
return ((format, ...args)=>{
let i = 0;
return format.replace(/%s/g, ()=>encodeURIComponent(args[i++]));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
{
/***/ 9145: /***/ function(m, S, h) {
"use strict";
let E, k;
/* harmony export */ h.d(S, {
/* harmony export */ u: function() {
return /* binding */ ei;
}
});
/* unused harmony exports TooltipProvider, TooltipWrapper */ /* harmony import */ var R, A = h(7294);
/* unused harmony exports TooltipProvider, TooltipWrapper */ /* harmony import */ var E, k, R, A = h(7294);
/* harmony import */ var O = h(5893);
var L = Object.create;
var j = Object.defineProperty;
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_minifier/tests/libs-size.snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
| lodash.js | 531.35 KiB | 68.92 KiB | 24.60 KiB |
| moment.js | 169.83 KiB | 57.34 KiB | 18.25 KiB |
| react.js | 70.45 KiB | 22.45 KiB | 8.04 KiB |
| terser.js | 1.08 MiB | 446.63 KiB | 120.49 KiB |
| terser.js | 1.08 MiB | 446.63 KiB | 120.50 KiB |
| three.js | 1.19 MiB | 630.55 KiB | 154.77 KiB |
| typescript.js | 10.45 MiB | 3.17 MiB | 840.61 KiB |
| victory.js | 2.30 MiB | 694.04 KiB | 154.18 KiB |
Expand Down
Loading