diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56a6cdb50bb..f9650993d24 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,22 @@
(0, import_fs.readFile)();
```
+* Strip overwritten function declarations when minifying ([#610](https://github.com/evanw/esbuild/issues/610))
+
+ JavaScript allows functions to be re-declared, with each declaration overwriting the previous declaration. This type of code can sometimes be emitted by automatic code generators. With this release, esbuild now takes this behavior into account when minifying to drop all but the last declaration for a given function:
+
+ ```js
+ // Original code
+ function foo() { console.log(1) }
+ function foo() { console.log(2) }
+
+ // Old output (with --minify)
+ function foo(){console.log(1)}function foo(){console.log(2)}
+
+ // New output (with --minify)
+ function foo(){console.log(2)}
+ ```
+
* Allow whitespace around `:` in JSX elements ([#1877](https://github.com/evanw/esbuild/issues/1877))
This release allows you to write the JSX `` as `` instead. Doing this is not forbidden by [the JSX specification](https://facebook.github.io/jsx/). While this doesn't work in TypeScript, it does work with other JSX parsers in the ecosystem, so support for this has been added to esbuild.
diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go
index 86f9e5a0943..bab684a1f97 100644
--- a/internal/js_ast/js_ast.go
+++ b/internal/js_ast/js_ast.go
@@ -1561,6 +1561,14 @@ const (
// Foo.#foo = Foo;
//
PrivateSymbolMustBeLowered
+
+ // This is used to remove the all but the last function re-declaration if a
+ // function is re-declared multiple times like this:
+ //
+ // function foo() { console.log(1) }
+ // function foo() { console.log(2) }
+ //
+ RemoveOverwrittenFunctionDeclaration
)
func (flags SymbolFlags) Has(flag SymbolFlags) bool {
diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go
index e809e83138b..c129a86dfd1 100644
--- a/internal/js_parser/js_parser.go
+++ b/internal/js_parser/js_parser.go
@@ -1426,7 +1426,7 @@ func (p *parser) canMergeSymbols(scope *js_ast.Scope, existing js_ast.SymbolKind
if new.IsHoistedOrFunction() && existing.IsHoistedOrFunction() &&
(scope.Kind == js_ast.ScopeEntry || scope.Kind == js_ast.ScopeFunctionBody ||
(new.IsHoisted() && existing.IsHoisted())) {
- return mergeKeepExisting
+ return mergeReplaceWithNew
}
// "get #foo() {} set #foo() {}"
@@ -1491,6 +1491,11 @@ func (p *parser) declareSymbol(kind js_ast.SymbolKind, loc logger.Loc, name stri
case mergeReplaceWithNew:
symbol.Link = ref
+ // If these are both functions, remove the overwritten declaration
+ if p.options.mangleSyntax && kind.IsFunction() && symbol.Kind.IsFunction() {
+ symbol.Flags |= js_ast.RemoveOverwrittenFunctionDeclaration
+ }
+
case mergeBecomePrivateGetSetPair:
ref = existing.Ref
symbol.Kind = js_ast.SymbolPrivateGetSetPair
@@ -9485,6 +9490,11 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
case *js_ast.SFunction:
p.visitFn(&s.Fn, s.Fn.OpenParenLoc)
+ // Strip this function declaration if it was overwritten
+ if p.symbols[s.Fn.Name.Ref.InnerIndex].Flags.Has(js_ast.RemoveOverwrittenFunctionDeclaration) && !s.IsExport {
+ return stmts
+ }
+
// Handle exporting this function from a namespace
if s.IsExport && p.enclosingNamespaceArgRef != nil {
s.IsExport = false
diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go
index a22440f8b99..05cc4578aba 100644
--- a/internal/js_parser/js_parser_test.go
+++ b/internal/js_parser/js_parser_test.go
@@ -1342,6 +1342,21 @@ func TestFunction(t *testing.T) {
expectPrintedMangle(t, "function* foo() { return undefined }", "function* foo() {\n}\n")
expectPrintedMangle(t, "async function foo() { return undefined }", "async function foo() {\n}\n")
expectPrintedMangle(t, "async function* foo() { return undefined }", "async function* foo() {\n return void 0;\n}\n")
+
+ // Strip overwritten function declarations
+ expectPrintedMangle(t, "function f() { x() } function f() { y() }", "function f() {\n y();\n}\n")
+ expectPrintedMangle(t, "function f() { x() } function *f() { y() }", "function* f() {\n y();\n}\n")
+ expectPrintedMangle(t, "function *f() { x() } function f() { y() }", "function f() {\n y();\n}\n")
+ expectPrintedMangle(t, "function *f() { x() } function *f() { y() }", "function* f() {\n y();\n}\n")
+ expectPrintedMangle(t, "function f() { x() } async function f() { y() }", "async function f() {\n y();\n}\n")
+ expectPrintedMangle(t, "async function f() { x() } function f() { y() }", "function f() {\n y();\n}\n")
+ expectPrintedMangle(t, "async function f() { x() } async function f() { y() }", "async function f() {\n y();\n}\n")
+ expectPrintedMangle(t, "var f; function f() {}", "var f;\nfunction f() {\n}\n")
+ expectPrintedMangle(t, "function f() {} var f", "function f() {\n}\nvar f;\n")
+ expectPrintedMangle(t, "var f; function f() { x() } function f() { y() }", "var f;\nfunction f() {\n y();\n}\n")
+ expectPrintedMangle(t, "function f() { x() } function f() { y() } var f", "function f() {\n y();\n}\nvar f;\n")
+ expectPrintedMangle(t, "function f() { x() } var f; function f() { y() }", "function f() {\n x();\n}\nvar f;\nfunction f() {\n y();\n}\n")
+ expectPrintedMangle(t, "export function f() { x() } function f() { y() }", "export function f() {\n x();\n}\nfunction f() {\n y();\n}\n")
}
func TestClass(t *testing.T) {