Skip to content

Commit

Permalink
fix bug with re-export names when bundling
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jun 20, 2020
1 parent a467e6b commit cbfdf05
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 48 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Fix re-exports when bundling

This is similar to the fix for re-exports in version 0.5.6 except that it applies when bundling, instead of just when transforming. It needed to be fixed differently because of how cross-file linking works when bundling.

## 0.5.6

* Fix re-export statements ([#190](https://github.com/evanw/esbuild/issues/190))
Expand Down
5 changes: 5 additions & 0 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,11 @@ type ClauseItem struct {
Alias string
AliasLoc Loc
Name LocRef

// This is needed for "export {foo as bar} from 'path'" statements. This case
// is a re-export and "foo" and "bar" are both aliases. We need to preserve
// both aliases in case the symbol is renamed.
OriginalName string
}

type Decl struct {
Expand Down
40 changes: 40 additions & 0 deletions internal/bundler/bundler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5350,6 +5350,46 @@ export {
})
}

func TestReExportDefaultExternal(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
export {default as foo} from 'foo'
export {bar} from './bar'
`,
"/bar.js": `
export {default as bar} from 'bar'
`,
},
entryPaths: []string{"/entry.js"},
parseOptions: parser.ParseOptions{
IsBundling: true,
},
bundleOptions: BundleOptions{
IsBundling: true,
AbsOutputFile: "/out.js",
},
resolveOptions: resolver.ResolveOptions{
ExternalModules: map[string]bool{
"foo": true,
"bar": true,
},
},
expected: map[string]string{
"/out.js": `// /bar.js
import {default as default2} from "bar";
// /entry.js
import {default as default3} from "foo";
export {
default2 as bar,
default3 as foo
};
`,
},
})
}

func TestReExportDefaultNoBundle(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
Expand Down
10 changes: 3 additions & 7 deletions internal/bundler/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,9 @@ func (c *linkerContext) convertStmtsForChunk(sourceIndex uint32, stmtList *stmtL

if shouldStripExports {
// Turn this statement into "import {foo} from 'path'"
for i, item := range s.Items {
s.Items[i].Alias = item.OriginalName
}
stmt.Data = &ast.SImport{
NamespaceRef: s.NamespaceRef,
Items: &s.Items,
Expand Down Expand Up @@ -2378,13 +2381,6 @@ func (c *linkerContext) markExportsAsUnbound(sourceIndex uint32) {
hasImportOrExport = true

case *ast.SExportFrom:
// This is a re-export and the symbols created here are used to reference
// names in another file. This means the symbols are really aliases. The
// symbols are marked as "unbound" so that they aren't accidentally renamed
// by the code that avoids symbol name collisions.
for _, item := range s.Items {
c.symbols.Get(item.Name.Ref).Kind = ast.SymbolUnbound
}
hasImportOrExport = true
}
}
Expand Down
14 changes: 9 additions & 5 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3745,6 +3745,7 @@ func (p *parser) parseImportClause() ([]ast.ClauseItem, bool) {
alias := p.lexer.Identifier
aliasLoc := p.lexer.Loc()
name := ast.LocRef{aliasLoc, p.storeNameInRef(alias)}
originalName := alias

// The alias may be a keyword
isIdentifier := p.lexer.Token == lexer.TIdentifier
Expand All @@ -3755,14 +3756,15 @@ func (p *parser) parseImportClause() ([]ast.ClauseItem, bool) {

if p.lexer.IsContextualKeyword("as") {
p.lexer.Next()
name = ast.LocRef{p.lexer.Loc(), p.storeNameInRef(p.lexer.Identifier)}
originalName := p.lexer.Identifier
name = ast.LocRef{p.lexer.Loc(), p.storeNameInRef(originalName)}
p.lexer.Expect(lexer.TIdentifier)
} else if !isIdentifier {
// An import where the name is a keyword must have an alias
p.lexer.Unexpected()
}

items = append(items, ast.ClauseItem{alias, aliasLoc, name})
items = append(items, ast.ClauseItem{alias, aliasLoc, name, originalName})

if p.lexer.Token != lexer.TComma {
break
Expand Down Expand Up @@ -3793,6 +3795,7 @@ func (p *parser) parseExportClause() ([]ast.ClauseItem, bool) {
alias := p.lexer.Identifier
aliasLoc := p.lexer.Loc()
name := ast.LocRef{aliasLoc, p.storeNameInRef(alias)}
originalName := alias

// The name can actually be a keyword if we're really an "export from"
// statement. However, we won't know until later. Allow keywords as
Expand Down Expand Up @@ -3826,7 +3829,7 @@ func (p *parser) parseExportClause() ([]ast.ClauseItem, bool) {
p.lexer.Next()
}

items = append(items, ast.ClauseItem{alias, aliasLoc, name})
items = append(items, ast.ClauseItem{alias, aliasLoc, name, originalName})

if p.lexer.Token != lexer.TComma {
break
Expand Down Expand Up @@ -9310,8 +9313,9 @@ func (p *parser) scanForImportsAndExports(stmts []ast.Stmt, isBundling bool) []a
if items == nil {
items = &[]ast.ClauseItem{}
}
for name, item := range importItems {
*items = append(*items, ast.ClauseItem{name, item.Loc, item})
for alias, name := range importItems {
originalName := p.symbols[name.Ref.InnerIndex].Name
*items = append(*items, ast.ClauseItem{alias, name.Loc, name, originalName})
}
s.Items = items
}
Expand Down
101 changes: 65 additions & 36 deletions internal/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2199,15 +2199,78 @@ func (p *printer) printStmt(stmt ast.Stmt) {
p.printSpaceBeforeIdentifier()
p.print("export")
p.printSpace()
p.printExportClause(s.Items, s.IsSingleLine)
p.print("{")

if !s.IsSingleLine {
p.options.Indent++
}

for i, item := range s.Items {
if i != 0 {
p.print(",")
if s.IsSingleLine {
p.printSpace()
}
}

if !s.IsSingleLine {
p.printNewline()
p.printIndent()
}
name := p.symbolName(item.Name.Ref)
p.print(name)
if name != item.Alias {
p.print(" as ")
p.print(item.Alias)
}
}

if !s.IsSingleLine {
p.options.Indent--
p.printNewline()
p.printIndent()
}

p.print("}")
p.printSemicolonAfterStatement()

case *ast.SExportFrom:
p.printIndent()
p.printSpaceBeforeIdentifier()
p.print("export")
p.printSpace()
p.printExportClause(s.Items, s.IsSingleLine)
p.print("{")

if !s.IsSingleLine {
p.options.Indent++
}

for i, item := range s.Items {
if i != 0 {
p.print(",")
if s.IsSingleLine {
p.printSpace()
}
}

if !s.IsSingleLine {
p.printNewline()
p.printIndent()
}
p.print(item.OriginalName)
if item.OriginalName != item.Alias {
p.print(" as ")
p.print(item.Alias)
}
}

if !s.IsSingleLine {
p.options.Indent--
p.printNewline()
p.printIndent()
}

p.print("}")
p.printSpace()
p.print("from")
p.printSpace()
Expand Down Expand Up @@ -2556,40 +2619,6 @@ func (p *printer) printStmt(stmt ast.Stmt) {
}
}

func (p *printer) printExportClause(items []ast.ClauseItem, isSingleLine bool) {
p.print("{")
if !isSingleLine {
p.options.Indent++
}

for i, item := range items {
if i != 0 {
p.print(",")
if isSingleLine {
p.printSpace()
}
}

if !isSingleLine {
p.printNewline()
p.printIndent()
}
name := p.symbolName(item.Name.Ref)
p.print(name)
if name != item.Alias {
p.print(" as ")
p.print(item.Alias)
}
}

if !isSingleLine {
p.options.Indent--
p.printNewline()
p.printIndent()
}
p.print("}")
}

type PrintOptions struct {
OutputFormat Format
RemoveWhitespace bool
Expand Down

0 comments on commit cbfdf05

Please sign in to comment.