Skip to content

Commit

Permalink
forbid typescript's "export =" in esm
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Mar 7, 2021
1 parent 2cc7032 commit 3164eb7
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@

Getting this change out should hopefully make the linker rewrite go more smoothly.

* You can no longer mix ESM exports and CommonJS exports in the same file

Previously doing this was supported and caused the file to become a CommonJS module with the ESM exports becoming CommonJS exports. However, now that ESM code is no longer allowed inside a CommonJS wrapper, doing this is no longer supported. When you try to do this the ESM exports will win and references to the CommonJS `exports` and `module` variables will be passed through unmodified just like any other global variable reference. In addition, the TypeScript-specific `export =` syntax is not allowed in an ESM file. This is an alias for `module.exports =` and is already forbidden by the TypeScript compiler in an ESM file.

## Unreleased

* Fix overlapping chunk names when code splitting is active ([#928](https://github.com/evanw/esbuild/issues/928))
Expand Down
19 changes: 18 additions & 1 deletion internal/js_parser/js_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8110,7 +8110,24 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
}

case *js_ast.SExportEquals:
// "module.exports = value"
if p.hasESModuleSyntax {
var msgData logger.MsgData
exportRange := js_lexer.RangeOfIdentifier(p.source, stmt.Loc)
text := "Export assignment cannot be used in an ECMAScript module"
if equalsRange := p.source.RangeOfOperatorAfter(logger.Loc{Start: exportRange.End()}, "="); equalsRange.Len > 0 {
msgData = logger.RangeData(&p.source, equalsRange, text+" (consider using \"export default\" instead)")
msgData.Location.Suggestion = "default"
} else {
msgData = logger.RangeData(&p.source, exportRange, text)
}
p.log.AddMsg(logger.Msg{
Kind: logger.Error,
Data: msgData,
Notes: p.whyESModuleNotes(),
})
}

// "export = value" => "module.exports = value"
stmts = append(stmts, js_ast.AssignStmt(
js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EDot{
Target: js_ast.Expr{Loc: stmt.Loc, Data: &js_ast.EIdentifier{Ref: p.moduleRef}},
Expand Down
19 changes: 19 additions & 0 deletions internal/js_parser/js_parser_lower.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,25 @@ func (p *parser) markStrictModeFeature(feature strictModeFeature, r logger.Range
}
}

func (p *parser) whyESModuleNotes() []logger.MsgData {
var why string
var keywordRange logger.Range
if p.es6ImportKeyword.Len > 0 {
why = "This file is considered an ECMAScript module because of the \"import\" keyword"
keywordRange = p.es6ImportKeyword
} else if p.es6ExportKeyword.Len > 0 {
why = "This file is considered an ECMAScript module because of the \"export\" keyword"
keywordRange = p.es6ExportKeyword
} else if p.topLevelAwaitKeyword.Len > 0 {
why = "This file is considered an ECMAScript module because of the top-level \"await\" keyword"
keywordRange = p.topLevelAwaitKeyword
}
if why != "" {
return []logger.MsgData{logger.RangeData(&p.source, keywordRange, why)}
}
return nil
}

// Mark the feature if "loweredFeature" is unsupported. This is used when one
// feature is implemented in terms of another feature.
func (p *parser) markLoweredSyntaxFeature(feature compat.JSFeature, r logger.Range, loweredFeature compat.JSFeature) {
Expand Down
17 changes: 17 additions & 0 deletions internal/js_parser/ts_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1321,10 +1321,27 @@ func TestTSImport(t *testing.T) {

// This is TypeScript-specific export syntax
func TestTSExportEquals(t *testing.T) {
// Check that "export =" is relocated to the bottom of the file
expectPrintedTS(t, "export = x; let x = 1", "let x = 1;\nmodule.exports = x;\n")

// This use of the "export" keyword should not trigger strict mode because
// this syntax works in CommonJS modules, not in ECMAScript modules
expectPrintedTS(t, "export = []", "module.exports = [];\n")
expectPrintedTS(t, "export = []; with ({}) ;", "with ({})\n ;\nmodule.exports = [];\n")

// Disallow "export =" with ECMAScript modules
expectParseErrorTS(t, "export = []; import 'x'",
`<stdin>: error: Export assignment cannot be used in an ECMAScript module (consider using "export default" instead)
<stdin>: note: This file is considered an ECMAScript module because of the "import" keyword
`)
expectParseErrorTS(t, "export = []; export {}",
`<stdin>: error: Export assignment cannot be used in an ECMAScript module (consider using "export default" instead)
<stdin>: note: This file is considered an ECMAScript module because of the "export" keyword
`)
expectParseErrorTS(t, "export = []; await 0",
`<stdin>: error: Export assignment cannot be used in an ECMAScript module (consider using "export default" instead)
<stdin>: note: This file is considered an ECMAScript module because of the top-level "await" keyword
`)
}

// This is TypeScript-specific import syntax
Expand Down

0 comments on commit 3164eb7

Please sign in to comment.