Skip to content

Commit 4d9b764

Browse files
committed
fix #3311: pop scope after --drop-labels= runs
1 parent f2d23b2 commit 4d9b764

4 files changed

Lines changed: 47 additions & 6 deletions

File tree

CHANGELOG.md

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

1919
Note that this change means esbuild no longer considers any existing browser to support CSS nesting since none of the existing browsers support this new syntax. CSS nesting will now always be transformed when targeting a browser. This situation will change in the future as browsers add support for this new syntax.
2020

21+
* Fix a scope-related bug with `--drop-labels=` ([#3311](https://github.com/evanw/esbuild/issues/3311))
22+
23+
The recently-released `--drop-labels=` feature previously had a bug where esbuild's internal scope stack wasn't being restored properly when a statement with a label was dropped. This could manifest as a tree-shaking issue, although it's possible that this could have also been causing other subtle problems too. The bug has been fixed in this release.
24+
2125
* Make renamed CSS names unique across entry points ([#3295](https://github.com/evanw/esbuild/issues/3295))
2226

2327
Previously esbuild's generated names for local names in CSS were only unique within a given entry point (or across all entry points when code splitting was enabled). That meant that building multiple entry points with esbuild could result in local names being renamed to the same identifier even when those entry points were built simultaneously within a single esbuild API call. This problem was especially likely to happen with minification enabled. With this release, esbuild will now avoid renaming local names from two separate entry points to the same name if those entry points were built with a single esbuild API call, even when code splitting is disabled.

internal/bundler_tests/bundler_dce_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4510,3 +4510,24 @@ func TestRemoveCodeAfterLabelWithReturn(t *testing.T) {
45104510
},
45114511
})
45124512
}
4513+
4514+
func TestDropLabelTreeShakingBugIssue3311(t *testing.T) {
4515+
dce_suite.expectBundled(t, bundled{
4516+
files: map[string]string{
4517+
"/entry.js": `
4518+
const myFunc = ()=> {
4519+
DROP: {console.log("drop")}
4520+
console.log("keep")
4521+
}
4522+
export default myFunc
4523+
`,
4524+
},
4525+
entryPaths: []string{"/entry.js"},
4526+
options: config.Options{
4527+
Mode: config.ModeBundle,
4528+
AbsOutputFile: "/out.js",
4529+
DropLabels: []string{"DROP"},
4530+
OutputFormat: config.FormatESModule,
4531+
},
4532+
})
4533+
}

internal/bundler_tests/snapshots/snapshots_dce.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,18 @@ var keepMe4 = keepMe3();
803803
var keepMe5 = pure();
804804
var keepMe6 = some.fn();
805805

806+
================================================================================
807+
TestDropLabelTreeShakingBugIssue3311
808+
---------- /out.js ----------
809+
// entry.js
810+
var myFunc = () => {
811+
console.log("keep");
812+
};
813+
var entry_default = myFunc;
814+
export {
815+
entry_default as default
816+
};
817+
806818
================================================================================
807819
TestDropLabels
808820
---------- /out.js ----------

internal/js_parser/js_parser.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10089,18 +10089,22 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
1008910089
p.currentScope.LabelStmtIsLoop = true
1009010090
}
1009110091

10092-
// Drop this entire statement if requested
10093-
if _, ok := p.dropLabelsMap[name]; ok {
10094-
old := p.isControlFlowDead
10092+
// If we're dropping this statement, consider control flow to be dead
10093+
_, shouldDropLabel := p.dropLabelsMap[name]
10094+
old := p.isControlFlowDead
10095+
if shouldDropLabel {
1009510096
p.isControlFlowDead = true
10096-
s.Stmt = p.visitSingleStmt(s.Stmt, stmtsNormal)
10097-
p.isControlFlowDead = old
10098-
return stmts
1009910097
}
1010010098

1010110099
s.Stmt = p.visitSingleStmt(s.Stmt, stmtsNormal)
1010210100
p.popScope()
1010310101

10102+
// Drop this entire statement if requested
10103+
if shouldDropLabel {
10104+
p.isControlFlowDead = old
10105+
return stmts
10106+
}
10107+
1010410108
if p.options.minifySyntax {
1010510109
// Optimize "x: break x" which some people apparently write by hand
1010610110
if child, ok := s.Stmt.Data.(*js_ast.SBreak); ok && child.Label != nil && child.Label.Ref == s.Name.Ref {

0 commit comments

Comments
 (0)