diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c8f6a94b1..19e1e8963ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ ## Unreleased +* Transform arrow functions to function expressions with `--target=es5` ([#182](https://github.com/evanw/esbuild/issues/182) and [#297](https://github.com/evanw/esbuild/issues/297)) + + Arrow functions are now transformed into function expressions when targeting `es5`. For example, this code: + + ```js + function foo() { + var x = () => [this, arguments] + return x() + } + ``` + + is transformed into this code: + + ```js + function foo() { + var _this = this, _arguments = arguments; + var x = function() { + return [_this, _arguments]; + }; + return x(); + } + ``` + * Fix a bug where `module` was incorrectly minified for non-JavaScript loaders If you pass a non-JavaScript file such as a `.json` file to esbuild, it will by default generate `module.exports = {...}`. However, the `module` variable would incorrectly be minified when `--minify` is present. This issue has been fixed. This bug did not appear if `--format=cjs` was also present, only if no `--format` flag was specified. diff --git a/internal/bundler/bundler_lower_test.go b/internal/bundler/bundler_lower_test.go index 00d989d0ded..72844570ce0 100644 --- a/internal/bundler/bundler_lower_test.go +++ b/internal/bundler/bundler_lower_test.go @@ -900,9 +900,7 @@ func TestLowerAsyncES5(t *testing.T) { AbsOutputFile: "/out.js", }, expectedScanLog: `/arrow-1.js: error: Transforming async functions to the configured target environment is not supported yet -/arrow-1.js: error: Transforming arrow functions to the configured target environment is not supported yet /arrow-2.js: error: Transforming async functions to the configured target environment is not supported yet -/arrow-2.js: error: Transforming arrow functions to the configured target environment is not supported yet /export-def-1.js: error: Transforming async functions to the configured target environment is not supported yet /export-def-2.js: error: Transforming async functions to the configured target environment is not supported yet /fn-expr.js: error: Transforming async functions to the configured target environment is not supported yet diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 3a30d3feadb..adf8a43a583 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -220,6 +220,7 @@ type fnOrArrowDataParse struct { type fnOrArrowDataVisit struct { superIndexRef *js_ast.Ref + isArrow bool isAsync bool isInsideLoop bool isInsideSwitch bool @@ -1610,12 +1611,7 @@ func (p *parser) parseArrowBody(args []js_ast.Arg, data fnOrArrowDataParse) *js_ panic(js_lexer.LexerPanic{}) } - if p.lexer.Token == js_lexer.TEqualsGreaterThan { - p.markSyntaxFeature(compat.Arrow, p.lexer.Range()) - p.lexer.Next() - } else { - p.lexer.Expected(js_lexer.TEqualsGreaterThan) - } + p.lexer.Expect(js_lexer.TEqualsGreaterThan) for _, arg := range args { p.declareBinding(js_ast.SymbolHoisted, arg.Binding, parseStmtOpts{}) @@ -7933,6 +7929,12 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO return value, exprOut{} } + // Capture "this" inside arrow functions that will be lowered into normal + // function expressions for older language environments + if p.fnOrArrowDataVisit.isArrow && p.UnsupportedFeatures.Has(compat.Arrow) && p.fnOnlyDataVisit.isThisNested { + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EIdentifier{Ref: p.captureThis()}}, exprOut{} + } + case *js_ast.EImportMeta: if p.importMetaRef != js_ast.InvalidRef { // Replace "import.meta" with a reference to the symbol @@ -9036,6 +9038,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO case *js_ast.EArrow: oldFnOrArrowData := p.fnOrArrowDataVisit p.fnOrArrowDataVisit = fnOrArrowDataVisit{ + isArrow: true, isAsync: e.IsAsync, } @@ -9071,6 +9074,17 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO p.fnOnlyDataVisit.isInsideAsyncArrowFn = oldInsideAsyncArrowFn p.fnOrArrowDataVisit = oldFnOrArrowData + // Convert arrow functions to function expressions when lowering + if p.UnsupportedFeatures.Has(compat.Arrow) { + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EFunction{Fn: js_ast.Fn{ + Args: e.Args, + Body: e.Body, + ArgumentsRef: js_ast.InvalidRef, + IsAsync: e.IsAsync, + HasRestArg: e.HasRestArg, + }}}, exprOut{} + } + case *js_ast.EFunction: p.visitFn(&e.Fn, expr.Loc) @@ -9115,10 +9129,12 @@ func (p *parser) handleIdentifier(loc logger.Loc, assignTarget js_ast.AssignTarg ref := e.Ref // Capture the "arguments" variable if necessary - if p.fnOnlyDataVisit.isInsideAsyncArrowFn && p.UnsupportedFeatures.Has(compat.AsyncAwait) && - p.fnOnlyDataVisit.argumentsRef != nil && ref == *p.fnOnlyDataVisit.argumentsRef { - ref = p.captureArguments() - e.Ref = ref + if p.fnOnlyDataVisit.argumentsRef != nil && ref == *p.fnOnlyDataVisit.argumentsRef { + isInsideUnsupportedArrow := p.fnOrArrowDataVisit.isArrow && p.UnsupportedFeatures.Has(compat.Arrow) + isInsideUnsupportedAsyncArrow := p.fnOnlyDataVisit.isInsideAsyncArrowFn && p.UnsupportedFeatures.Has(compat.AsyncAwait) + if isInsideUnsupportedArrow || isInsideUnsupportedAsyncArrow { + return js_ast.Expr{Loc: loc, Data: &js_ast.EIdentifier{Ref: p.captureArguments()}} + } } if p.Mode == config.ModeBundle && assignTarget != js_ast.AssignTargetNone { diff --git a/internal/js_parser/js_parser_lower.go b/internal/js_parser/js_parser_lower.go index 6d2830d6d05..68a906e8a5c 100644 --- a/internal/js_parser/js_parser_lower.go +++ b/internal/js_parser/js_parser_lower.go @@ -71,9 +71,6 @@ func (p *parser) markSyntaxFeature(feature compat.Feature, r logger.Range) (didG case compat.Let: name = "let" - case compat.Arrow: - name = "arrow functions" - case compat.Class: name = "class syntax" diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 48248b6f20f..372b238b155 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -3222,15 +3222,10 @@ func TestES5(t *testing.T) { ": error: Transforming const to the configured target environment is not supported yet\n") expectParseErrorTarget(t, 5, "let x = 2;", ": error: Transforming let to the configured target environment is not supported yet\n") - expectParseErrorTarget(t, 5, "async => foo;", - ": error: Transforming arrow functions to the configured target environment is not supported yet\n") + expectPrintedTarget(t, 5, "async => foo;", "(function(async) {\n return foo;\n});\n") + expectPrintedTarget(t, 5, "x => x;", "(function(x) {\n return x;\n});\n") expectParseErrorTarget(t, 5, "async () => foo;", - ": error: Transforming async functions to the configured target environment is not supported yet\n"+ - ": error: Transforming arrow functions to the configured target environment is not supported yet\n") - expectParseErrorTarget(t, 5, "() => foo;", - ": error: Transforming arrow functions to the configured target environment is not supported yet\n") - expectParseErrorTarget(t, 5, "x => x;", - ": error: Transforming arrow functions to the configured target environment is not supported yet\n") + ": error: Transforming async functions to the configured target environment is not supported yet\n") expectParseErrorTarget(t, 5, "class Foo {}", ": error: Transforming class syntax to the configured target environment is not supported yet\n") expectParseErrorTarget(t, 5, "(class {});", diff --git a/internal/js_printer/js_printer.go b/internal/js_printer/js_printer.go index e4a4e8c1535..be781e8c1e8 100644 --- a/internal/js_printer/js_printer.go +++ b/internal/js_printer/js_printer.go @@ -936,7 +936,7 @@ func (p *printer) printFnArgs(args []js_ast.Arg, hasRestArg bool, isArrow bool) } func (p *printer) printFn(fn js_ast.Fn) { - p.printFnArgs(fn.Args, fn.HasRestArg, false) + p.printFnArgs(fn.Args, fn.HasRestArg, false /* isArrow */) p.printSpace() p.printBlock(fn.Body.Stmts) } @@ -1485,9 +1485,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) { } case *js_ast.EArrow: - n := len(p.js) - useFunction := p.options.UnsupportedFeatures.Has(compat.Arrow) - wrap := level >= js_ast.LAssign || (useFunction && (p.stmtStart == n || p.exportDefaultStart == n)) + wrap := level >= js_ast.LAssign if wrap { p.print("(") @@ -1498,21 +1496,13 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags int) { p.printSpace() } - if useFunction { - p.printSpaceBeforeIdentifier() - p.print("function") - } - - p.printFnArgs(e.Args, e.HasRestArg, !useFunction) + p.printFnArgs(e.Args, e.HasRestArg, true /* isArrow */) + p.printSpace() + p.print("=>") p.printSpace() - - if !useFunction { - p.print("=>") - p.printSpace() - } wasPrinted := false - if len(e.Body.Stmts) == 1 && e.PreferExpr && !useFunction { + if len(e.Body.Stmts) == 1 && e.PreferExpr { if s, ok := e.Body.Stmts[0].Data.(*js_ast.SReturn); ok && s.Value != nil { p.arrowExprStart = len(p.js) p.printExpr(*s.Value, js_ast.LComma, 0) diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index 89bce8be6ca..b9699e64b83 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -24,7 +24,9 @@ func expectPrintedCommon(t *testing.T, name string, contents string, expected st t.Run(name, func(t *testing.T) { t.Helper() log := logger.NewDeferLog() - tree, ok := js_parser.Parse(log, test.SourceForTest(contents), config.Options{}) + tree, ok := js_parser.Parse(log, test.SourceForTest(contents), config.Options{ + UnsupportedFeatures: options.UnsupportedFeatures, + }) msgs := log.Done() text := "" for _, msg := range msgs { diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go index fd4e09f7570..a99ebad9748 100644 --- a/internal/runtime/runtime.go +++ b/internal/runtime/runtime.go @@ -16,7 +16,7 @@ import ( const SourceIndex = uint32(0) func CanUseES6(unsupportedFeatures compat.Feature) bool { - return !unsupportedFeatures.Has(compat.Let) + return !unsupportedFeatures.Has(compat.Let) && !unsupportedFeatures.Has(compat.Arrow) } func code(isES6 bool) string {