Skip to content

Commit

Permalink
fix #316: omit export default of local type
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Aug 5, 2020
1 parent df265cd commit aee1a02
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

The cache holds a maximum of 5 entries and purges least-recently-used entries above that limit.

* Omit `export default` of local type names ([#316](https://github.com/evanw/esbuild/issues/316))

Normally the `export default` syntax takes a value expression to export. However, TypeScript has a special case for `export default <identifier>` where the identifier is allowed to be a type expression instead of a value expression. In that case, the type expression should not be emitted in the resulting bundle. This release improves support for this case by omitting the export when the identifier matches a local type name.

## 0.6.16

* Colors for Windows console output
Expand Down
208 changes: 208 additions & 0 deletions internal/bundler/bundler_ts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1007,3 +1007,211 @@ console.log(all_default, all_computed_default, a, b, c, d, e_default2, f_default
},
})
}

func TestTSExportDefaultTypeIssue316(t *testing.T) {
expectBundled(t, bundled{
files: map[string]string{
"/entry.ts": `
import dc_def, { bar as dc } from './keep/declare-class'
import dl_def, { bar as dl } from './keep/declare-let'
import im_def, { bar as im } from './keep/interface-merged'
import in_def, { bar as _in } from './keep/interface-nested'
import tn_def, { bar as tn } from './keep/type-nested'
import vn_def, { bar as vn } from './keep/value-namespace'
import vnm_def, { bar as vnm } from './keep/value-namespace-merged'
import i_def, { bar as i } from './remove/interface'
import ie_def, { bar as ie } from './remove/interface-exported'
import t_def, { bar as t } from './remove/type'
import te_def, { bar as te } from './remove/type-exported'
import ton_def, { bar as ton } from './remove/type-only-namespace'
import tone_def, { bar as tone } from './remove/type-only-namespace-exported'
export default [
dc,
dl,
im,
_in,
tn,
vn,
vnm,
i,
ie,
t,
te,
ton,
tone,
]
`,
"/keep/declare-class.ts": `
declare class foo {}
export default foo
export let bar = 123
`,
"/keep/declare-let.ts": `
declare let foo: number
export default foo
export let bar = 123
`,
"/keep/interface-merged.ts": `
class foo {
static x = new foo
}
interface foo {}
export default foo
export let bar = 123
`,
"/keep/interface-nested.ts": `
if (true) {
interface foo {}
}
export default foo
export let bar = 123
`,
"/keep/type-nested.ts": `
if (true) {
type foo = number
}
export default foo
export let bar = 123
`,
"/keep/value-namespace.ts": `
namespace foo {
export let num = 0
}
export default foo
export let bar = 123
`,
"/keep/value-namespace-merged.ts": `
namespace foo {
export type num = number
}
namespace foo {
export let num = 0
}
export default foo
export let bar = 123
`,
"/remove/interface.ts": `
interface foo { }
export default foo
export let bar = 123
`,
"/remove/interface-exported.ts": `
export interface foo { }
export default foo
export let bar = 123
`,
"/remove/type.ts": `
type foo = number
export default foo
export let bar = 123
`,
"/remove/type-exported.ts": `
export type foo = number
export default foo
export let bar = 123
`,
"/remove/type-only-namespace.ts": `
namespace foo {
export type num = number
}
export default foo
export let bar = 123
`,
"/remove/type-only-namespace-exported.ts": `
export namespace foo {
export type num = number
}
export default foo
export let bar = 123
`,
},
entryPaths: []string{"/entry.ts"},
options: config.Options{
IsBundling: true,
AbsOutputFile: "/out.js",
},
expected: map[string]string{
"/out.js": `// /keep/declare-class.ts
var declare_class_default = foo;
let bar = 123;
// /keep/declare-let.ts
var declare_let_default = foo;
let bar2 = 123;
// /keep/interface-merged.ts
class foo2 {
}
foo2.x = new foo2();
let bar3 = 123;
// /keep/interface-nested.ts
if (true) {
}
var interface_nested_default = foo;
let bar4 = 123;
// /keep/type-nested.ts
if (true) {
}
var type_nested_default = foo;
let bar5 = 123;
// /keep/value-namespace.ts
var foo4;
(function(foo5) {
foo5.num = 0;
})(foo4 || (foo4 = {}));
let bar6 = 123;
// /keep/value-namespace-merged.ts
var foo3;
(function(foo5) {
foo5.num = 0;
})(foo3 || (foo3 = {}));
let bar7 = 123;
// /remove/interface.ts
let bar8 = 123;
// /remove/interface-exported.ts
let bar9 = 123;
// /remove/type.ts
let bar10 = 123;
// /remove/type-exported.ts
let bar11 = 123;
// /remove/type-only-namespace.ts
let bar12 = 123;
// /remove/type-only-namespace-exported.ts
let bar13 = 123;
// /entry.ts
var entry_default = [
bar,
bar2,
bar3,
bar4,
bar5,
bar6,
bar7,
bar8,
bar9,
bar10,
bar11,
bar12,
bar13
];
export {
entry_default as default
};
`,
},
})
}
22 changes: 20 additions & 2 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type parser struct {
emittedNamespaceVars map[ast.Ref]bool
isExportedInsideNamespace map[ast.Ref]ast.Ref
knownEnumValues map[ast.Ref]map[string]float64
localTypeNames map[string]bool

// Imports (both ES6 and CommonJS) are tracked at the top level
importRecords []ast.ImportRecord
Expand Down Expand Up @@ -3954,7 +3955,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) ast.Stmt {
case "type":
// "export type foo = ..."
p.lexer.Next()
p.skipTypeScriptTypeStmt(parseStmtOpts{isExport: true})
p.skipTypeScriptTypeStmt(parseStmtOpts{isModuleScope: opts.isModuleScope, isExport: true})
return ast.Stmt{Loc: loc, Data: &ast.STypeScript{}}

case "namespace", "abstract", "module":
Expand Down Expand Up @@ -4185,7 +4186,13 @@ func (p *parser) parseStmt(opts parseStmtOpts) ast.Stmt {
}

p.lexer.Next()
name := p.lexer.Identifier
p.lexer.Expect(lexer.TIdentifier)

if opts.isModuleScope {
p.localTypeNames[name] = true
}

p.skipTypeScriptTypeParameters()

if p.lexer.Token == lexer.TExtends {
Expand Down Expand Up @@ -4880,7 +4887,7 @@ func (p *parser) parseStmt(opts parseStmtOpts) ast.Stmt {
case "type":
if p.lexer.Token == lexer.TIdentifier {
// "type Foo = any"
p.skipTypeScriptTypeStmt(parseStmtOpts{})
p.skipTypeScriptTypeStmt(parseStmtOpts{isModuleScope: opts.isModuleScope})
return ast.Stmt{Loc: loc, Data: &ast.STypeScript{}}
}

Expand Down Expand Up @@ -5839,6 +5846,16 @@ func (p *parser) visitAndAppendStmt(stmts []ast.Stmt, stmt ast.Stmt) []ast.Stmt
case s.Value.Expr != nil:
*s.Value.Expr = p.visitExpr(*s.Value.Expr)

// Discard type-only export default statements
if p.TS.Parse {
if id, ok := (*s.Value.Expr).Data.(*ast.EIdentifier); ok {
symbol := p.symbols[id.Ref.InnerIndex]
if symbol.Kind == ast.SymbolUnbound && p.localTypeNames[symbol.Name] {
return stmts
}
}
}

case s.Value.Stmt != nil:
switch s2 := s.Value.Stmt.Data.(type) {
case *ast.SFunction:
Expand Down Expand Up @@ -8587,6 +8604,7 @@ func newParser(log logging.Log, source logging.Source, lexer lexer.Lexer, option
emittedNamespaceVars: make(map[ast.Ref]bool),
isExportedInsideNamespace: make(map[ast.Ref]ast.Ref),
knownEnumValues: make(map[ast.Ref]map[string]float64),
localTypeNames: make(map[string]bool),

// These are for handling ES6 imports and exports
importItemsForNamespace: make(map[ast.Ref]map[string]ast.LocRef),
Expand Down
9 changes: 9 additions & 0 deletions internal/parser/parser_ts.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,13 @@ func (p *parser) skipTypeScriptTypeStmt(opts parseStmtOpts) {
return
}

name := p.lexer.Identifier
p.lexer.Expect(lexer.TIdentifier)

if opts.isModuleScope {
p.localTypeNames[name] = true
}

p.skipTypeScriptTypeParameters()
p.lexer.Expect(lexer.TEquals)
p.skipTypeScriptType(ast.LLowest)
Expand Down Expand Up @@ -820,6 +826,9 @@ func (p *parser) parseTypeScriptNamespaceStmt(loc ast.Loc, opts parseStmtOpts) a
// real export either.
if len(stmts) == importEqualsCount || opts.isTypeScriptDeclare {
p.popAndDiscardScope(scopeIndex)
if opts.isModuleScope {
p.localTypeNames[nameText] = true
}
return ast.Stmt{Loc: loc, Data: &ast.STypeScript{}}
}

Expand Down
7 changes: 0 additions & 7 deletions internal/printer/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,6 @@ func (p *printer) addSourceMapping(loc ast.Loc) {
}
originalLine--

if originalLine == -1 {
fmt.Printf("crash in file %s\n", p.options.SourceForSourceMap.PrettyPath)
fmt.Printf("lookup %d\n", loc.Start)
fmt.Printf("length %d\n", len(lineOffsetTables))
fmt.Printf("first %d\n", lineOffsetTables[0].byteOffsetToStartOfLine)
}

// Use the line to compute the column
line := &lineOffsetTables[originalLine]
originalColumn := int(loc.Start - line.byteOffsetToStartOfLine)
Expand Down

0 comments on commit aee1a02

Please sign in to comment.