Skip to content

Commit f3c6248

Browse files
committed
fix minify bug with variable inlining and this
1 parent 1dd274d commit f3c6248

File tree

4 files changed

+58
-0
lines changed

4 files changed

+58
-0
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@
3434

3535
Notice how the property `foo` is always used unquoted but the property `bar` is always used quoted, so `foo` should be consistently mangled while `bar` should be consistently not mangled.
3636

37+
* Fix a minification bug regarding `this` and property initializers
38+
39+
When minification is enabled, esbuild attempts to inline the initializers of variables that have only been used once into the start of the following expression to reduce code size. However, there was a bug where this transformation could change the value of `this` when the initializer is a property access and the start of the following expression is a call expression. This release fixes the bug:
40+
41+
```js
42+
// Original code
43+
function foo(obj) {
44+
let fn = obj.prop;
45+
fn();
46+
}
47+
48+
// Old output (with --minify)
49+
function foo(f){f.prop()}
50+
51+
// New output (with --minify)
52+
function foo(o){let f=o.prop;f()}
53+
```
54+
3755
## 0.14.46
3856

3957
* Add the ability to override support for individual syntax features ([#2060](https://github.com/evanw/esbuild/issues/2060), [#2290](https://github.com/evanw/esbuild/issues/2290), [#2308](https://github.com/evanw/esbuild/issues/2308))

internal/js_parser/js_parser.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8285,6 +8285,15 @@ func (p *parser) substituteSingleUseSymbolInExpr(
82858285
}
82868286

82878287
case *js_ast.ECall:
8288+
// Don't substitute something into a call target that could change "this"
8289+
_, isDot := replacement.Data.(*js_ast.EDot)
8290+
_, isIndex := replacement.Data.(*js_ast.EIndex)
8291+
if isDot || isIndex {
8292+
if id, ok := e.Target.Data.(*js_ast.EIdentifier); ok && id.Ref == ref {
8293+
break
8294+
}
8295+
}
8296+
82888297
if value, status := p.substituteSingleUseSymbolInExpr(e.Target, ref, replacement, replacementCanBeRemoved); status != substituteContinue {
82898298
e.Target = value
82908299
return expr, status

internal/js_parser/js_parser_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4315,6 +4315,20 @@ func TestMangleInlineLocals(t *testing.T) {
43154315
expectPrintedMangleTarget(t, 2015, "(x => { let y = x; throw y ?? z })()", "((x) => {\n let y = x;\n throw y != null ? y : z;\n})();\n")
43164316
expectPrintedMangleTarget(t, 2015, "(x => { let y = x; y.z ??= z })()", "((x) => {\n var _a;\n let y = x;\n (_a = y.z) != null || (y.z = z);\n})();\n")
43174317
expectPrintedMangleTarget(t, 2015, "(x => { let y = x; y?.z })()", "((x) => {\n let y = x;\n y == null || y.z;\n})();\n")
4318+
4319+
// Cannot substitute into call targets when it would change "this"
4320+
check("let x = arg0; x()", "arg0();")
4321+
check("let x = arg0; (0, x)()", "arg0();")
4322+
check("let x = arg0.foo; x.bar()", "arg0.foo.bar();")
4323+
check("let x = arg0.foo; x[bar]()", "arg0.foo[bar]();")
4324+
check("let x = arg0.foo; x()", "let x = arg0.foo;\nx();")
4325+
check("let x = arg0[foo]; x()", "let x = arg0[foo];\nx();")
4326+
check("let x = arg0?.foo; x()", "let x = arg0?.foo;\nx();")
4327+
check("let x = arg0?.[foo]; x()", "let x = arg0?.[foo];\nx();")
4328+
check("let x = arg0.foo; (0, x)()", "let x = arg0.foo;\nx();")
4329+
check("let x = arg0[foo]; (0, x)()", "let x = arg0[foo];\nx();")
4330+
check("let x = arg0?.foo; (0, x)()", "let x = arg0?.foo;\nx();")
4331+
check("let x = arg0?.[foo]; (0, x)()", "let x = arg0?.[foo];\nx();")
43184332
}
43194333

43204334
func TestTrimCodeInDeadControlFlow(t *testing.T) {

scripts/end-to-end-tests.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,6 +2616,23 @@
26162616
`,
26172617
}),
26182618
)
2619+
2620+
// Check variable initializer inlining
2621+
tests.push(
2622+
test(['in.js', '--outfile=node.js'].concat(minify), {
2623+
'in.js': `
2624+
function foo() {
2625+
if (this !== globalThis) throw 'fail'
2626+
}
2627+
function main() {
2628+
let obj = { bar: foo };
2629+
let fn = obj.bar;
2630+
(0, fn)();
2631+
}
2632+
main()
2633+
`,
2634+
}),
2635+
);
26192636
}
26202637

26212638
// Test minification of top-level symbols

0 commit comments

Comments
 (0)