Skip to content

Commit 7a72735

Browse files
committed
fix #4348: bundler bug with var inside if
1 parent 4e4e177 commit 7a72735

File tree

5 files changed

+101
-1
lines changed

5 files changed

+101
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
* Fix bundler bug with `var` nested inside `if` ([#4348](https://github.com/evanw/esbuild/issues/4348))
6+
7+
This release fixes a bug with the bundler that happens when importing an ES module using `require` (which causes it to be wrapped) and there's a top-level `var` inside an `if` statement without being wrapped in a `{ ... }` block (and a few other conditions). The bundling transform needed to hoist these `var` declarations outside of the lazy ES module wrapper for correctness. See the issue for details.
8+
59
* Fix minifier bug with `for` inside `try` inside label ([#4351](https://github.com/evanw/esbuild/issues/4351))
610

711
This fixes an old regression from [version v0.21.4](https://github.com/evanw/esbuild/releases/v0.21.4). Some code was introduced to move the label inside the `try` statement to address a problem with transforming labeled `for await` loops to avoid the `await` (the transformation involves converting the `for await` loop into a `for` loop and wrapping it in a `try` statement). However, it introduces problems for cross-compiled JVM code that uses all three of these features heavily. This release restricts this transform to only apply to `for` loops that esbuild itself generates internally as part of the `for await` transform. Here is an example of some affected code:

internal/bundler_tests/bundler_default_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9358,3 +9358,32 @@ func TestResolveExtensionsOrderIssue4053(t *testing.T) {
93589358
},
93599359
})
93609360
}
9361+
9362+
func TestBundleESMWithNestedVarIssue4348(t *testing.T) {
9363+
css_suite.expectBundled(t, bundled{
9364+
files: map[string]string{
9365+
"/entry.js": `
9366+
require('./foo')
9367+
`,
9368+
"/foo.js": `
9369+
var a = 'a'
9370+
for (var b = 'b'; 0; ) ;
9371+
if (true) { var c = 'c' }
9372+
if (true) var d = 'd'
9373+
if (false) {} else var e = 'e'
9374+
var x = 1
9375+
while (x--) var f = 'f'
9376+
do var g = 'g'; while (0);
9377+
for (; x++; ) var h = 'h'
9378+
for (var y in 'y') var i = 'i'
9379+
for (var y of 'y') var j = 'j'
9380+
export { a, b, c, d, e, f, g, h, i, j }
9381+
`,
9382+
},
9383+
entryPaths: []string{"/entry.js"},
9384+
options: config.Options{
9385+
Mode: config.ModeBundle,
9386+
AbsOutputFile: "/out.js",
9387+
},
9388+
})
9389+
}

internal/bundler_tests/snapshots/snapshots_css.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,48 @@ a {
1313
background: url(data:application/octet-stream;base64,iVBORw0KGgo=);
1414
}
1515

16+
================================================================================
17+
TestBundleESMWithNestedVarIssue4348
18+
---------- /out.js ----------
19+
// foo.js
20+
var foo_exports = {};
21+
__export(foo_exports, {
22+
a: () => a,
23+
b: () => b,
24+
c: () => c,
25+
d: () => d,
26+
e: () => e,
27+
f: () => f,
28+
g: () => g,
29+
h: () => h,
30+
i: () => i,
31+
j: () => j
32+
});
33+
var a, b, c, d, e, x, f, g, h, i, y, j, y;
34+
var init_foo = __esm({
35+
"foo.js"() {
36+
a = "a";
37+
for (b = "b"; 0; ) ;
38+
if (true) {
39+
c = "c";
40+
}
41+
if (true) d = "d";
42+
if (false) {
43+
} else e = "e";
44+
x = 1;
45+
while (x--) f = "f";
46+
do
47+
g = "g";
48+
while (0);
49+
for (; x++; ) h = "h";
50+
for (y in "y") i = "i";
51+
for (y of "y") j = "j";
52+
}
53+
});
54+
55+
// entry.js
56+
init_foo();
57+
1658
================================================================================
1759
TestCSSAndJavaScriptCodeSplittingIssue1064
1860
---------- /out/a.js ----------

internal/js_parser/js_parser.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ type parser struct {
252252
firstJSXElementLoc logger.Loc
253253

254254
fnOrArrowDataVisit fnOrArrowDataVisit
255+
singleStmtDepth int
255256

256257
// ArrowFunction is a special case in the grammar. Although it appears to be
257258
// a PrimaryExpression, it's actually an AssignmentExpression. This means if
@@ -9911,7 +9912,9 @@ func (p *parser) visitSingleStmt(stmt js_ast.Stmt, kind stmtsKind) js_ast.Stmt {
99119912
}
99129913
}
99139914

9915+
p.singleStmtDepth++
99149916
stmts := p.visitStmts([]js_ast.Stmt{stmt}, kind)
9917+
p.singleStmtDepth--
99159918

99169919
// Balance the fake block scope introduced above
99179920
if hasIfScope {
@@ -11623,7 +11626,7 @@ const (
1162311626
// instead of having to traverse recursively into the statement tree.
1162411627
func (p *parser) maybeRelocateVarsToTopLevel(decls []js_ast.Decl, mode relocateVarsMode) (js_ast.Stmt, bool) {
1162511628
// Only do this when bundling, and not when the scope is already top-level
11626-
if p.options.mode != config.ModeBundle || p.currentScope == p.moduleScope {
11629+
if p.options.mode != config.ModeBundle || (p.currentScope == p.moduleScope && p.singleStmtDepth == 0) {
1162711630
return js_ast.Stmt{}, false
1162811631
}
1162911632

scripts/end-to-end-tests.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,28 @@ for (const minify of [[], ['--minify']]) {
19061906
'in.js': `import foo from './foo'; if (foo() !== (function() { return this })()) throw 'fail'`,
19071907
'foo.js': `module.exports = function() { return this }`,
19081908
}),
1909+
1910+
// https://github.com/evanw/esbuild/issues/4348
1911+
test(['--bundle', '--format=cjs', 'in.js', '--outfile=node.js', '--target=' + target].concat(minify), {
1912+
'in.js': `
1913+
var foo = Object.entries({ ...require('./foo') }).join('|')
1914+
if (foo !== 'a,a|b,b|c,c|d,d|e,e|f,f|g,g|h,h|i,i|j,j') throw 'fail: ' + foo
1915+
`,
1916+
'foo.js': `
1917+
var a = 'a'
1918+
for (var b = 'b'; 0; ) ;
1919+
if (true) { var c = 'c' }
1920+
if (true) var d = 'd'
1921+
if (false) {} else var e = 'e'
1922+
var x = 1
1923+
while (x--) var f = 'f'
1924+
do var g = 'g'; while (0);
1925+
for (; x++; ) var h = 'h'
1926+
for (var y in 'y') var i = 'i'
1927+
for (var y ${target === 'es5' ? 'in' : 'of'} 'y') var j = 'j'
1928+
export { a, b, c, d, e, f, g, h, i, j }
1929+
`,
1930+
}),
19091931
)
19101932
}
19111933

0 commit comments

Comments
 (0)