Skip to content

Commit

Permalink
fix evanw#610: minify now drops overwritten functions
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Dec 22, 2021
1 parent 70d1943 commit 4fa3d7a
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 1 deletion.
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<rdf:Description rdf:ID="foo" />` as `<rdf : Description rdf : ID="foo" />` 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.
Expand Down
8 changes: 8 additions & 0 deletions internal/js_ast/js_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions internal/js_parser/js_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 4fa3d7a

Please sign in to comment.