From 90db88d6a080e18d832731319c2f915e47df72ec Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:15:40 -0600 Subject: [PATCH 01/22] Add new JSX options to API --- cmd/esbuild/main.go | 4 ++++ internal/config/config.go | 12 ++++++++---- lib/shared/common.ts | 6 ++++++ lib/shared/types.ts | 6 ++++++ pkg/api/api.go | 25 +++++++++++++++++++------ pkg/api/api_impl.go | 18 ++++++++++++------ pkg/cli/cli_impl.go | 37 +++++++++++++++++++++++++++++++++++++ 7 files changed, 92 insertions(+), 16 deletions(-) diff --git a/cmd/esbuild/main.go b/cmd/esbuild/main.go index ac9d85fd3b7..50e9c3a50e9 100644 --- a/cmd/esbuild/main.go +++ b/cmd/esbuild/main.go @@ -80,6 +80,10 @@ var helpText = func(colors logger.Colors) string { --jsx-factory=... What to use for JSX instead of React.createElement --jsx-fragment=... What to use for JSX instead of React.Fragment --jsx=... Set to "preserve" to disable transforming JSX to JS + --jsx-runtime=... Set to "automatic" to use the new JSX transform + --jsx-development Use the automatic runtime in development mode + --jsx-import-source=... Override the package name for the automatic runtime + (default "react") --keep-names Preserve "name" on functions and classes --legal-comments=... Where to place legal comments (none | inline | eof | linked | external, default eof when bundling diff --git a/internal/config/config.go b/internal/config/config.go index 28d7fe5ac38..ab5ca2821f2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,10 +13,14 @@ import ( ) type JSXOptions struct { - Factory DefineExpr - Fragment DefineExpr - Parse bool - Preserve bool + Factory DefineExpr + Fragment DefineExpr + Parse bool + Preserve bool + AutomaticRuntime bool + ImportSource string + Development bool +} } type TSOptions struct { diff --git a/lib/shared/common.ts b/lib/shared/common.ts index acf59266528..2a7c4d53b79 100644 --- a/lib/shared/common.ts +++ b/lib/shared/common.ts @@ -140,6 +140,9 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe let jsx = getFlag(options, keys, 'jsx', mustBeString); let jsxFactory = getFlag(options, keys, 'jsxFactory', mustBeString); let jsxFragment = getFlag(options, keys, 'jsxFragment', mustBeString); + let jsxRuntime = getFlag(options, keys, 'jsxRuntime', mustBeString); + let jsxImportSource = getFlag(options, keys, 'jsxImportSource', mustBeString); + let jsxDevelopment = getFlag(options, keys, 'jsxDevelopment', mustBeBoolean); let define = getFlag(options, keys, 'define', mustBeObject); let logOverride = getFlag(options, keys, 'logOverride', mustBeObject); let supported = getFlag(options, keys, 'supported', mustBeObject); @@ -171,6 +174,9 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe if (jsx) flags.push(`--jsx=${jsx}`); if (jsxFactory) flags.push(`--jsx-factory=${jsxFactory}`); if (jsxFragment) flags.push(`--jsx-fragment=${jsxFragment}`); + if (jsxRuntime) flags.push(`--jsx-runtime=${jsxRuntime}`); + if (jsxImportSource) flags.push(`--jsx-import-source=${jsxImportSource}`); + if (jsxDevelopment) flags.push(`--jsx-development`); if (define) { for (let key in define) { diff --git a/lib/shared/types.ts b/lib/shared/types.ts index 9f4fd8c5f56..d840deae87a 100644 --- a/lib/shared/types.ts +++ b/lib/shared/types.ts @@ -55,6 +55,12 @@ interface CommonOptions { jsxFactory?: string; /** Documentation: https://esbuild.github.io/api/#jsx-fragment */ jsxFragment?: string; + /** Documentation: https://esbuild.github.io/api/#jsx-runtime */ + jsxRuntime?: 'classic' | 'automatic'; + /** Documentation: https://esbuild.github.io/api/#jsx-import-source */ + jsxImportSource?: string; + /** Documentation: https://esbuild.github.io/api/#jsx-development */ + jsxDevelopment?: boolean; /** Documentation: https://esbuild.github.io/api/#define */ define?: { [key: string]: string }; diff --git a/pkg/api/api.go b/pkg/api/api.go index 9c281c2af05..8fd5d4e2413 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -112,6 +112,13 @@ const ( JSXModePreserve ) +type JSXRuntime uint8 + +const ( + JSXRuntimeClassic JSXRuntime = iota + JSXRuntimeAutomatic +) + type Target uint8 const ( @@ -288,9 +295,12 @@ type BuildOptions struct { IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments - JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx-mode - JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory - JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx-mode + JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory + JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXRuntime JSXRuntime // Documentation: https://esbuild.github.io/api/#jsx-runtime + JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source + JSXDevelopment bool // Documentation: https://esbuild.github.io/api/#jsx-development Define map[string]string // Documentation: https://esbuild.github.io/api/#define Pure []string // Documentation: https://esbuild.github.io/api/#pure @@ -407,9 +417,12 @@ type TransformOptions struct { IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments - JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx - JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory - JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx + JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory + JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXRuntime JSXRuntime // Documentation: https://esbuild.github.io/api/#jsx-runtime + JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source + JSXDevelopment bool // Documentation: https://esbuild.github.io/api/#jsx-development TsconfigRaw string // Documentation: https://esbuild.github.io/api/#tsconfig-raw Banner string // Documentation: https://esbuild.github.io/api/#banner diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index b282fd2edc1..0c9596acab7 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -913,9 +913,12 @@ func rebuildImpl( UnsupportedCSSFeatureOverridesMask: cssMask, OriginalTargetEnv: targetEnv, JSX: config.JSXOptions{ - Preserve: buildOpts.JSXMode == JSXModePreserve, - Factory: validateJSXExpr(log, buildOpts.JSXFactory, "factory"), - Fragment: validateJSXExpr(log, buildOpts.JSXFragment, "fragment"), + Preserve: buildOpts.JSXMode == JSXModePreserve, + Factory: validateJSXExpr(log, buildOpts.JSXFactory, "factory"), + Fragment: validateJSXExpr(log, buildOpts.JSXFragment, "fragment"), + AutomaticRuntime: buildOpts.JSXRuntime == JSXRuntimeAutomatic, + ImportSource: buildOpts.JSXImportSource, + Development: buildOpts.JSXDevelopment, }, Defines: defines, InjectedDefines: injectedDefines, @@ -1372,9 +1375,12 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult var unusedImportFlagsTS config.UnusedImportFlagsTS useDefineForClassFieldsTS := config.Unspecified jsx := config.JSXOptions{ - Preserve: transformOpts.JSXMode == JSXModePreserve, - Factory: validateJSXExpr(log, transformOpts.JSXFactory, "factory"), - Fragment: validateJSXExpr(log, transformOpts.JSXFragment, "fragment"), + Preserve: transformOpts.JSXMode == JSXModePreserve, + Factory: validateJSXExpr(log, transformOpts.JSXFactory, "factory"), + Fragment: validateJSXExpr(log, transformOpts.JSXFragment, "fragment"), + AutomaticRuntime: transformOpts.JSXRuntime == JSXRuntimeAutomatic, + Development: transformOpts.JSXDevelopment, + ImportSource: transformOpts.JSXImportSource, } // Settings from "tsconfig.json" override those diff --git a/pkg/cli/cli_impl.go b/pkg/cli/cli_impl.go index 98651a12474..7a574534cc6 100644 --- a/pkg/cli/cli_impl.go +++ b/pkg/cli/cli_impl.go @@ -638,6 +638,43 @@ func parseOptionsImpl( transformOpts.JSXFragment = value } + case strings.HasPrefix(arg, "--jsx-runtime="): + value := arg[len("--jsx-runtime="):] + var runtime api.JSXRuntime + switch value { + case "classic": + runtime = api.JSXRuntimeClassic + case "automatic": + runtime = api.JSXRuntimeAutomatic + default: + return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote( + fmt.Sprintf("Invalid value %q in %q", value, arg), + "Valid values are \"classic\" or \"automatic\".", + ) + } + if buildOpts != nil { + buildOpts.JSXRuntime = runtime + } else { + transformOpts.JSXRuntime = runtime + } + + case strings.HasPrefix(arg, "--jsx-import-source="): + value := arg[len("--jsx-import-source="):] + if buildOpts != nil { + buildOpts.JSXImportSource = value + } else { + transformOpts.JSXImportSource = value + } + + case isBoolFlag(arg, "--jsx-development"): + if value, err := parseBoolFlag(arg, true); err != nil { + return parseOptionsExtras{}, err + } else if buildOpts != nil { + buildOpts.JSXDevelopment = value + } else { + transformOpts.JSXDevelopment = value + } + case strings.HasPrefix(arg, "--banner=") && transformOpts != nil: transformOpts.Banner = arg[len("--banner="):] From 34a0667fa594859d58c80b3694b1c03d8a4842c7 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:21:20 -0600 Subject: [PATCH 02/22] Resolve applicable jsx options from tsconfig --- internal/bundler/bundler.go | 6 ++++++ internal/config/config.go | 25 +++++++++++++++++++++++++ internal/resolver/resolver.go | 8 ++++++-- internal/resolver/tsconfig_json.go | 27 +++++++++++++++++++++++++++ pkg/api/api_impl.go | 6 ++++++ 5 files changed, 70 insertions(+), 2 deletions(-) diff --git a/internal/bundler/bundler.go b/internal/bundler/bundler.go index 274cd73de40..f03405fa52d 100644 --- a/internal/bundler/bundler.go +++ b/internal/bundler/bundler.go @@ -1195,6 +1195,12 @@ func (s *scanner) maybeParseFile( if len(resolveResult.JSXFragment) > 0 { optionsClone.JSX.Fragment = config.DefineExpr{Parts: resolveResult.JSXFragment} } + if resolveResult.JSX != config.TSJSXNone { + optionsClone.JSX.SetOptionsFromTSJSX(resolveResult.JSX) + } + if resolveResult.JSXImportSource != "" { + optionsClone.JSX.ImportSource = resolveResult.JSXImportSource + } if resolveResult.UseDefineForClassFieldsTS != config.Unspecified { optionsClone.UseDefineForClassFields = resolveResult.UseDefineForClassFieldsTS } diff --git a/internal/config/config.go b/internal/config/config.go index ab5ca2821f2..2c028fe1265 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,31 @@ type JSXOptions struct { ImportSource string Development bool } + +type TSJSX uint8 + +const ( + TSJSXNone TSJSX = iota + TSJSXPreserve + TSJSXReact + TSJSXReactJSX + TSJSXReactJSXDev +) + +func (jsxOptions *JSXOptions) SetOptionsFromTSJSX(tsx TSJSX) { + switch tsx { + case TSJSXPreserve: + jsxOptions.Preserve = true + case TSJSXReact: + jsxOptions.AutomaticRuntime = false + jsxOptions.Development = false + case TSJSXReactJSX: + jsxOptions.AutomaticRuntime = true + // Don't set Development = false implicitly + case TSJSXReactJSXDev: + jsxOptions.AutomaticRuntime = true + jsxOptions.Development = true + } } type TSOptions struct { diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index cfbf9490f74..0c468f12566 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -102,8 +102,10 @@ type ResolveResult struct { PluginData interface{} // If not empty, these should override the default values - JSXFactory []string // Default if empty: "React.createElement" - JSXFragment []string // Default if empty: "React.Fragment" + JSXFactory []string // Default if empty: "React.createElement" + JSXFragment []string // Default if empty: "React.Fragment" + JSXImportSource string // Default if empty: "react" + JSX config.TSJSX DifferentCase *fs.DifferentCase @@ -627,6 +629,8 @@ func (r resolverQuery) finalizeResolve(result *ResolveResult) { } else { result.JSXFactory = dirInfo.enclosingTSConfigJSON.JSXFactory result.JSXFragment = dirInfo.enclosingTSConfigJSON.JSXFragmentFactory + result.JSX = dirInfo.enclosingTSConfigJSON.JSX + result.JSXImportSource = dirInfo.enclosingTSConfigJSON.JSXImportSource result.UseDefineForClassFieldsTS = dirInfo.enclosingTSConfigJSON.UseDefineForClassFields result.UnusedImportFlagsTS = config.UnusedImportFlagsFromTsconfigValues( dirInfo.enclosingTSConfigJSON.PreserveImportsNotUsedAsValues, diff --git a/internal/resolver/tsconfig_json.go b/internal/resolver/tsconfig_json.go index ff2292b0fb0..a47eadb1eb7 100644 --- a/internal/resolver/tsconfig_json.go +++ b/internal/resolver/tsconfig_json.go @@ -38,8 +38,10 @@ type TSConfigJSON struct { TSTarget *config.TSTarget TSStrict *config.TSAlwaysStrict TSAlwaysStrict *config.TSAlwaysStrict + JSX config.TSJSX JSXFactory []string JSXFragmentFactory []string + JSXImportSource string ModuleSuffixes []string UseDefineForClassFields config.MaybeBool PreserveImportsNotUsedAsValues bool @@ -105,6 +107,24 @@ func ParseTSConfigJSON( } } + // Parse "jsx" + if valueJSON, _, ok := getProperty(compilerOptionsJSON, "jsx"); ok { + if value, ok := getString(valueJSON); ok { + switch strings.ToLower(value) { + case "none": + result.JSX = config.TSJSXNone + case "preserve", "react-native": + result.JSX = config.TSJSXPreserve + case "react": + result.JSX = config.TSJSXReact + case "react-jsx": + result.JSX = config.TSJSXReactJSX + case "react-jsxdev": + result.JSX = config.TSJSXReactJSXDev + } + } + } + // Parse "jsxFactory" if valueJSON, _, ok := getProperty(compilerOptionsJSON, "jsxFactory"); ok { if value, ok := getString(valueJSON); ok { @@ -119,6 +139,13 @@ func ParseTSConfigJSON( } } + // Parse "jsxImportSource" + if valueJSON, _, ok := getProperty(compilerOptionsJSON, "jsxImportSource"); ok { + if value, ok := getString(valueJSON); ok { + result.JSXImportSource = value + } + } + // Parse "moduleSuffixes" if valueJSON, _, ok := getProperty(compilerOptionsJSON, "moduleSuffixes"); ok { if value, ok := valueJSON.Data.(*js_ast.EArray); ok { diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index 0c9596acab7..06f36653910 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -1394,12 +1394,18 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult Contents: transformOpts.TsconfigRaw, } if result := resolver.ParseTSConfigJSON(log, source, &caches.JSONCache, nil); result != nil { + if result.JSX != config.TSJSXNone { + jsx.SetOptionsFromTSJSX(result.JSX) + } if len(result.JSXFactory) > 0 { jsx.Factory = config.DefineExpr{Parts: result.JSXFactory} } if len(result.JSXFragmentFactory) > 0 { jsx.Fragment = config.DefineExpr{Parts: result.JSXFragmentFactory} } + if len(result.JSXImportSource) > 0 { + jsx.ImportSource = result.JSXImportSource + } if result.UseDefineForClassFields != config.Unspecified { useDefineForClassFieldsTS = result.UseDefineForClassFields } From 9d81ee74363b072ed08ebe14c4c338d296dc0e00 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:22:34 -0600 Subject: [PATCH 03/22] Default options, parse pragmas --- internal/js_lexer/js_lexer.go | 26 ++++++++++++++++++-------- internal/js_parser/js_parser.go | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/internal/js_lexer/js_lexer.go b/internal/js_lexer/js_lexer.go index f86fe0fd99e..3551188c575 100644 --- a/internal/js_lexer/js_lexer.go +++ b/internal/js_lexer/js_lexer.go @@ -245,14 +245,16 @@ type MaybeSubstring struct { } type Lexer struct { - CommentsToPreserveBefore []js_ast.Comment - AllOriginalComments []js_ast.Comment - Identifier MaybeSubstring - log logger.Log - source logger.Source - JSXFactoryPragmaComment logger.Span - JSXFragmentPragmaComment logger.Span - SourceMappingURL logger.Span + CommentsToPreserveBefore []js_ast.Comment + AllOriginalComments []js_ast.Comment + Identifier MaybeSubstring + log logger.Log + source logger.Source + JSXFactoryPragmaComment logger.Span + JSXFragmentPragmaComment logger.Span + JSXRuntimePragmaComment logger.Span + JSXImportSourcePragmaComment logger.Span + SourceMappingURL logger.Span // Escape sequences in string literals are decoded lazily because they are // not interpreted inside tagged templates, and tagged templates can contain @@ -2770,6 +2772,14 @@ func (lexer *Lexer) scanCommentText() { if arg, ok := scanForPragmaArg(pragmaSkipSpaceFirst, lexer.start+i+1, "jsxFrag", rest); ok { lexer.JSXFragmentPragmaComment = arg } + } else if hasPrefixWithWordBoundary(rest, "jsxRuntime") { + if arg, ok := scanForPragmaArg(pragmaSkipSpaceFirst, lexer.start+i+1, "jsxRuntime", rest); ok { + lexer.JSXRuntimePragmaComment = arg + } + } else if hasPrefixWithWordBoundary(rest, "jsxImportSource") { + if arg, ok := scanForPragmaArg(pragmaSkipSpaceFirst, lexer.start+i+1, "jsxImportSource", rest); ok { + lexer.JSXImportSourcePragmaComment = arg + } } else if i == 2 && strings.HasPrefix(rest, " sourceMappingURL=") { if arg, ok := scanForPragmaArg(pragmaNoSpaceFirst, lexer.start+i+1, " sourceMappingURL=", rest); ok { lexer.SourceMappingURL = arg diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index e7aa849b2f5..0a5052b6c58 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -15269,6 +15269,8 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio var defaultJSXFactory = []string{"React", "createElement"} var defaultJSXFragment = []string{"React", "Fragment"} +const defaultJSXImportSource = "react" + func Parse(log logger.Log, source logger.Source, options Options) (result js_ast.AST, ok bool) { ok = true defer func() { @@ -15287,6 +15289,9 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast if len(options.jsx.Fragment.Parts) == 0 && options.jsx.Fragment.Constant == nil { options.jsx.Fragment = config.DefineExpr{Parts: defaultJSXFragment} } + if len(options.jsx.ImportSource) == 0 { + options.jsx.ImportSource = defaultJSXImportSource + } if !options.ts.Parse { // Non-TypeScript files always get the real JavaScript class field behavior @@ -15656,8 +15661,21 @@ func (p *parser) prepareForVisitPass() { // Handle "@jsx" and "@jsxFrag" pragmas now that lexing is done if p.options.jsx.Parse { + if jsxRuntime := p.lexer.JSXRuntimePragmaComment; jsxRuntime.Text != "" { + if jsxRuntime.Text == "automatic" { + p.options.jsx.AutomaticRuntime = true + } else if jsxRuntime.Text == "classic" { + p.options.jsx.AutomaticRuntime = false + } else { + p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxRuntime.Range, + fmt.Sprintf("Invalid JSX runtime: %s", jsxRuntime.Text)) + } + } if jsxFactory := p.lexer.JSXFactoryPragmaComment; jsxFactory.Text != "" { - if expr, _ := ParseDefineExprOrJSON(jsxFactory.Text); len(expr.Parts) > 0 { + if p.options.jsx.AutomaticRuntime { + p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFactory.Range, + fmt.Sprintf("JSX factory cannot be set when runtime is automatic: %s", jsxFactory.Text)) + } else if expr, _ := ParseDefineExprOrJSON(jsxFactory.Text); len(expr.Parts) > 0 { p.options.jsx.Factory = expr } else { p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFactory.Range, @@ -15665,13 +15683,24 @@ func (p *parser) prepareForVisitPass() { } } if jsxFragment := p.lexer.JSXFragmentPragmaComment; jsxFragment.Text != "" { - if expr, _ := ParseDefineExprOrJSON(jsxFragment.Text); len(expr.Parts) > 0 || expr.Constant != nil { + if p.options.jsx.AutomaticRuntime { + p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFragment.Range, + fmt.Sprintf("JSX fragment cannot be set when runtime is automatic: %s", jsxFragment.Text)) + } else if expr, _ := ParseDefineExprOrJSON(jsxFragment.Text); len(expr.Parts) > 0 || expr.Constant != nil { p.options.jsx.Fragment = expr } else { p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFragment.Range, fmt.Sprintf("Invalid JSX fragment: %s", jsxFragment.Text)) } } + if jsxImportSource := p.lexer.JSXImportSourcePragmaComment; jsxImportSource.Text != "" { + if !p.options.jsx.AutomaticRuntime { + p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxImportSource.Range, + fmt.Sprintf("JSX import source cannot be set when runtime is classic: %s", jsxImportSource.Text)) + } else { + p.options.jsx.ImportSource = jsxImportSource.Text + } + } } } From 9e00cd760eba3b9bdd0ba3516735906d9d2b1c8e Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:25:01 -0600 Subject: [PATCH 04/22] Accept nil sourceIndex for external imports --- internal/js_parser/js_parser.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 0a5052b6c58..f47d81f0a95 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -15407,7 +15407,7 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast } } } - before = p.generateImportStmt(file.Source.KeyPath.Text, exportsNoConflict, file.Source.Index, before, symbols) + before = p.generateImportStmt(file.Source.KeyPath.Text, exportsNoConflict, &file.Source.Index, before, symbols) } // Bind symbols in a second pass over the AST. I started off doing this in a @@ -15807,7 +15807,7 @@ func (p *parser) computeCharacterFrequency() *js_ast.CharFreq { func (p *parser) generateImportStmt( path string, imports []string, - sourceIndex uint32, + sourceIndex *uint32, parts []js_ast.Part, symbols map[string]js_ast.Ref, ) []js_ast.Part { @@ -15816,7 +15816,9 @@ func (p *parser) generateImportStmt( declaredSymbols := make([]js_ast.DeclaredSymbol, len(imports)) clauseItems := make([]js_ast.ClauseItem, len(imports)) importRecordIndex := p.addImportRecord(ast.ImportStmt, logger.Loc{}, path, nil) - p.importRecords[importRecordIndex].SourceIndex = ast.MakeIndex32(sourceIndex) + if sourceIndex != nil { + p.importRecords[importRecordIndex].SourceIndex = ast.MakeIndex32(*sourceIndex) + } // Create per-import information for i, alias := range imports { @@ -15853,7 +15855,8 @@ func (p *parser) toAST(parts []js_ast.Part, hashbang string, directive string) j keys = append(keys, key) } sort.Strings(keys) - parts = p.generateImportStmt("", keys, runtime.SourceIndex, parts, p.runtimeImports) + sourceIndex := runtime.SourceIndex + parts = p.generateImportStmt("", keys, &sourceIndex, parts, p.runtimeImports) } // Handle import paths after the whole file has been visited because we need From 2a6495a953690957d378810f1d9fbcb882818e03 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:25:52 -0600 Subject: [PATCH 05/22] Parsing and import generation --- internal/js_parser/js_parser.go | 303 +++++++++++++++++++++++++++++--- 1 file changed, 278 insertions(+), 25 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index f47d81f0a95..b6e9f076cca 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -204,6 +204,11 @@ type parser struct { superCtorRef js_ast.Ref jsxDevRef js_ast.Ref + // Imports from "react/jsx-runtime" and "react", respectively. + // (Or whatever was specified in the "importSource" option) + jsxRuntimeImports map[string]js_ast.Ref + jsxLegacyImports map[string]js_ast.Ref + // For lowering private methods weakMapRef js_ast.Ref weakSetRef js_ast.Ref @@ -1515,6 +1520,55 @@ func (p *parser) callRuntime(loc logger.Loc, name string, args []js_ast.Expr) js }} } +type JSXImport uint8 + +const ( + JSXImportJSX JSXImport = iota + JSXImportJSXS + JSXImportFragment + JSXImportCreateElement +) + +func (p *parser) importJSXSymbol(loc logger.Loc, jsx JSXImport) js_ast.Expr { + var symbols map[string]js_ast.Ref + var name string + + switch jsx { + case JSXImportJSX: + symbols = p.jsxRuntimeImports + if p.options.jsx.Development { + name = "jsxDEV" + } else { + name = "jsx" + } + case JSXImportJSXS: + symbols = p.jsxRuntimeImports + if p.options.jsx.Development { + name = "jsxDEV" + } else { + name = "jsxs" + } + case JSXImportFragment: + symbols = p.jsxRuntimeImports + name = "Fragment" + case JSXImportCreateElement: + symbols = p.jsxLegacyImports + name = "createElement" + } + + ref, ok := symbols[name] + if !ok { + ref = p.newSymbol(js_ast.SymbolOther, name) + p.moduleScope.Generated = append(p.moduleScope.Generated, ref) + p.isImportItem[ref] = true + symbols[name] = ref + } + p.recordUsage(ref) + return p.handleIdentifier(loc, &js_ast.EIdentifier{Ref: ref}, identifierOpts{ + wasOriginallyIdentifier: true, + }) +} + func (p *parser) valueToSubstituteForRequire(loc logger.Loc) js_ast.Expr { if p.source.Index != runtime.SourceIndex && config.ShouldCallRuntimeRequire(p.options.mode, p.options.outputFormat) { @@ -11989,37 +12043,199 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO } else { // A missing tag is a fragment if e.TagOrNil.Data == nil { - e.TagOrNil = p.instantiateDefineExpr(expr.Loc, p.options.jsx.Fragment, identifierOpts{ - wasOriginallyIdentifier: true, - }) + if p.options.jsx.AutomaticRuntime { + e.TagOrNil = p.importJSXSymbol(expr.Loc, JSXImportFragment) + } else { + e.TagOrNil = p.instantiateDefineExpr(expr.Loc, p.options.jsx.Fragment, identifierOpts{ + wasOriginallyIdentifier: true, + }) + } } - // Arguments to createElement() - args := []js_ast.Expr{e.TagOrNil} - if len(e.Properties) > 0 { + shouldUseCreateElement := !p.options.jsx.AutomaticRuntime + if !shouldUseCreateElement { + // Even for runtime="automatic",
is special cased to createElement + // See https://github.com/babel/babel/blob/e482c763466ba3f44cb9e3467583b78b7f030b4a/packages/babel-plugin-transform-react-jsx/src/create-plugin.ts#L352 + seenPropsSpread := false + for _, property := range e.Properties { + if seenPropsSpread && property.Kind == js_ast.PropertyNormal { + if str, ok := property.Key.Data.(*js_ast.EString); ok && helpers.UTF16EqualsString(str.Value, "key") { + shouldUseCreateElement = true + break + } + } else if property.Kind == js_ast.PropertySpread { + seenPropsSpread = true + } + } + } + + if shouldUseCreateElement { + // Arguments to createElement() + args := []js_ast.Expr{e.TagOrNil} + if len(e.Properties) > 0 { + args = append(args, p.lowerObjectSpread(propsLoc, &js_ast.EObject{ + Properties: e.Properties, + })) + } else { + args = append(args, js_ast.Expr{Loc: propsLoc, Data: js_ast.ENullShared}) + } + if len(e.Children) > 0 { + args = append(args, e.Children...) + } + + // Call createElement() + var target js_ast.Expr + if p.options.jsx.AutomaticRuntime { + target = p.importJSXSymbol(expr.Loc, JSXImportCreateElement) + } else { + target = p.instantiateDefineExpr(expr.Loc, p.options.jsx.Factory, identifierOpts{ + wasOriginallyIdentifier: true, + }) + } + p.warnAboutImportNamespaceCall(target, exprKindCall) + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ + Target: target, + Args: args, + CloseParenLoc: e.CloseLoc, + + // Enable tree shaking + CanBeUnwrappedIfUnused: !p.options.ignoreDCEAnnotations, + }}, exprOut{} + } else { + // Arguments to jsx() + args := []js_ast.Expr{e.TagOrNil} + + // For jsx(), "key", "__source" (dev), and "__self" (dev) are passed in as + // separate arguments rather than in the args object. We go through the + // props and filter out these three keywords so we can pass them in + // as separate arguments later. + extracted := make(map[string]js_ast.Property, 3) + properties := make([]js_ast.Property, 0, len(e.Properties)+len(e.Children)) + + for _, property := range e.Properties { + if str, ok := property.Key.Data.(*js_ast.EString); ok { + key := helpers.UTF16ToString(str.Value) + switch key { + case "key": + if property.Flags.Has(js_ast.PropertyWasShorthand) { + r := js_lexer.RangeOfIdentifier(p.source, property.Loc) + p.log.AddErrorWithNotes(&p.tracker, r, + "Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.", + []logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, property.Loc), + fmt.Sprintf("The property %q was defined here:", key))}) + } + fallthrough + case "__source", "__self": + extracted[key] = property + continue + } + } + properties = append(properties, property) + } + + isStaticChildren := len(e.Children) > 1 + + // Children are passed in as an explicit prop + if len(e.Children) > 0 { + childrenLoc := e.Children[0].Loc + var childrenValue js_ast.Expr + + if len(e.Children) > 1 { + childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: e.Children}} + } else if len(e.Children) == 1 { + if _, ok := e.Children[0].Data.(*js_ast.ESpread); ok { + // Special case for spread children + childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: []js_ast.Expr{e.Children[0]}}} + // TypeScript considers spread children to be static + isStaticChildren = true + } else { + childrenValue = e.Children[0] + } + } + + properties = append(properties, js_ast.Property{ + Key: js_ast.Expr{ + Data: &js_ast.EString{Value: helpers.StringToUTF16("children")}, + Loc: childrenLoc, + }, + ValueOrNil: childrenValue, + Kind: js_ast.PropertyNormal, + Loc: childrenLoc, + }) + } + args = append(args, p.lowerObjectSpread(propsLoc, &js_ast.EObject{ - Properties: e.Properties, + Properties: properties, })) - } else { - args = append(args, js_ast.Expr{Loc: propsLoc, Data: js_ast.ENullShared}) - } - if len(e.Children) > 0 { - args = append(args, e.Children...) - } - // Call createElement() - target := p.instantiateDefineExpr(expr.Loc, p.options.jsx.Factory, identifierOpts{ - wasOriginallyIdentifier: true, - }) - p.warnAboutImportNamespaceCall(target, exprKindCall) - return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ - Target: target, - Args: args, - CloseParenLoc: e.CloseLoc, + if p.options.jsx.Development { + // "key" + if keyProperty, ok := extracted["key"]; ok { + args = append(args, keyProperty.ValueOrNil) + } else { + args = append(args, js_ast.Expr{Data: js_ast.EUndefinedShared}) + } - // Enable tree shaking - CanBeUnwrappedIfUnused: !p.options.ignoreDCEAnnotations, - }}, exprOut{} + // "isStaticChildren" + args = append(args, js_ast.Expr{Data: &js_ast.EBoolean{Value: isStaticChildren}}) + + // "__source" + if sourceProperty, ok := extracted["__source"]; ok { + args = append(args, sourceProperty.ValueOrNil) + } else { + // Resolving the location to a specific line and column could be expensive, but it + // only happens in development mode and is a documented tradeoff. + prettyLoc := p.tracker.MsgLocationOrNil(js_lexer.RangeOfIdentifier(p.source, expr.Loc)) + args = append(args, js_ast.Expr{Data: &js_ast.EObject{ + Properties: []js_ast.Property{ + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("fileName")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(p.source.PrettyPath)}}, + }, + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Line)}}, + }, + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Column)}}, + }, + }, + }}) + } + + // "__self" + if selfProperty, ok := extracted["__self"]; ok { + args = append(args, selfProperty.ValueOrNil) + } else if p.fnOrArrowDataParse.isThisDisallowed { + args = append(args, js_ast.Expr{Data: js_ast.EUndefinedShared}) + } else { + args = append(args, js_ast.Expr{Data: js_ast.EThisShared}) + } + } else if keyProperty, ok := extracted["key"]; ok { + // Production, "key" + args = append(args, keyProperty.ValueOrNil) + } + + jsx := JSXImportJSX + if isStaticChildren { + jsx = JSXImportJSXS + } + + target := p.importJSXSymbol(expr.Loc, jsx) + p.warnAboutImportNamespaceCall(target, exprKindCall) + return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ + Target: target, + Args: args, + CloseParenLoc: e.CloseLoc, + + // Enable tree shaking + CanBeUnwrappedIfUnused: !p.options.ignoreDCEAnnotations, + }}, exprOut{} + } } case *js_ast.ETemplate: @@ -15254,6 +15470,10 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio namedImports: make(map[js_ast.Ref]js_ast.NamedImport), namedExports: make(map[string]js_ast.NamedExport), + // For JSX runtime imports + jsxRuntimeImports: make(map[string]js_ast.Ref), + jsxLegacyImports: make(map[string]js_ast.Ref), + suppressWarningsAboutWeirdCode: helpers.IsInsideNodeModules(source.KeyPath.Text), } @@ -15859,6 +16079,39 @@ func (p *parser) toAST(parts []js_ast.Part, hashbang string, directive string) j parts = p.generateImportStmt("", keys, &sourceIndex, parts, p.runtimeImports) } + // Insert an import statement for any jsx runtime imports we generated + if len(p.jsxRuntimeImports) > 0 && !p.options.omitJSXRuntimeForTests { + // Sort the imports for determinism + keys := make([]string, 0, len(p.jsxRuntimeImports)) + for key := range p.jsxRuntimeImports { + keys = append(keys, key) + } + sort.Strings(keys) + + // Determine the runtime source and whether it's prod or dev + path := p.options.jsx.ImportSource + if p.options.jsx.Development { + path = path + "/jsx-dev-runtime" + } else { + path = path + "/jsx-runtime" + } + + parts = p.generateImportStmt(path, keys, nil, parts, p.jsxRuntimeImports) + } + + // Insert an import statement for any legacy jsx imports we generated (i.e., createElement) + if len(p.jsxLegacyImports) > 0 && !p.options.omitJSXRuntimeForTests { + // Sort the imports for determinism + keys := make([]string, 0, len(p.jsxLegacyImports)) + for key := range p.jsxLegacyImports { + keys = append(keys, key) + } + sort.Strings(keys) + + path := p.options.jsx.ImportSource + parts = p.generateImportStmt(path, keys, nil, parts, p.jsxLegacyImports) + } + // Handle import paths after the whole file has been visited because we need // symbol usage counts to be able to remove unused type-only imports in // TypeScript code. From 032c9e2dbbd5ecbae801b2e5691b86d88703f70a Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:26:12 -0600 Subject: [PATCH 06/22] tsconfig tests --- internal/bundler/bundler_default_test.go | 82 ++++++++++++++++++ internal/bundler/bundler_tsconfig_test.go | 85 +++++++++++++++++++ .../bundler/snapshots/snapshots_default.txt | 42 +++++++++ .../bundler/snapshots/snapshots_tsconfig.txt | 70 +++++++++++++++ scripts/js-api-tests.js | 31 +++++++ 5 files changed, 310 insertions(+) diff --git a/internal/bundler/bundler_default_test.go b/internal/bundler/bundler_default_test.go index 389d46c72b9..cb3cf260b3c 100644 --- a/internal/bundler/bundler_default_test.go +++ b/internal/bundler/bundler_default_test.go @@ -539,6 +539,88 @@ func TestJSXConstantFragments(t *testing.T) { }) } +func TestJSXAutomaticImportsCommonJS(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.jsx": ` + import {jsx, Fragment} from './custom-react' + console.log(
, <>) + `, + "/custom-react.js": ` + module.exports = {} + `, + }, + entryPaths: []string{"/entry.jsx"}, + options: config.Options{ + Mode: config.ModeBundle, + JSX: config.JSXOptions{ + AutomaticRuntime: true, + }, + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "react/jsx-runtime": true, + }}, + }, + AbsOutputFile: "/out.js", + }, + }) +} + +func TestJSXAutomaticImportsES6(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.jsx": ` + import {jsx, Fragment} from './custom-react' + console.log(
, <>) + `, + "/custom-react.js": ` + export function jsx() {} + export function Fragment() {} + `, + }, + entryPaths: []string{"/entry.jsx"}, + options: config.Options{ + Mode: config.ModeBundle, + JSX: config.JSXOptions{ + AutomaticRuntime: true, + }, + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "react/jsx-runtime": true, + }}, + }, + AbsOutputFile: "/out.js", + }, + }) +} + +func TestJSXAutomaticSyntaxInJS(t *testing.T) { + default_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/entry.js": ` + console.log(
) + `, + }, + entryPaths: []string{"/entry.js"}, + options: config.Options{ + Mode: config.ModeBundle, + JSX: config.JSXOptions{ + AutomaticRuntime: true, + }, + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "react/jsx-runtime": true, + }}, + }, + AbsOutputFile: "/out.js", + }, + expectedScanLog: `entry.js: ERROR: The JSX syntax extension is not currently enabled +NOTE: The esbuild loader for this file is currently set to "js" but it must be set to "jsx" to be able to parse JSX syntax. ` + + `You can use 'Loader: map[string]api.Loader{".js": api.LoaderJSX}' to do that. +`, + }) +} + func TestNodeModules(t *testing.T) { default_suite.expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/bundler/bundler_tsconfig_test.go b/internal/bundler/bundler_tsconfig_test.go index 8aa8c74a87a..5fd33baa7aa 100644 --- a/internal/bundler/bundler_tsconfig_test.go +++ b/internal/bundler/bundler_tsconfig_test.go @@ -601,6 +601,91 @@ func TestTsConfigNestedJSX(t *testing.T) { }) } +func TestTsConfigReactJSX(t *testing.T) { + tsconfig_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/entry.tsx": ` + console.log(<>
) + `, + "/Users/user/project/tsconfig.json": ` + { + "compilerOptions": { + "jsx": "react-jsx", + "jsxImportSource": "notreact" + } + } + `, + }, + entryPaths: []string{"/Users/user/project/entry.tsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/Users/user/project/out.js", + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "notreact/jsx-runtime": true, + }}, + }, + }, + }) +} + +func TestTsConfigReactJSXDev(t *testing.T) { + tsconfig_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/entry.tsx": ` + console.log(<>
) + `, + "/Users/user/project/tsconfig.json": ` + { + "compilerOptions": { + "jsx": "react-jsxdev" + } + } + `, + }, + entryPaths: []string{"/Users/user/project/entry.tsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/Users/user/project/out.js", + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "react/jsx-dev-runtime": true, + }}, + }, + }, + }) +} + +func TestTsConfigReactJSXWithDevInMainConfig(t *testing.T) { + tsconfig_suite.expectBundled(t, bundled{ + files: map[string]string{ + "/Users/user/project/entry.tsx": ` + console.log(<>
) + `, + "/Users/user/project/tsconfig.json": ` + { + "compilerOptions": { + "jsx": "react-jsx" + } + } + `, + }, + entryPaths: []string{"/Users/user/project/entry.tsx"}, + options: config.Options{ + Mode: config.ModeBundle, + AbsOutputFile: "/Users/user/project/out.js", + JSX: config.JSXOptions{ + Development: true, + }, + ExternalSettings: config.ExternalSettings{ + PreResolve: config.ExternalMatchers{Exact: map[string]bool{ + "react/jsx-dev-runtime": true, + }}, + }, + }, + }) +} + func TestTsconfigJsonBaseUrl(t *testing.T) { tsconfig_suite.expectBundled(t, bundled{ files: map[string]string{ diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 70fd4957342..69e58cd158e 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1376,6 +1376,48 @@ console.log(replace.test); console.log(collide); console.log(re_export); +================================================================================ +TestJSXAutomaticImportsCommonJS +---------- /out.js ---------- +// custom-react.js +var require_custom_react = __commonJS({ + "custom-react.js"(exports, module) { + module.exports = {}; + } +}); + +// entry.jsx +var import_custom_react = __toESM(require_custom_react()); +console.log(/* @__PURE__ */ jsx2("div", { + jsx: import_custom_react.jsx +}), /* @__PURE__ */ jsx2(Fragment2, { + children: /* @__PURE__ */ jsx2(import_custom_react.Fragment, {}) +})); +import { + Fragment as Fragment2, + jsx as jsx2 +} from "react/jsx-runtime"; + +================================================================================ +TestJSXAutomaticImportsES6 +---------- /out.js ---------- +// custom-react.js +function jsx() { +} +function Fragment() { +} + +// entry.jsx +console.log(/* @__PURE__ */ jsx2("div", { + jsx +}), /* @__PURE__ */ jsx2(Fragment2, { + children: /* @__PURE__ */ jsx2(Fragment, {}) +})); +import { + Fragment as Fragment2, + jsx as jsx2 +} from "react/jsx-runtime"; + ================================================================================ TestJSXConstantFragments ---------- /out.js ---------- diff --git a/internal/bundler/snapshots/snapshots_tsconfig.txt b/internal/bundler/snapshots/snapshots_tsconfig.txt index 3bf3e3b6f01..e125dbeb06c 100644 --- a/internal/bundler/snapshots/snapshots_tsconfig.txt +++ b/internal/bundler/snapshots/snapshots_tsconfig.txt @@ -314,6 +314,76 @@ function fib(input) { // Users/user/project/entry.ts console.log(fib(10)); +================================================================================ +TestTsConfigReactJSX +---------- /Users/user/project/out.js ---------- +// Users/user/project/entry.tsx +console.log(/* @__PURE__ */ jsxs(Fragment, { + children: [ + /* @__PURE__ */ jsx("div", {}), + /* @__PURE__ */ jsx("div", {}) + ] +})); +import { + Fragment, + jsx, + jsxs +} from "notreact/jsx-runtime"; + +================================================================================ +TestTsConfigReactJSXDev +---------- /Users/user/project/out.js ---------- +// Users/user/project/entry.tsx +console.log(/* @__PURE__ */ jsxDEV(Fragment, { + children: [ + /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 18 + }, this), + /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 24 + }, this) + ] +}, void 0, true, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 16 +}, this)); +import { + Fragment, + jsxDEV +} from "react/jsx-dev-runtime"; + +================================================================================ +TestTsConfigReactJSXWithDevInMainConfig +---------- /Users/user/project/out.js ---------- +// Users/user/project/entry.tsx +console.log(/* @__PURE__ */ jsxDEV(Fragment, { + children: [ + /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 18 + }, this), + /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 24 + }, this) + ] +}, void 0, true, { + fileName: "Users/user/project/entry.tsx", + lineNumber: 2, + columnNumber: 16 +}, this)); +import { + Fragment, + jsxDEV +} from "react/jsx-dev-runtime"; + ================================================================================ TestTsConfigWithStatementAlwaysStrictFalse ---------- /Users/user/project/out.js ---------- diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index d8e1b817a92..fc1782a936c 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -3489,6 +3489,37 @@ let transformTests = { loader: 'jsx', }) assert.strictEqual(code2, `/* @__PURE__ */ factory(fragment, null, /* @__PURE__ */ factory("div", null));\n`) + + const { code: code3 } = await esbuild.transform(`<>
`, { + tsconfigRaw: { + compilerOptions: { + jsx: 'react-jsx' + }, + }, + loader: 'jsx', + }) + assert.strictEqual(code3, `/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ jsx("div", {})\n});\nimport {\n Fragment,\n jsx\n} from "react/jsx-runtime";\n`) + + const { code: code4 } = await esbuild.transform(`<>
`, { + tsconfigRaw: { + compilerOptions: { + jsx: 'react-jsx', + jsxImportSource: 'notreact' + }, + }, + loader: 'jsx', + }) + assert.strictEqual(code4, `/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ jsx("div", {})\n});\nimport {\n Fragment,\n jsx\n} from "notreact/jsx-runtime";\n`) + + const { code: code5 } = await esbuild.transform(`<>
`, { + tsconfigRaw: { + compilerOptions: { + jsx: 'react-jsxdev' + }, + }, + loader: 'jsx', + }) + assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 2\n }, this)\n}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) }, // Note: tree shaking is disabled when the output format isn't IIFE From fe6007669e31f3e9f277be54d18e4ad392b1379f Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:26:39 -0600 Subject: [PATCH 07/22] JS API tests --- scripts/js-api-tests.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index fc1782a936c..1dcdb4402fa 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -3877,6 +3877,21 @@ let transformTests = { assert.strictEqual(code, `console.log(
);\n`) }, + async jsxRuntimeAutomatic({ esbuild }) { + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic' }) + assert.strictEqual(code, `console.log(/* @__PURE__ */ jsx("div", {}));\nimport {\n jsx\n} from "react/jsx-runtime";\n`) + }, + + async jsxDevelopment({ esbuild }) { + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxDevelopment: true }) + assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 12\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) + }, + + async jsxImportSource({ esbuild }) { + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxImportSource: 'notreact' }) + assert.strictEqual(code, `console.log(/* @__PURE__ */ jsx("div", {}));\nimport {\n jsx\n} from "notreact/jsx-runtime";\n`) + }, + async ts({ esbuild }) { const { code } = await esbuild.transform(`enum Foo { FOO }`, { loader: 'ts' }) assert.strictEqual(code, `var Foo = /* @__PURE__ */ ((Foo2) => {\n Foo2[Foo2["FOO"] = 0] = "FOO";\n return Foo2;\n})(Foo || {});\n`) From 8e4a46cbacfb65b2b4eac583f7d9ef823cb242c8 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Thu, 23 Jun 2022 22:26:55 -0600 Subject: [PATCH 08/22] Parser tests --- internal/config/config.go | 1 + internal/js_parser/js_parser.go | 2 + internal/js_parser/js_parser_test.go | 112 ++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 2c028fe1265..043313fd970 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -305,6 +305,7 @@ type Options struct { WriteToStdout bool OmitRuntimeForTests bool + OmitJSXRuntimeForTests bool UnusedImportFlagsTS UnusedImportFlagsTS UseDefineForClassFields MaybeBool ASCIIOnly bool diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index b6e9f076cca..22372888b57 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -401,6 +401,7 @@ type optionsThatSupportStructuralEquality struct { minifySyntax bool minifyIdentifiers bool omitRuntimeForTests bool + omitJSXRuntimeForTests bool ignoreDCEAnnotations bool treeShaking bool dropDebugger bool @@ -435,6 +436,7 @@ func OptionsFromConfig(options *config.Options) Options { minifySyntax: options.MinifySyntax, minifyIdentifiers: options.MinifyIdentifiers, omitRuntimeForTests: options.OmitRuntimeForTests, + omitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, ignoreDCEAnnotations: options.IgnoreDCEAnnotations, treeShaking: options.TreeShaking, dropDebugger: options.DropDebugger, diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 975a7a5ee86..21376aa8464 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -149,26 +149,34 @@ func expectPrintedJSX(t *testing.T, contents string, expected string) { }) } -func expectParseErrorTargetJSX(t *testing.T, esVersion int, contents string, expected string) { +type JSXAutomaticTestOptions struct { + Development bool + ImportSource string + OmitJSXRuntimeForTests bool +} + +func expectParseErrorJSXAutomatic(t *testing.T, contents string, expected string, options JSXAutomaticTestOptions) { t.Helper() expectParseErrorCommon(t, contents, expected, config.Options{ - UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{ - compat.ES: {esVersion}, - }), + OmitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, JSX: config.JSXOptions{ - Parse: true, + AutomaticRuntime: true, + Parse: true, + Development: options.Development, + ImportSource: options.ImportSource, }, }) } -func expectPrintedTargetJSX(t *testing.T, esVersion int, contents string, expected string) { +func expectPrintedJSXAutomatic(t *testing.T, contents string, expected string, options JSXAutomaticTestOptions) { t.Helper() expectPrintedCommon(t, contents, expected, config.Options{ - UnsupportedJSFeatures: compat.UnsupportedJSFeatures(map[compat.Engine][]int{ - compat.ES: {esVersion}, - }), + OmitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, JSX: config.JSXOptions{ - Parse: true, + AutomaticRuntime: true, + Parse: true, + Development: options.Development, + ImportSource: options.ImportSource, }, }) } @@ -4718,6 +4726,90 @@ func TestJSXPragmas(t *testing.T) { expectPrintedJSX(t, "/* @jsxFrag a.b.c */\n<>", "/* @__PURE__ */ React.createElement(a.b.c, null);\n") } +func TestJSXAutomatic(t *testing.T) { + // Prod, without imports + prodOpts := JSXAutomaticTestOptions{Development: false, OmitJSXRuntimeForTests: true} + expectPrintedJSXAutomatic(t, "
>
", "/* @__PURE__ */ jsx(\"div\", {\n children: \">\"\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "
{1}}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {}, true);\n", prodOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {}, \"key\");\n", prodOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {\n ...props\n}, \"key\");\n", prodOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n", prodOpts) // Falls back to createElement + expectPrintedJSXAutomatic(t, "
{...children}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children\n ]\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "
{...children}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsx(\"a\", {})\n ]\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "<>>", "/* @__PURE__ */ jsx(Fragment, {\n children: \">\"\n});\n", prodOpts) + + // Prod, with imports + prodImportOpts := JSXAutomaticTestOptions{Development: false} + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n", prodImportOpts) + expectPrintedJSXAutomatic(t, "<>", "/* @__PURE__ */ jsxs(Fragment, {\n children: [\n /* @__PURE__ */ jsx(\"a\", {}),\n /* @__PURE__ */ jsx(\"b\", {})\n ]\n});\nimport {\n Fragment,\n jsx,\n jsxs\n} from \"react/jsx-runtime\";\n", prodImportOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"react\";\n", prodImportOpts) + expectPrintedJSXAutomatic(t, "<>
", "/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n })\n});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\nimport {\n createElement\n} from \"react\";\n", prodImportOpts) + + prodImportSourceOpts := JSXAutomaticTestOptions{Development: false, ImportSource: "my-jsx-lib"} + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"my-jsx-lib/jsx-runtime\";\n", prodImportSourceOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"my-jsx-lib\";\n", prodImportSourceOpts) + + expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", prodOpts) + + // Dev, without imports + devOpts := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} + expectPrintedJSXAutomatic(t, "
>
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
{1}}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n", devOpts) // Falls back to createElement + expectPrintedJSXAutomatic(t, "
{...children}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + expectPrintedJSXAutomatic(t, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) + + // Dev, with imports + devImportOpts := JSXAutomaticTestOptions{Development: true} + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n", devImportOpts) + expectPrintedJSXAutomatic(t, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n", devImportOpts) + + devImportSourceOpts := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"} + expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n", devImportSourceOpts) + expectPrintedJSXAutomatic(t, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n", devImportSourceOpts) + + expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", devOpts) +} + +func TestJSXAutomaticPragmas(t *testing.T) { + expectPrintedJSX(t, "// @jsxRuntime automatic\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "/* @jsxRuntime automatic */\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "\n/* @jsxRuntime automatic */", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + + expectPrintedJSX(t, "// @jsxRuntime classic\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + expectPrintedJSX(t, "/*@jsxRuntime classic*/\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + expectPrintedJSX(t, "/* @jsxRuntime classic */\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + expectPrintedJSX(t, "\n/*@jsxRuntime classic*/\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + expectPrintedJSX(t, "\n/* @jsxRuntime classic */\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + + expectParseErrorJSX(t, "// @jsxRuntime foo\n", ": WARNING: Invalid JSX runtime: foo\n") + + expectPrintedJSX(t, "// @jsxRuntime automatic @jsxImportSource src\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic @jsxImportSource src*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic*//*@jsxImportSource src*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "/* @jsxRuntime automatic */\n/* @jsxImportSource src */\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic @jsxImportSource src*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/\n/*@jsxImportSource src*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "\n/* @jsxRuntime automatic */\n/* @jsxImportSource src */", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + + expectPrintedJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") + expectParseErrorJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") + expectParseErrorJSX(t, "// @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") + + expectPrintedJSX(t, "// @jsxRuntime automatic @jsx h\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectParseErrorJSX(t, "// @jsxRuntime automatic @jsx h\n", ": WARNING: JSX factory cannot be set when runtime is automatic: h\n") + + expectPrintedJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", "/* @__PURE__ */ jsx(Fragment, {});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\n") + expectParseErrorJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", ": WARNING: JSX fragment cannot be set when runtime is automatic: f\n") +} + func TestPreserveOptionalChainParentheses(t *testing.T) { expectPrinted(t, "a?.b.c", "a?.b.c;\n") expectPrinted(t, "(a?.b).c", "(a?.b).c;\n") From ff5ec74eac741ae16ca4fe7f87ea5573bb928427 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Fri, 24 Jun 2022 14:52:54 -0600 Subject: [PATCH 09/22] Add sourcemap tests --- scripts/verify-source-map.js | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/scripts/verify-source-map.js b/scripts/verify-source-map.js index e0bf6845e68..6fb782a6b6e 100644 --- a/scripts/verify-source-map.js +++ b/scripts/verify-source-map.js @@ -320,6 +320,33 @@ const testCaseBundleCSS = { `, } +const testCaseJSXRuntime = { + 'entry.jsx': ` + import { A0, A1, A2 } from './a.jsx'; + console.log() + `, + 'a.jsx': ` + import {jsx} from './b-dir/b' + import {Fragment} from './b-dir/c-dir/c' + export function A0() { return <>a0 } + export function A1() { return
a1
} + export function A2() { return
} + `, + 'b-dir/b.js': ` + export const jsx = {id: 'jsx'} + `, + 'b-dir/c-dir/c.jsx': ` + exports.Fragment = function() { return <> } + `, +} + +const toSearchJSXRuntime = { + A0: 'a.jsx', + A1: 'a.jsx', + A2: 'a.jsx', + jsx: 'b-dir/b.js', +} + async function check(kind, testCase, toSearch, { ext, flags, entryPoints, crlf, followUpFlags = [] }) { let failed = 0 @@ -600,6 +627,18 @@ async function main() { entryPoints: ['entry.css'], crlf, }), + check('jsx-runtime' + suffix, testCaseJSXRuntime, toSearchJSXRuntime, { + ext: 'js', + flags: flags.concat('--outfile=out.js', '--bundle', '--jsx-runtime=automatic', '--external:react/jsx-runtime'), + entryPoints: ['entry.jsx'], + crlf, + }), + check('jsx-dev-runtime' + suffix, testCaseJSXRuntime, toSearchJSXRuntime, { + ext: 'js', + flags: flags.concat('--outfile=out.js', '--bundle', '--jsx-runtime=automatic', '--jsx-development', '--external:react/jsx-dev-runtime'), + entryPoints: ['entry.jsx'], + crlf, + }), ) } } From 7934001870e232b11c56c4225c2beaaf35323e41 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sat, 25 Jun 2022 12:58:12 -0600 Subject: [PATCH 10/22] Fix handling of __self and __source --- internal/js_parser/js_parser.go | 104 +++++++++++++-------------- internal/js_parser/js_parser_test.go | 4 ++ 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 22372888b57..3437010f093 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -12107,28 +12107,37 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // Arguments to jsx() args := []js_ast.Expr{e.TagOrNil} - // For jsx(), "key", "__source" (dev), and "__self" (dev) are passed in as - // separate arguments rather than in the args object. We go through the - // props and filter out these three keywords so we can pass them in - // as separate arguments later. - extracted := make(map[string]js_ast.Property, 3) - properties := make([]js_ast.Property, 0, len(e.Properties)+len(e.Children)) - + // Props argument + properties := make([]js_ast.Property, 0, len(e.Properties)+1) + + // For jsx(), "key" is passed in as a separate argument, so filter it out + // from the props here. Also, check for __source and __self, which might have + // been added by some upstream plugin. Their presence here would represent a + // configuration error. + hasKey := false + keyProperty := js_ast.Expr{Data: js_ast.EUndefinedShared} for _, property := range e.Properties { if str, ok := property.Key.Data.(*js_ast.EString); ok { - key := helpers.UTF16ToString(str.Value) - switch key { + propName := helpers.UTF16ToString(str.Value) + switch propName { case "key": if property.Flags.Has(js_ast.PropertyWasShorthand) { r := js_lexer.RangeOfIdentifier(p.source, property.Loc) p.log.AddErrorWithNotes(&p.tracker, r, "Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.", []logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, property.Loc), - fmt.Sprintf("The property %q was defined here:", key))}) + fmt.Sprintf("The property %q was defined here:", propName))}) + } else { + keyProperty = property.ValueOrNil + hasKey = true } - fallthrough + continue case "__source", "__self": - extracted[key] = property + r := js_lexer.RangeOfIdentifier(p.source, property.Loc) + p.log.AddErrorWithNotes(&p.tracker, r, + fmt.Sprintf("Duplicate \"%s\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.", propName), + []logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, property.Loc), + fmt.Sprintf("The property %q was defined here:", propName))}) continue } } @@ -12146,9 +12155,11 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: e.Children}} } else if len(e.Children) == 1 { if _, ok := e.Children[0].Data.(*js_ast.ESpread); ok { - // Special case for spread children + // TypeScript considers spread children to be static, but Babel considers + // it to be an error ("Spread children are not supported in React."). + // We'll follow TypeScript's behavior here because spread children may be + // valid with non-React source runtimes. childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: []js_ast.Expr{e.Children[0]}}} - // TypeScript considers spread children to be static isStaticChildren = true } else { childrenValue = e.Children[0] @@ -12170,56 +12181,45 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO Properties: properties, })) - if p.options.jsx.Development { - // "key" - if keyProperty, ok := extracted["key"]; ok { - args = append(args, keyProperty.ValueOrNil) - } else { - args = append(args, js_ast.Expr{Data: js_ast.EUndefinedShared}) - } + // "key" + if hasKey || p.options.jsx.Development { + args = append(args, keyProperty) + } + if p.options.jsx.Development { // "isStaticChildren" args = append(args, js_ast.Expr{Data: &js_ast.EBoolean{Value: isStaticChildren}}) // "__source" - if sourceProperty, ok := extracted["__source"]; ok { - args = append(args, sourceProperty.ValueOrNil) - } else { - // Resolving the location to a specific line and column could be expensive, but it - // only happens in development mode and is a documented tradeoff. - prettyLoc := p.tracker.MsgLocationOrNil(js_lexer.RangeOfIdentifier(p.source, expr.Loc)) - args = append(args, js_ast.Expr{Data: &js_ast.EObject{ - Properties: []js_ast.Property{ - { - Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("fileName")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(p.source.PrettyPath)}}, - }, - { - Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Line)}}, - }, - { - Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Column)}}, - }, + // Resolving the location to a specific line and column could be expensive, but it + // only happens in development mode and is a documented tradeoff. + prettyLoc := p.tracker.MsgLocationOrNil(js_lexer.RangeOfIdentifier(p.source, expr.Loc)) + args = append(args, js_ast.Expr{Data: &js_ast.EObject{ + Properties: []js_ast.Property{ + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("fileName")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(p.source.PrettyPath)}}, }, - }}) - } + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Line)}}, + }, + { + Kind: js_ast.PropertyNormal, + Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Column)}}, + }, + }, + }}) // "__self" - if selfProperty, ok := extracted["__self"]; ok { - args = append(args, selfProperty.ValueOrNil) - } else if p.fnOrArrowDataParse.isThisDisallowed { + if p.fnOrArrowDataParse.isThisDisallowed { args = append(args, js_ast.Expr{Data: js_ast.EUndefinedShared}) } else { args = append(args, js_ast.Expr{Data: js_ast.EThisShared}) } - } else if keyProperty, ok := extracted["key"]; ok { - // Production, "key" - args = append(args, keyProperty.ValueOrNil) } jsx := JSXImportJSX diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 21376aa8464..d18954eab20 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4751,6 +4751,8 @@ func TestJSXAutomatic(t *testing.T) { expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"my-jsx-lib\";\n", prodImportSourceOpts) expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", prodOpts) + expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n", prodOpts) + expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n", prodOpts) // Dev, without imports devOpts := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} @@ -4774,6 +4776,8 @@ func TestJSXAutomatic(t *testing.T) { expectPrintedJSXAutomatic(t, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n", devImportSourceOpts) expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", devOpts) + expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n", devOpts) + expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n", devOpts) } func TestJSXAutomaticPragmas(t *testing.T) { From 3676e5b0b4deb055d22f42dc7f4b40615e847003 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sat, 25 Jun 2022 13:09:18 -0600 Subject: [PATCH 11/22] Add namespace tests --- internal/js_parser/js_parser_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index d18954eab20..04c88310140 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4778,6 +4778,26 @@ func TestJSXAutomatic(t *testing.T) { expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", devOpts) expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n", devOpts) expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n", devOpts) + + // JSX namespaced names + for _, colon := range []string{":", " :", ": ", " : "} { + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a:b\", {});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-b:c-d\", {});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-:b-\", {});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"Te:st\", {});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": true\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": true\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": true\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": true\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": 0\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": 0\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": 0\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": 0\n});\n", prodOpts) + expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-b\", {\n \"a-b\": a - b\n});\n", prodOpts) + expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n", prodOpts) + expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected \">\" but found \":\"\n", prodOpts) + expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n", prodOpts) + } } func TestJSXAutomaticPragmas(t *testing.T) { From 428cdb1931ddb0206634ba7973d490f380b79e47 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sat, 25 Jun 2022 13:24:09 -0600 Subject: [PATCH 12/22] Clean up tests --- internal/js_parser/js_parser_test.go | 138 +++++++++++++-------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 04c88310140..b382aed49a9 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -155,7 +155,7 @@ type JSXAutomaticTestOptions struct { OmitJSXRuntimeForTests bool } -func expectParseErrorJSXAutomatic(t *testing.T, contents string, expected string, options JSXAutomaticTestOptions) { +func expectParseErrorJSXAutomatic(t *testing.T, options JSXAutomaticTestOptions, contents string, expected string) { t.Helper() expectParseErrorCommon(t, contents, expected, config.Options{ OmitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, @@ -168,7 +168,7 @@ func expectParseErrorJSXAutomatic(t *testing.T, contents string, expected string }) } -func expectPrintedJSXAutomatic(t *testing.T, contents string, expected string, options JSXAutomaticTestOptions) { +func expectPrintedJSXAutomatic(t *testing.T, options JSXAutomaticTestOptions, contents string, expected string) { t.Helper() expectPrintedCommon(t, contents, expected, config.Options{ OmitJSXRuntimeForTests: options.OmitJSXRuntimeForTests, @@ -4727,76 +4727,76 @@ func TestJSXPragmas(t *testing.T) { } func TestJSXAutomatic(t *testing.T) { - // Prod, without imports - prodOpts := JSXAutomaticTestOptions{Development: false, OmitJSXRuntimeForTests: true} - expectPrintedJSXAutomatic(t, "
>
", "/* @__PURE__ */ jsx(\"div\", {\n children: \">\"\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "
{1}}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {}, true);\n", prodOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {}, \"key\");\n", prodOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {\n ...props\n}, \"key\");\n", prodOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n", prodOpts) // Falls back to createElement - expectPrintedJSXAutomatic(t, "
{...children}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children\n ]\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsx(\"a\", {})\n ]\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "<>>", "/* @__PURE__ */ jsx(Fragment, {\n children: \">\"\n});\n", prodOpts) - - // Prod, with imports - prodImportOpts := JSXAutomaticTestOptions{Development: false} - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n", prodImportOpts) - expectPrintedJSXAutomatic(t, "<>", "/* @__PURE__ */ jsxs(Fragment, {\n children: [\n /* @__PURE__ */ jsx(\"a\", {}),\n /* @__PURE__ */ jsx(\"b\", {})\n ]\n});\nimport {\n Fragment,\n jsx,\n jsxs\n} from \"react/jsx-runtime\";\n", prodImportOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"react\";\n", prodImportOpts) - expectPrintedJSXAutomatic(t, "<>
", "/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n })\n});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\nimport {\n createElement\n} from \"react\";\n", prodImportOpts) - - prodImportSourceOpts := JSXAutomaticTestOptions{Development: false, ImportSource: "my-jsx-lib"} - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"my-jsx-lib/jsx-runtime\";\n", prodImportSourceOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"my-jsx-lib\";\n", prodImportSourceOpts) - - expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", prodOpts) - expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n", prodOpts) - expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n", prodOpts) - - // Dev, without imports - devOpts := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} - expectPrintedJSXAutomatic(t, "
>
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
{1}}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n", devOpts) // Falls back to createElement - expectPrintedJSXAutomatic(t, "
{...children}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - expectPrintedJSXAutomatic(t, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n", devOpts) - - // Dev, with imports - devImportOpts := JSXAutomaticTestOptions{Development: true} - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n", devImportOpts) - expectPrintedJSXAutomatic(t, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n", devImportOpts) - - devImportSourceOpts := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"} - expectPrintedJSXAutomatic(t, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n", devImportSourceOpts) - expectPrintedJSXAutomatic(t, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n", devImportSourceOpts) - - expectParseErrorJSXAutomatic(t, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n", devOpts) - expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n", devOpts) - expectParseErrorJSXAutomatic(t, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n", devOpts) + // Prod, without runtime imports + p := JSXAutomaticTestOptions{Development: false, OmitJSXRuntimeForTests: true} + expectPrintedJSXAutomatic(t, p, "
>
", "/* @__PURE__ */ jsx(\"div\", {\n children: \">\"\n});\n") + expectPrintedJSXAutomatic(t, p, "
{1}}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n});\n") + expectPrintedJSXAutomatic(t, p, "
", "/* @__PURE__ */ jsx(\"div\", {}, true);\n") + expectPrintedJSXAutomatic(t, p, "
", "/* @__PURE__ */ jsx(\"div\", {}, \"key\");\n") + expectPrintedJSXAutomatic(t, p, "
", "/* @__PURE__ */ jsx(\"div\", {\n ...props\n}, \"key\");\n") + expectPrintedJSXAutomatic(t, p, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") // Falls back to createElement + expectPrintedJSXAutomatic(t, p, "
{...children}
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children\n ]\n});\n") + expectPrintedJSXAutomatic(t, p, "
", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsx(\"a\", {})\n ]\n});\n") + expectPrintedJSXAutomatic(t, p, "<>>", "/* @__PURE__ */ jsx(Fragment, {\n children: \">\"\n});\n") + + expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n") + expectParseErrorJSXAutomatic(t, p, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n") + expectParseErrorJSXAutomatic(t, p, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n") + + // Prod, with runtime imports + pr := JSXAutomaticTestOptions{Development: false} + expectPrintedJSXAutomatic(t, pr, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSXAutomatic(t, pr, "<>", "/* @__PURE__ */ jsxs(Fragment, {\n children: [\n /* @__PURE__ */ jsx(\"a\", {}),\n /* @__PURE__ */ jsx(\"b\", {})\n ]\n});\nimport {\n Fragment,\n jsx,\n jsxs\n} from \"react/jsx-runtime\";\n") + expectPrintedJSXAutomatic(t, pr, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"react\";\n") + expectPrintedJSXAutomatic(t, pr, "<>
", "/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n })\n});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\nimport {\n createElement\n} from \"react\";\n") + + pri := JSXAutomaticTestOptions{Development: false, ImportSource: "my-jsx-lib"} + expectPrintedJSXAutomatic(t, pri, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"my-jsx-lib/jsx-runtime\";\n") + expectPrintedJSXAutomatic(t, pri, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"my-jsx-lib\";\n") + + // Dev, without runtime imports + d := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} + expectPrintedJSXAutomatic(t, d, "
>
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
{1}}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") // Falls back to createElement + expectPrintedJSXAutomatic(t, d, "
{...children}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
\n {...children}\n
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + + expectParseErrorJSXAutomatic(t, d, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n") + expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n") + expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n") + + // Dev, with runtime imports + dr := JSXAutomaticTestOptions{Development: true} + expectPrintedJSXAutomatic(t, dr, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dr, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") + + dri := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"} + expectPrintedJSXAutomatic(t, dri, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dri, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") // JSX namespaced names for _, colon := range []string{":", " :", ": ", " : "} { - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a:b\", {});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-b:c-d\", {});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-:b-\", {});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"Te:st\", {});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": true\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": true\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": true\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": true\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": 0\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": 0\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": 0\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": 0\n});\n", prodOpts) - expectPrintedJSXAutomatic(t, "", "/* @__PURE__ */ jsx(\"a-b\", {\n \"a-b\": a - b\n});\n", prodOpts) - expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n", prodOpts) - expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected \">\" but found \":\"\n", prodOpts) - expectParseErrorJSXAutomatic(t, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n", prodOpts) + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"a:b\", {});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"a-b:c-d\", {});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"a-:b-\", {});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"Te:st\", {});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": true\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": true\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": true\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": true\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a:b\": 0\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-b:c-d\": 0\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"a-:b-\": 0\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"x\", {\n \"Te:st\": 0\n});\n") + expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsx(\"a-b\", {\n \"a-b\": a - b\n});\n") + expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n") + expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Expected \">\" but found \":\"\n") + expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n") } } From add10ea4348e33b6b58a95c42c4e2d3edc1f6544 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sat, 25 Jun 2022 13:24:20 -0600 Subject: [PATCH 13/22] Remove unused JSX consts --- internal/js_parser/js_parser.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 3437010f093..a852f07b484 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -15741,13 +15741,6 @@ func LazyExportAST(log logger.Log, source logger.Source, options Options, expr j return ast } -type JSXExprKind uint8 - -const ( - JSXFactory JSXExprKind = iota - JSXFragment -) - func ParseDefineExprOrJSON(text string) (config.DefineExpr, js_ast.E) { if text == "" { return config.DefineExpr{}, nil From 1f9ddc97c11a49c2e906f6d0df109cd5ab7c04f3 Mon Sep 17 00:00:00 2001 From: John Gozde Date: Sat, 23 Jul 2022 09:21:19 -0600 Subject: [PATCH 14/22] Set matchAgainstDefines: true for imported JSX symbols --- internal/js_parser/js_parser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 522bead9e4f..c278c9e0c3f 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -1568,6 +1568,7 @@ func (p *parser) importJSXSymbol(loc logger.Loc, jsx JSXImport) js_ast.Expr { p.recordUsage(ref) return p.handleIdentifier(loc, &js_ast.EIdentifier{Ref: ref}, identifierOpts{ wasOriginallyIdentifier: true, + matchAgainstDefines: true, // Allow defines to rewrite imported JSX symbols }) } From 8ae6058cad781e067c4f9ea53ba2dab7c9567dd6 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Wed, 27 Jul 2022 22:53:49 -0400 Subject: [PATCH 15/22] Some fixes for jsx source positions * Improve line/column performance by not repeatedly scanning backward * Column numbers are supposed to be 1-based to match Babel, not 0-based * Column numbers should be in UTF-16 code units to match Babel, not bytes --- .../bundler/snapshots/snapshots_tsconfig.txt | 12 ++--- internal/js_parser/js_parser.go | 44 ++++++++++++++++--- internal/js_parser/js_parser_test.go | 31 ++++++++----- scripts/js-api-tests.js | 4 +- 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/internal/bundler/snapshots/snapshots_tsconfig.txt b/internal/bundler/snapshots/snapshots_tsconfig.txt index e125dbeb06c..900c913c406 100644 --- a/internal/bundler/snapshots/snapshots_tsconfig.txt +++ b/internal/bundler/snapshots/snapshots_tsconfig.txt @@ -339,18 +339,18 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, { /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 18 + columnNumber: 19 }, this), /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 24 + columnNumber: 25 }, this) ] }, void 0, true, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 16 + columnNumber: 17 }, this)); import { Fragment, @@ -366,18 +366,18 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, { /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 18 + columnNumber: 19 }, this), /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 24 + columnNumber: 25 }, this) ] }, void 0, true, { fileName: "Users/user/project/entry.tsx", lineNumber: 2, - columnNumber: 16 + columnNumber: 17 }, this)); import { Fragment, diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 1534fcc8a20..2905f3e9c8c 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -194,6 +194,11 @@ type parser struct { tempRefCount int topLevelTempRefCount int + // We need to scan over the source contents to recover the line and column offsets + jsxSourceLoc int + jsxSourceLine int + jsxSourceColumn int + exportsRef js_ast.Ref requireRef js_ast.Ref moduleRef js_ast.Ref @@ -12115,6 +12120,38 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO case *js_ast.EJSXElement: propsLoc := expr.Loc + + // Resolving the location index to a specific line and column in + // development mode is not too expensive because we seek from the + // previous JSX element. It amounts to at most a single additional + // scan over the source code. Note that this has to happen before + // we visit anything about this JSX element to make sure that we + // only ever need to scan forward, not backward. + var jsxSourceLine int + var jsxSourceColumn int + if p.options.jsx.Development && p.options.jsx.AutomaticRuntime { + for p.jsxSourceLoc < int(propsLoc.Start) { + r, size := utf8.DecodeRuneInString(p.source.Contents[p.jsxSourceLoc:]) + p.jsxSourceLoc += size + if r == '\n' || r == '\r' || r == '\u2028' || r == '\u2029' { + if r == '\r' && p.jsxSourceLoc < len(p.source.Contents) && p.source.Contents[p.jsxSourceLoc] == '\n' { + p.jsxSourceLoc++ // Handle Windows-style CRLF newlines + } + p.jsxSourceLine++ + p.jsxSourceColumn = 0 + } else { + // Babel and TypeScript count columns in UTF-16 code units + if r < 0xFFFF { + p.jsxSourceColumn++ + } else { + p.jsxSourceColumn += 2 + } + } + } + jsxSourceLine = p.jsxSourceLine + jsxSourceColumn = p.jsxSourceColumn + } + if e.TagOrNil.Data != nil { propsLoc = e.TagOrNil.Loc e.TagOrNil = p.visitExpr(e.TagOrNil) @@ -12306,9 +12343,6 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO args = append(args, js_ast.Expr{Data: &js_ast.EBoolean{Value: isStaticChildren}}) // "__source" - // Resolving the location to a specific line and column could be expensive, but it - // only happens in development mode and is a documented tradeoff. - prettyLoc := p.tracker.MsgLocationOrNil(js_lexer.RangeOfIdentifier(p.source, expr.Loc)) args = append(args, js_ast.Expr{Data: &js_ast.EObject{ Properties: []js_ast.Property{ { @@ -12319,12 +12353,12 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO { Kind: js_ast.PropertyNormal, Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Line)}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceLine + 1)}}, // 1-based lines }, { Kind: js_ast.PropertyNormal, Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(prettyLoc.Column)}}, + ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceColumn + 1)}}, // 1-based columns }, }, }}) diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index b382aed49a9..e743294eea2 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4756,28 +4756,35 @@ func TestJSXAutomatic(t *testing.T) { // Dev, without runtime imports d := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} - expectPrintedJSXAutomatic(t, d, "
>
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "
{1}}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
>
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
{1}}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n 1,\n \"}\"\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, true, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n ...props\n}, \"key\", false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") // Falls back to createElement - expectPrintedJSXAutomatic(t, d, "
{...children}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") - expectPrintedJSXAutomatic(t, d, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
{...children}
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "
", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") expectParseErrorJSXAutomatic(t, d, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n") expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n") expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n") + // Line/column offset tests. Unlike Babel, TypeScript sometimes points to a + // location other than the start of the element. I'm not sure if that's a bug + // or not, but it seems weird. So I decided to match Babel instead. + expectPrintedJSXAutomatic(t, d, "\r\n", "/* @__PURE__ */ jsxDEV(\"x\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "\n\r", "/* @__PURE__ */ jsxDEV(\"x\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, d, "let 𐀀 = 🍕🍕🍕", "let 𐀀 = /* @__PURE__ */ jsxDEV(\"x\", {\n children: [\n \"🍕🍕🍕\",\n /* @__PURE__ */ jsxDEV(\"y\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 19\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 10\n}, this);\n") + // Dev, with runtime imports dr := JSXAutomaticTestOptions{Development: true} - expectPrintedJSXAutomatic(t, dr, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") - expectPrintedJSXAutomatic(t, dr, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dr, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dr, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") dri := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"} - expectPrintedJSXAutomatic(t, dri, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") - expectPrintedJSXAutomatic(t, dri, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 2\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 2\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dri, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dri, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") // JSX namespaced names for _, colon := range []string{":", " :", ": ", " : "} { diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index 6a9488af3ba..e2dd2a06177 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -3556,7 +3556,7 @@ let transformTests = { }, loader: 'jsx', }) - assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 2\n }, this)\n}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 0\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) + assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 3\n }, this)\n}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) }, // Note: tree shaking is disabled when the output format isn't IIFE @@ -3921,7 +3921,7 @@ let transformTests = { async jsxDevelopment({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxDevelopment: true }) - assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 12\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) + assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 13\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) }, async jsxImportSource({ esbuild }) { From 7e6c025daa241b0342911a3ab1b19a84b3dfc4ab Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Wed, 27 Jul 2022 23:07:08 -0400 Subject: [PATCH 16/22] Add missing source map positions --- internal/js_parser/js_parser.go | 49 +++++++++++++++------------------ 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 2905f3e9c8c..72e30f4415c 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -12267,7 +12267,7 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // been added by some upstream plugin. Their presence here would represent a // configuration error. hasKey := false - keyProperty := js_ast.Expr{Data: js_ast.EUndefinedShared} + keyProperty := js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared} for _, property := range e.Properties { if str, ok := property.Key.Data.(*js_ast.EString); ok { propName := helpers.UTF16ToString(str.Value) @@ -12300,32 +12300,27 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO // Children are passed in as an explicit prop if len(e.Children) > 0 { - childrenLoc := e.Children[0].Loc - var childrenValue js_ast.Expr + childrenValue := e.Children[0] if len(e.Children) > 1 { - childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: e.Children}} - } else if len(e.Children) == 1 { - if _, ok := e.Children[0].Data.(*js_ast.ESpread); ok { - // TypeScript considers spread children to be static, but Babel considers - // it to be an error ("Spread children are not supported in React."). - // We'll follow TypeScript's behavior here because spread children may be - // valid with non-React source runtimes. - childrenValue = js_ast.Expr{Data: &js_ast.EArray{Items: []js_ast.Expr{e.Children[0]}}} - isStaticChildren = true - } else { - childrenValue = e.Children[0] - } + childrenValue.Data = &js_ast.EArray{Items: e.Children} + } else if _, ok := childrenValue.Data.(*js_ast.ESpread); ok { + // TypeScript considers spread children to be static, but Babel considers + // it to be an error ("Spread children are not supported in React."). + // We'll follow TypeScript's behavior here because spread children may be + // valid with non-React source runtimes. + childrenValue.Data = &js_ast.EArray{Items: []js_ast.Expr{childrenValue}} + isStaticChildren = true } properties = append(properties, js_ast.Property{ Key: js_ast.Expr{ Data: &js_ast.EString{Value: helpers.StringToUTF16("children")}, - Loc: childrenLoc, + Loc: childrenValue.Loc, }, ValueOrNil: childrenValue, Kind: js_ast.PropertyNormal, - Loc: childrenLoc, + Loc: childrenValue.Loc, }) } @@ -12340,34 +12335,34 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO if p.options.jsx.Development { // "isStaticChildren" - args = append(args, js_ast.Expr{Data: &js_ast.EBoolean{Value: isStaticChildren}}) + args = append(args, js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EBoolean{Value: isStaticChildren}}) // "__source" - args = append(args, js_ast.Expr{Data: &js_ast.EObject{ + args = append(args, js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EObject{ Properties: []js_ast.Property{ { Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("fileName")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16(p.source.PrettyPath)}}, + Key: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: helpers.StringToUTF16("fileName")}}, + ValueOrNil: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: helpers.StringToUTF16(p.source.PrettyPath)}}, }, { Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceLine + 1)}}, // 1-based lines + Key: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: helpers.StringToUTF16("lineNumber")}}, + ValueOrNil: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: float64(jsxSourceLine + 1)}}, // 1-based lines }, { Kind: js_ast.PropertyNormal, - Key: js_ast.Expr{Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, - ValueOrNil: js_ast.Expr{Data: &js_ast.ENumber{Value: float64(jsxSourceColumn + 1)}}, // 1-based columns + Key: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.EString{Value: helpers.StringToUTF16("columnNumber")}}, + ValueOrNil: js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ENumber{Value: float64(jsxSourceColumn + 1)}}, // 1-based columns }, }, }}) // "__self" if p.fnOrArrowDataParse.isThisDisallowed { - args = append(args, js_ast.Expr{Data: js_ast.EUndefinedShared}) + args = append(args, js_ast.Expr{Loc: expr.Loc, Data: js_ast.EUndefinedShared}) } else { - args = append(args, js_ast.Expr{Data: js_ast.EThisShared}) + args = append(args, js_ast.Expr{Loc: expr.Loc, Data: js_ast.EThisShared}) } } From 033eaa1938d428053390b976b46518c02b17fb54 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 00:07:19 -0400 Subject: [PATCH 17/22] Move imports to top to avoid bugs in other tools --- .../bundler/snapshots/snapshots_default.txt | 10 +-- .../bundler/snapshots/snapshots_tsconfig.txt | 16 +---- internal/js_parser/js_parser.go | 62 +++++++++++-------- internal/js_parser/js_parser_test.go | 48 +++++++------- internal/js_printer/js_printer_test.go | 10 +-- scripts/js-api-tests.js | 12 ++-- 6 files changed, 76 insertions(+), 82 deletions(-) diff --git a/internal/bundler/snapshots/snapshots_default.txt b/internal/bundler/snapshots/snapshots_default.txt index 795b346d5ab..4816fecf9c7 100644 --- a/internal/bundler/snapshots/snapshots_default.txt +++ b/internal/bundler/snapshots/snapshots_default.txt @@ -1427,15 +1427,12 @@ var require_custom_react = __commonJS({ // entry.jsx var import_custom_react = __toESM(require_custom_react()); +import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime"; console.log(/* @__PURE__ */ jsx2("div", { jsx: import_custom_react.jsx }), /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2(import_custom_react.Fragment, {}) })); -import { - Fragment as Fragment2, - jsx as jsx2 -} from "react/jsx-runtime"; ================================================================================ TestJSXAutomaticImportsES6 @@ -1447,15 +1444,12 @@ function Fragment() { } // entry.jsx +import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime"; console.log(/* @__PURE__ */ jsx2("div", { jsx }), /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2(Fragment, {}) })); -import { - Fragment as Fragment2, - jsx as jsx2 -} from "react/jsx-runtime"; ================================================================================ TestJSXConstantFragments diff --git a/internal/bundler/snapshots/snapshots_tsconfig.txt b/internal/bundler/snapshots/snapshots_tsconfig.txt index 900c913c406..63831a5ca01 100644 --- a/internal/bundler/snapshots/snapshots_tsconfig.txt +++ b/internal/bundler/snapshots/snapshots_tsconfig.txt @@ -318,22 +318,19 @@ console.log(fib(10)); TestTsConfigReactJSX ---------- /Users/user/project/out.js ---------- // Users/user/project/entry.tsx +import { Fragment, jsx, jsxs } from "notreact/jsx-runtime"; console.log(/* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsx("div", {}), /* @__PURE__ */ jsx("div", {}) ] })); -import { - Fragment, - jsx, - jsxs -} from "notreact/jsx-runtime"; ================================================================================ TestTsConfigReactJSXDev ---------- /Users/user/project/out.js ---------- // Users/user/project/entry.tsx +import { Fragment, jsxDEV } from "react/jsx-dev-runtime"; console.log(/* @__PURE__ */ jsxDEV(Fragment, { children: [ /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { @@ -352,15 +349,12 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, { lineNumber: 2, columnNumber: 17 }, this)); -import { - Fragment, - jsxDEV -} from "react/jsx-dev-runtime"; ================================================================================ TestTsConfigReactJSXWithDevInMainConfig ---------- /Users/user/project/out.js ---------- // Users/user/project/entry.tsx +import { Fragment, jsxDEV } from "react/jsx-dev-runtime"; console.log(/* @__PURE__ */ jsxDEV(Fragment, { children: [ /* @__PURE__ */ jsxDEV("div", {}, void 0, false, { @@ -379,10 +373,6 @@ console.log(/* @__PURE__ */ jsxDEV(Fragment, { lineNumber: 2, columnNumber: 17 }, this)); -import { - Fragment, - jsxDEV -} from "react/jsx-dev-runtime"; ================================================================================ TestTsConfigWithStatementAlwaysStrictFalse diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 72e30f4415c..9d7cf3a1f48 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -15863,8 +15863,7 @@ func Parse(log logger.Log, source logger.Source, options Options) (result js_ast // Pop the module scope to apply the "ContainsDirectEval" rules p.popScope() - parts = append(append(before, parts...), after...) - result = p.toAST(parts, hashbang, directive) + result = p.toAST(before, parts, after, hashbang, directive) result.SourceMapComment = p.lexer.SourceMappingURL return } @@ -15895,7 +15894,7 @@ func LazyExportAST(log logger.Log, source logger.Source, options Options, expr j } p.symbolUses = nil - ast := p.toAST([]js_ast.Part{nsExportPart, part}, "", "") + ast := p.toAST(nil, []js_ast.Part{nsExportPart, part}, nil, "", "") ast.HasLazyExport = true return ast } @@ -16216,31 +16215,32 @@ func (p *parser) generateImportStmt( NamespaceRef: namespaceRef, Items: &clauseItems, ImportRecordIndex: importRecordIndex, + IsSingleLine: true, }}}, }) } -func (p *parser) toAST(parts []js_ast.Part, hashbang string, directive string) js_ast.AST { +// Sort the keys for determinism +func sortedKeysOfMapStringRef(in map[string]js_ast.Ref) []string { + keys := make([]string, 0, len(in)) + for key := range in { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func (p *parser) toAST(before, parts, after []js_ast.Part, hashbang string, directive string) js_ast.AST { // Insert an import statement for any runtime imports we generated if len(p.runtimeImports) > 0 && !p.options.omitRuntimeForTests { - // Sort the imports for determinism - keys := make([]string, 0, len(p.runtimeImports)) - for key := range p.runtimeImports { - keys = append(keys, key) - } - sort.Strings(keys) + keys := sortedKeysOfMapStringRef(p.runtimeImports) sourceIndex := runtime.SourceIndex - parts = p.generateImportStmt("", keys, &sourceIndex, parts, p.runtimeImports) + before = p.generateImportStmt("", keys, &sourceIndex, before, p.runtimeImports) } // Insert an import statement for any jsx runtime imports we generated if len(p.jsxRuntimeImports) > 0 && !p.options.omitJSXRuntimeForTests { - // Sort the imports for determinism - keys := make([]string, 0, len(p.jsxRuntimeImports)) - for key := range p.jsxRuntimeImports { - keys = append(keys, key) - } - sort.Strings(keys) + keys := sortedKeysOfMapStringRef(p.jsxRuntimeImports) // Determine the runtime source and whether it's prod or dev path := p.options.jsx.ImportSource @@ -16250,21 +16250,31 @@ func (p *parser) toAST(parts []js_ast.Part, hashbang string, directive string) j path = path + "/jsx-runtime" } - parts = p.generateImportStmt(path, keys, nil, parts, p.jsxRuntimeImports) + before = p.generateImportStmt(path, keys, nil, before, p.jsxRuntimeImports) } // Insert an import statement for any legacy jsx imports we generated (i.e., createElement) if len(p.jsxLegacyImports) > 0 && !p.options.omitJSXRuntimeForTests { - // Sort the imports for determinism - keys := make([]string, 0, len(p.jsxLegacyImports)) - for key := range p.jsxLegacyImports { - keys = append(keys, key) - } - sort.Strings(keys) - + keys := sortedKeysOfMapStringRef(p.jsxLegacyImports) path := p.options.jsx.ImportSource - parts = p.generateImportStmt(path, keys, nil, parts, p.jsxLegacyImports) + before = p.generateImportStmt(path, keys, nil, before, p.jsxLegacyImports) + } + + // Generated imports are inserted before other code instead of appending them + // to the end of the file. Appending them should work fine because JavaScript + // import statements are "hoisted" to run before the importing file. However, + // some buggy JavaScript toolchains such as the TypeScript compiler convert + // ESM into CommonJS by replacing "import" statements inline without doing + // any hoisting, which is incorrect. See the following issue for more info: + // https://github.com/microsoft/TypeScript/issues/16166. Since JSX-related + // imports are present in the generated code when bundling is disabled, and + // could therefore be processed by these buggy tools, it's more robust to put + // them at the top even though it means potentially reallocating almost the + // entire array of parts. + if len(before) > 0 { + parts = append(before, parts...) } + parts = append(parts, after...) // Handle import paths after the whole file has been visited because we need // symbol usage counts to be able to remove unused type-only imports in diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index e743294eea2..b8e4833c70c 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4745,14 +4745,14 @@ func TestJSXAutomatic(t *testing.T) { // Prod, with runtime imports pr := JSXAutomaticTestOptions{Development: false} - expectPrintedJSXAutomatic(t, pr, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") - expectPrintedJSXAutomatic(t, pr, "<>", "/* @__PURE__ */ jsxs(Fragment, {\n children: [\n /* @__PURE__ */ jsx(\"a\", {}),\n /* @__PURE__ */ jsx(\"b\", {})\n ]\n});\nimport {\n Fragment,\n jsx,\n jsxs\n} from \"react/jsx-runtime\";\n") - expectPrintedJSXAutomatic(t, pr, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"react\";\n") - expectPrintedJSXAutomatic(t, pr, "<>
", "/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n })\n});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\nimport {\n createElement\n} from \"react\";\n") + expectPrintedJSXAutomatic(t, pr, "
", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"div\", {});\n") + expectPrintedJSXAutomatic(t, pr, "<>", "import { Fragment, jsx, jsxs } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsxs(Fragment, {\n children: [\n /* @__PURE__ */ jsx(\"a\", {}),\n /* @__PURE__ */ jsx(\"b\", {})\n ]\n});\n") + expectPrintedJSXAutomatic(t, pr, "
", "import { createElement } from \"react\";\n/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") + expectPrintedJSXAutomatic(t, pr, "<>
", "import { Fragment, jsx } from \"react/jsx-runtime\";\nimport { createElement } from \"react\";\n/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n })\n});\n") pri := JSXAutomaticTestOptions{Development: false, ImportSource: "my-jsx-lib"} - expectPrintedJSXAutomatic(t, pri, "
", "/* @__PURE__ */ jsx(\"div\", {});\nimport {\n jsx\n} from \"my-jsx-lib/jsx-runtime\";\n") - expectPrintedJSXAutomatic(t, pri, "
", "/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\nimport {\n createElement\n} from \"my-jsx-lib\";\n") + expectPrintedJSXAutomatic(t, pri, "
", "import { jsx } from \"my-jsx-lib/jsx-runtime\";\n/* @__PURE__ */ jsx(\"div\", {});\n") + expectPrintedJSXAutomatic(t, pri, "
", "import { createElement } from \"my-jsx-lib\";\n/* @__PURE__ */ createElement(\"div\", {\n ...props,\n key: \"key\"\n});\n") // Dev, without runtime imports d := JSXAutomaticTestOptions{Development: true, OmitJSXRuntimeForTests: true} @@ -4779,12 +4779,12 @@ func TestJSXAutomatic(t *testing.T) { // Dev, with runtime imports dr := JSXAutomaticTestOptions{Development: true} - expectPrintedJSXAutomatic(t, dr, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") - expectPrintedJSXAutomatic(t, dr, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"react/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dr, "
", "import { jsxDEV } from \"react/jsx-dev-runtime\";\n/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, dr, "<>\n \n \n", "import { Fragment, jsxDEV } from \"react/jsx-dev-runtime\";\n/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") dri := JSXAutomaticTestOptions{Development: true, ImportSource: "preact"} - expectPrintedJSXAutomatic(t, dri, "
", "/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") - expectPrintedJSXAutomatic(t, dri, "<>\n \n \n", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from \"preact/jsx-dev-runtime\";\n") + expectPrintedJSXAutomatic(t, dri, "
", "import { jsxDEV } from \"preact/jsx-dev-runtime\";\n/* @__PURE__ */ jsxDEV(\"div\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") + expectPrintedJSXAutomatic(t, dri, "<>\n \n \n", "import { Fragment, jsxDEV } from \"preact/jsx-dev-runtime\";\n/* @__PURE__ */ jsxDEV(Fragment, {\n children: [\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 2,\n columnNumber: 3\n }, this),\n /* @__PURE__ */ jsxDEV(\"b\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") // JSX namespaced names for _, colon := range []string{":", " :", ": ", " : "} { @@ -4808,11 +4808,11 @@ func TestJSXAutomatic(t *testing.T) { } func TestJSXAutomaticPragmas(t *testing.T) { - expectPrintedJSX(t, "// @jsxRuntime automatic\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") - expectPrintedJSX(t, "/*@jsxRuntime automatic*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") - expectPrintedJSX(t, "/* @jsxRuntime automatic */\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") - expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") - expectPrintedJSX(t, "\n/* @jsxRuntime automatic */", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "// @jsxRuntime automatic\n", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic*/\n", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "/* @jsxRuntime automatic */\n", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "\n/* @jsxRuntime automatic */", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") expectPrintedJSX(t, "// @jsxRuntime classic\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") expectPrintedJSX(t, "/*@jsxRuntime classic*/\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") @@ -4822,22 +4822,22 @@ func TestJSXAutomaticPragmas(t *testing.T) { expectParseErrorJSX(t, "// @jsxRuntime foo\n", ": WARNING: Invalid JSX runtime: foo\n") - expectPrintedJSX(t, "// @jsxRuntime automatic @jsxImportSource src\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "/*@jsxRuntime automatic @jsxImportSource src*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "/*@jsxRuntime automatic*//*@jsxImportSource src*/\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "/* @jsxRuntime automatic */\n/* @jsxImportSource src */\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "\n/*@jsxRuntime automatic @jsxImportSource src*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/\n/*@jsxImportSource src*/", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") - expectPrintedJSX(t, "\n/* @jsxRuntime automatic */\n/* @jsxImportSource src */", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"src/jsx-runtime\";\n") + expectPrintedJSX(t, "// @jsxRuntime automatic @jsxImportSource src\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic @jsxImportSource src*/\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "/*@jsxRuntime automatic*//*@jsxImportSource src*/\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "/* @jsxRuntime automatic */\n/* @jsxImportSource src */\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic @jsxImportSource src*/", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "\n/*@jsxRuntime automatic*/\n/*@jsxImportSource src*/", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") + expectPrintedJSX(t, "\n/* @jsxRuntime automatic */\n/* @jsxImportSource src */", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") expectPrintedJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") expectParseErrorJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") expectParseErrorJSX(t, "// @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") - expectPrintedJSX(t, "// @jsxRuntime automatic @jsx h\n", "/* @__PURE__ */ jsx(\"a\", {});\nimport {\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "// @jsxRuntime automatic @jsx h\n", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") expectParseErrorJSX(t, "// @jsxRuntime automatic @jsx h\n", ": WARNING: JSX factory cannot be set when runtime is automatic: h\n") - expectPrintedJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", "/* @__PURE__ */ jsx(Fragment, {});\nimport {\n Fragment,\n jsx\n} from \"react/jsx-runtime\";\n") + expectPrintedJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", "import { Fragment, jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(Fragment, {});\n") expectParseErrorJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", ": WARNING: JSX fragment cannot be set when runtime is automatic: f\n") } diff --git a/internal/js_printer/js_printer_test.go b/internal/js_printer/js_printer_test.go index e49c1cb4319..66acda27a89 100644 --- a/internal/js_printer/js_printer_test.go +++ b/internal/js_printer/js_printer_test.go @@ -953,15 +953,15 @@ func TestAvoidSlashScript(t *testing.T) { expectPrinted(t, "/*! \";\n") + "import { __template } from \"\";\nvar _a;\nString.raw(_a || (_a = __template([\"<\\/script\"])));\n") expectPrinted(t, "String.raw`\";\n") + "import { __template } from \"\";\nvar _a;\nString.raw(_a || (_a = __template([\"<\\/script\", \"\"])), a);\n") expectPrinted(t, "String.raw`${a}\";\n") + "import { __template } from \"\";\nvar _a;\nString.raw(_a || (_a = __template([\"\", \"<\\/script\"])), a);\n") expectPrinted(t, "String.raw`\";\n") + "import { __template } from \"\";\nvar _a;\nString.raw(_a || (_a = __template([\"<\\/SCRIPT\"])));\n") expectPrinted(t, "String.raw`\";\n") + "import { __template } from \"\";\nvar _a;\nString.raw(_a || (_a = __template([\"<\\/ScRiPt\"])));\n") // Negative cases expectPrinted(t, "x = '
`, { tsconfigRaw: { @@ -3546,7 +3546,7 @@ let transformTests = { }, loader: 'jsx', }) - assert.strictEqual(code4, `/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ jsx("div", {})\n});\nimport {\n Fragment,\n jsx\n} from "notreact/jsx-runtime";\n`) + assert.strictEqual(code4, `import { Fragment, jsx } from "notreact/jsx-runtime";\n/* @__PURE__ */ jsx(Fragment, {\n children: /* @__PURE__ */ jsx("div", {})\n});\n`) const { code: code5 } = await esbuild.transform(`<>
`, { tsconfigRaw: { @@ -3556,7 +3556,7 @@ let transformTests = { }, loader: 'jsx', }) - assert.strictEqual(code5, `/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 3\n }, this)\n}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 1\n}, this);\nimport {\n Fragment,\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) + assert.strictEqual(code5, `import { Fragment, jsxDEV } from "react/jsx-dev-runtime";\n/* @__PURE__ */ jsxDEV(Fragment, {\n children: /* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 3\n }, this)\n}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n`) }, // Note: tree shaking is disabled when the output format isn't IIFE @@ -3916,17 +3916,17 @@ let transformTests = { async jsxRuntimeAutomatic({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic' }) - assert.strictEqual(code, `console.log(/* @__PURE__ */ jsx("div", {}));\nimport {\n jsx\n} from "react/jsx-runtime";\n`) + assert.strictEqual(code, `import { jsx } from "react/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, async jsxDevelopment({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxDevelopment: true }) - assert.strictEqual(code, `console.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 13\n}, this));\nimport {\n jsxDEV\n} from "react/jsx-dev-runtime";\n`) + assert.strictEqual(code, `import { jsxDEV } from "react/jsx-dev-runtime";\nconsole.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 13\n}, this));\n`) }, async jsxImportSource({ esbuild }) { const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxImportSource: 'notreact' }) - assert.strictEqual(code, `console.log(/* @__PURE__ */ jsx("div", {}));\nimport {\n jsx\n} from "notreact/jsx-runtime";\n`) + assert.strictEqual(code, `import { jsx } from "notreact/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, async ts({ esbuild }) { From 431db4fccdbeeca8d3adc80ab0f77b894d4871e6 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 00:36:22 -0400 Subject: [PATCH 18/22] Do not attempt to rewrite JSX symbols with define The define feature only applies to globals and "automatic" JSX symbols are never globals, so this never did anything. --- internal/js_parser/js_parser.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 9d7cf3a1f48..b4d9f4434c3 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -1573,7 +1573,6 @@ func (p *parser) importJSXSymbol(loc logger.Loc, jsx JSXImport) js_ast.Expr { p.recordUsage(ref) return p.handleIdentifier(loc, &js_ast.EIdentifier{Ref: ref}, identifierOpts{ wasOriginallyIdentifier: true, - matchAgainstDefines: true, // Allow defines to rewrite imported JSX symbols }) } @@ -12245,8 +12244,8 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO wasOriginallyIdentifier: true, matchAgainstDefines: true, // Allow defines to rewrite the JSX factory }) + p.warnAboutImportNamespaceCall(target, exprKindCall) } - p.warnAboutImportNamespaceCall(target, exprKindCall) return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ Target: target, Args: args, @@ -12371,10 +12370,8 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO jsx = JSXImportJSXS } - target := p.importJSXSymbol(expr.Loc, jsx) - p.warnAboutImportNamespaceCall(target, exprKindCall) return js_ast.Expr{Loc: expr.Loc, Data: &js_ast.ECall{ - Target: target, + Target: p.importJSXSymbol(expr.Loc, jsx), Args: args, CloseParenLoc: e.CloseLoc, From 1cdd6840b232d997476221631c66b8e30e5d8e79 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 01:12:10 -0400 Subject: [PATCH 19/22] Update error messages to match esbuild's style --- internal/js_parser/js_parser.go | 35 ++++++++++++-------- internal/js_parser/js_parser_test.go | 49 +++++++++++++++++++++------- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index b4d9f4434c3..1c8aceea185 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -12274,21 +12274,25 @@ func (p *parser) visitExprInOut(expr js_ast.Expr, in exprIn) (js_ast.Expr, exprO case "key": if property.Flags.Has(js_ast.PropertyWasShorthand) { r := js_lexer.RangeOfIdentifier(p.source, property.Loc) - p.log.AddErrorWithNotes(&p.tracker, r, - "Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.", - []logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, property.Loc), - fmt.Sprintf("The property %q was defined here:", propName))}) + msg := logger.Msg{ + Kind: logger.Error, + Data: p.tracker.MsgData(r, "Please provide an explicit value for \"key\":"), + Notes: []logger.MsgData{{Text: "Using \"key\" as a shorthand for \"key={true}\" is not allowed when using React's \"automatic\" JSX transform."}}, + } + msg.Data.Location.Suggestion = "key={true}" + p.log.AddMsg(msg) } else { keyProperty = property.ValueOrNil hasKey = true } continue + case "__source", "__self": r := js_lexer.RangeOfIdentifier(p.source, property.Loc) p.log.AddErrorWithNotes(&p.tracker, r, - fmt.Sprintf("Duplicate \"%s\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.", propName), - []logger.MsgData{p.tracker.MsgData(js_lexer.RangeOfIdentifier(p.source, property.Loc), - fmt.Sprintf("The property %q was defined here:", propName))}) + fmt.Sprintf("Duplicate \"%s\" prop found:", propName), + []logger.MsgData{{Text: "Both \"__source\" and \"__self\" are set automatically by esbuild when using React's \"automatic\" JSX transform. " + + "This duplicate prop may have come from a plugin."}}) continue } } @@ -16037,14 +16041,16 @@ func (p *parser) prepareForVisitPass() { } else if jsxRuntime.Text == "classic" { p.options.jsx.AutomaticRuntime = false } else { - p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxRuntime.Range, - fmt.Sprintf("Invalid JSX runtime: %s", jsxRuntime.Text)) + p.log.AddIDWithNotes(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxRuntime.Range, + fmt.Sprintf("Invalid JSX runtime: %q", jsxRuntime.Text), + []logger.MsgData{{Text: "The JSX runtime can only be set to either \"classic\" or \"automatic\"."}}) } } + if jsxFactory := p.lexer.JSXFactoryPragmaComment; jsxFactory.Text != "" { if p.options.jsx.AutomaticRuntime { p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFactory.Range, - fmt.Sprintf("JSX factory cannot be set when runtime is automatic: %s", jsxFactory.Text)) + "The JSX factory cannot be set when using React's \"automatic\" JSX transform") } else if expr, _ := ParseDefineExprOrJSON(jsxFactory.Text); len(expr.Parts) > 0 { p.options.jsx.Factory = expr } else { @@ -16052,10 +16058,11 @@ func (p *parser) prepareForVisitPass() { fmt.Sprintf("Invalid JSX factory: %s", jsxFactory.Text)) } } + if jsxFragment := p.lexer.JSXFragmentPragmaComment; jsxFragment.Text != "" { if p.options.jsx.AutomaticRuntime { p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxFragment.Range, - fmt.Sprintf("JSX fragment cannot be set when runtime is automatic: %s", jsxFragment.Text)) + "The JSX fragment cannot be set when using React's \"automatic\" JSX transform") } else if expr, _ := ParseDefineExprOrJSON(jsxFragment.Text); len(expr.Parts) > 0 || expr.Constant != nil { p.options.jsx.Fragment = expr } else { @@ -16063,10 +16070,12 @@ func (p *parser) prepareForVisitPass() { fmt.Sprintf("Invalid JSX fragment: %s", jsxFragment.Text)) } } + if jsxImportSource := p.lexer.JSXImportSourcePragmaComment; jsxImportSource.Text != "" { if !p.options.jsx.AutomaticRuntime { - p.log.AddID(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxImportSource.Range, - fmt.Sprintf("JSX import source cannot be set when runtime is classic: %s", jsxImportSource.Text)) + p.log.AddIDWithNotes(logger.MsgID_JS_UnsupportedJSXComment, logger.Warning, &p.tracker, jsxImportSource.Range, + fmt.Sprintf("The JSX import source cannot be set without also enabling React's \"automatic\" JSX transform"), + []logger.MsgData{{Text: "You can enable React's \"automatic\" JSX transform for this file by using a \"@jsxRuntime automatic\" comment."}}) } else { p.options.jsx.ImportSource = jsxImportSource.Text } diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index b8e4833c70c..76be17d0ff0 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4739,9 +4739,18 @@ func TestJSXAutomatic(t *testing.T) { expectPrintedJSXAutomatic(t, p, "", "/* @__PURE__ */ jsxs(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsx(\"a\", {})\n ]\n});\n") expectPrintedJSXAutomatic(t, p, "<>>", "/* @__PURE__ */ jsx(Fragment, {\n children: \">\"\n});\n") - expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n") - expectParseErrorJSXAutomatic(t, p, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n") - expectParseErrorJSXAutomatic(t, p, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n") + expectParseErrorJSXAutomatic(t, p, "", + `: ERROR: Please provide an explicit value for "key": +NOTE: Using "key" as a shorthand for "key={true}" is not allowed when using React's "automatic" JSX transform. +`) + expectParseErrorJSXAutomatic(t, p, "
", + `: ERROR: Duplicate "__self" prop found: +NOTE: Both "__source" and "__self" are set automatically by esbuild when using React's "automatic" JSX transform. This duplicate prop may have come from a plugin. +`) + expectParseErrorJSXAutomatic(t, p, "
", + `: ERROR: Duplicate "__source" prop found: +NOTE: Both "__source" and "__self" are set automatically by esbuild when using React's "automatic" JSX transform. This duplicate prop may have come from a plugin. +`) // Prod, with runtime imports pr := JSXAutomaticTestOptions{Development: false} @@ -4766,9 +4775,18 @@ func TestJSXAutomatic(t *testing.T) { expectPrintedJSXAutomatic(t, d, "", "/* @__PURE__ */ jsxDEV(\"div\", {\n children: [\n ...children,\n /* @__PURE__ */ jsxDEV(\"a\", {}, void 0, false, {\n fileName: \"\",\n lineNumber: 3,\n columnNumber: 3\n }, this)\n ]\n}, void 0, true, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") expectPrintedJSXAutomatic(t, d, "<>>", "/* @__PURE__ */ jsxDEV(Fragment, {\n children: \">\"\n}, void 0, false, {\n fileName: \"\",\n lineNumber: 1,\n columnNumber: 1\n}, this);\n") - expectParseErrorJSXAutomatic(t, d, "", ": ERROR: Please provide an explicit key value. Using \"key\" as a shorthand for \"key={true}\" is not allowed.\n: NOTE: The property \"key\" was defined here:\n") - expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__self\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__self\" was defined here:\n") - expectParseErrorJSXAutomatic(t, d, "
", ": ERROR: Duplicate \"__source\" prop found. Both __source and __self are set automatically by esbuild. These may have been set automatically by a plugin.\n: NOTE: The property \"__source\" was defined here:\n") + expectParseErrorJSXAutomatic(t, d, "", + `: ERROR: Please provide an explicit value for "key": +NOTE: Using "key" as a shorthand for "key={true}" is not allowed when using React's "automatic" JSX transform. +`) + expectParseErrorJSXAutomatic(t, d, "
", + `: ERROR: Duplicate "__self" prop found: +NOTE: Both "__source" and "__self" are set automatically by esbuild when using React's "automatic" JSX transform. This duplicate prop may have come from a plugin. +`) + expectParseErrorJSXAutomatic(t, d, "
", + `: ERROR: Duplicate "__source" prop found: +NOTE: Both "__source" and "__self" are set automatically by esbuild when using React's "automatic" JSX transform. This duplicate prop may have come from a plugin. +`) // Line/column offset tests. Unlike Babel, TypeScript sometimes points to a // location other than the start of the element. I'm not sure if that's a bug @@ -4820,7 +4838,10 @@ func TestJSXAutomaticPragmas(t *testing.T) { expectPrintedJSX(t, "\n/*@jsxRuntime classic*/\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") expectPrintedJSX(t, "\n/* @jsxRuntime classic */\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") - expectParseErrorJSX(t, "// @jsxRuntime foo\n", ": WARNING: Invalid JSX runtime: foo\n") + expectParseErrorJSX(t, "// @jsxRuntime foo\n", + `: WARNING: Invalid JSX runtime: "foo" +NOTE: The JSX runtime can only be set to either "classic" or "automatic". +`) expectPrintedJSX(t, "// @jsxRuntime automatic @jsxImportSource src\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") expectPrintedJSX(t, "/*@jsxRuntime automatic @jsxImportSource src*/\n", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") @@ -4831,14 +4852,20 @@ func TestJSXAutomaticPragmas(t *testing.T) { expectPrintedJSX(t, "\n/* @jsxRuntime automatic */\n/* @jsxImportSource src */", "import { jsx } from \"src/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") expectPrintedJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", "/* @__PURE__ */ React.createElement(\"a\", null);\n") - expectParseErrorJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") - expectParseErrorJSX(t, "// @jsxImportSource src\n", ": WARNING: JSX import source cannot be set when runtime is classic: src\n") + expectParseErrorJSX(t, "// @jsxRuntime classic @jsxImportSource src\n", + `: WARNING: The JSX import source cannot be set without also enabling React's "automatic" JSX transform +NOTE: You can enable React's "automatic" JSX transform for this file by using a "@jsxRuntime automatic" comment. +`) + expectParseErrorJSX(t, "// @jsxImportSource src\n", + `: WARNING: The JSX import source cannot be set without also enabling React's "automatic" JSX transform +NOTE: You can enable React's "automatic" JSX transform for this file by using a "@jsxRuntime automatic" comment. +`) expectPrintedJSX(t, "// @jsxRuntime automatic @jsx h\n", "import { jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(\"a\", {});\n") - expectParseErrorJSX(t, "// @jsxRuntime automatic @jsx h\n", ": WARNING: JSX factory cannot be set when runtime is automatic: h\n") + expectParseErrorJSX(t, "// @jsxRuntime automatic @jsx h\n", ": WARNING: The JSX factory cannot be set when using React's \"automatic\" JSX transform\n") expectPrintedJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", "import { Fragment, jsx } from \"react/jsx-runtime\";\n/* @__PURE__ */ jsx(Fragment, {});\n") - expectParseErrorJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", ": WARNING: JSX fragment cannot be set when runtime is automatic: f\n") + expectParseErrorJSX(t, "// @jsxRuntime automatic @jsxFrag f\n<>", ": WARNING: The JSX fragment cannot be set when using React's \"automatic\" JSX transform\n") } func TestPreserveOptionalChainParentheses(t *testing.T) { From 2f63726663faa272cd6a52ae32142fbd91f36a7a Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 01:41:58 -0400 Subject: [PATCH 20/22] Merge `--jsx-runtime` into `--jsx` I find it confusing to have both `--jsx` and `--jsx-runtime` since the underlying state is really only one of three settings. This merges both of them, which is similar to what TypeScript does. I have kept the `--jsx-development` option separate because the desire is to be able to override TypeScript's settings. But I have shortened it to `--jsx-dev` to make specifying it on the command line easier. --- cmd/esbuild/main.go | 6 +++--- lib/shared/common.ts | 6 ++---- lib/shared/types.ts | 6 ++---- pkg/api/api.go | 30 +++++++++++------------------- pkg/api/api_impl.go | 8 ++++---- pkg/cli/cli_impl.go | 32 ++++++++------------------------ scripts/js-api-tests.js | 8 ++++---- scripts/verify-source-map.js | 4 ++-- 8 files changed, 36 insertions(+), 64 deletions(-) diff --git a/cmd/esbuild/main.go b/cmd/esbuild/main.go index 50e9c3a50e9..6f9dc823a9a 100644 --- a/cmd/esbuild/main.go +++ b/cmd/esbuild/main.go @@ -77,13 +77,13 @@ var helpText = func(colors logger.Colors) string { incorrect tree-shaking annotations --inject:F Import the file F into all input files and automatically replace matching globals with imports + --jsx-dev Use React's automatic runtime in development mode --jsx-factory=... What to use for JSX instead of React.createElement --jsx-fragment=... What to use for JSX instead of React.Fragment - --jsx=... Set to "preserve" to disable transforming JSX to JS - --jsx-runtime=... Set to "automatic" to use the new JSX transform - --jsx-development Use the automatic runtime in development mode --jsx-import-source=... Override the package name for the automatic runtime (default "react") + --jsx=... Set to "automatic" to use React's automatic runtime + or to "preserve" to disable transforming JSX to JS --keep-names Preserve "name" on functions and classes --legal-comments=... Where to place legal comments (none | inline | eof | linked | external, default eof when bundling diff --git a/lib/shared/common.ts b/lib/shared/common.ts index 72630408245..662f43107de 100644 --- a/lib/shared/common.ts +++ b/lib/shared/common.ts @@ -140,9 +140,8 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe let jsx = getFlag(options, keys, 'jsx', mustBeString); let jsxFactory = getFlag(options, keys, 'jsxFactory', mustBeString); let jsxFragment = getFlag(options, keys, 'jsxFragment', mustBeString); - let jsxRuntime = getFlag(options, keys, 'jsxRuntime', mustBeString); let jsxImportSource = getFlag(options, keys, 'jsxImportSource', mustBeString); - let jsxDevelopment = getFlag(options, keys, 'jsxDevelopment', mustBeBoolean); + let jsxDev = getFlag(options, keys, 'jsxDev', mustBeBoolean); let define = getFlag(options, keys, 'define', mustBeObject); let logOverride = getFlag(options, keys, 'logOverride', mustBeObject); let supported = getFlag(options, keys, 'supported', mustBeObject); @@ -176,9 +175,8 @@ function pushCommonFlags(flags: string[], options: CommonOptions, keys: OptionKe if (jsx) flags.push(`--jsx=${jsx}`); if (jsxFactory) flags.push(`--jsx-factory=${jsxFactory}`); if (jsxFragment) flags.push(`--jsx-fragment=${jsxFragment}`); - if (jsxRuntime) flags.push(`--jsx-runtime=${jsxRuntime}`); if (jsxImportSource) flags.push(`--jsx-import-source=${jsxImportSource}`); - if (jsxDevelopment) flags.push(`--jsx-development`); + if (jsxDev) flags.push(`--jsx-dev`); if (define) { for (let key in define) { diff --git a/lib/shared/types.ts b/lib/shared/types.ts index 9ad52742cd1..31a5d0ba60a 100644 --- a/lib/shared/types.ts +++ b/lib/shared/types.ts @@ -52,17 +52,15 @@ interface CommonOptions { ignoreAnnotations?: boolean; /** Documentation: https://esbuild.github.io/api/#jsx */ - jsx?: 'transform' | 'preserve'; + jsx?: 'transform' | 'preserve' | 'automatic'; /** Documentation: https://esbuild.github.io/api/#jsx-factory */ jsxFactory?: string; /** Documentation: https://esbuild.github.io/api/#jsx-fragment */ jsxFragment?: string; - /** Documentation: https://esbuild.github.io/api/#jsx-runtime */ - jsxRuntime?: 'classic' | 'automatic'; /** Documentation: https://esbuild.github.io/api/#jsx-import-source */ jsxImportSource?: string; /** Documentation: https://esbuild.github.io/api/#jsx-development */ - jsxDevelopment?: boolean; + jsxDev?: boolean; /** Documentation: https://esbuild.github.io/api/#define */ define?: { [key: string]: string }; diff --git a/pkg/api/api.go b/pkg/api/api.go index 48b18590a7b..8d5745e44f5 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -110,13 +110,7 @@ type JSXMode uint8 const ( JSXModeTransform JSXMode = iota JSXModePreserve -) - -type JSXRuntime uint8 - -const ( - JSXRuntimeClassic JSXRuntime = iota - JSXRuntimeAutomatic + JSXModeAutomatic ) type Target uint8 @@ -283,12 +277,11 @@ type BuildOptions struct { IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments - JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx-mode - JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory - JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment - JSXRuntime JSXRuntime // Documentation: https://esbuild.github.io/api/#jsx-runtime - JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source - JSXDevelopment bool // Documentation: https://esbuild.github.io/api/#jsx-development + JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx-mode + JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory + JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source + JSXDev bool // Documentation: https://esbuild.github.io/api/#jsx-dev Define map[string]string // Documentation: https://esbuild.github.io/api/#define Pure []string // Documentation: https://esbuild.github.io/api/#pure @@ -406,12 +399,11 @@ type TransformOptions struct { IgnoreAnnotations bool // Documentation: https://esbuild.github.io/api/#ignore-annotations LegalComments LegalComments // Documentation: https://esbuild.github.io/api/#legal-comments - JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx - JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory - JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment - JSXRuntime JSXRuntime // Documentation: https://esbuild.github.io/api/#jsx-runtime - JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source - JSXDevelopment bool // Documentation: https://esbuild.github.io/api/#jsx-development + JSXMode JSXMode // Documentation: https://esbuild.github.io/api/#jsx + JSXFactory string // Documentation: https://esbuild.github.io/api/#jsx-factory + JSXFragment string // Documentation: https://esbuild.github.io/api/#jsx-fragment + JSXImportSource string // Documentation: https://esbuild.github.io/api/#jsx-import-source + JSXDev bool // Documentation: https://esbuild.github.io/api/#jsx-dev TsconfigRaw string // Documentation: https://esbuild.github.io/api/#tsconfig-raw Banner string // Documentation: https://esbuild.github.io/api/#banner diff --git a/pkg/api/api_impl.go b/pkg/api/api_impl.go index d736b78cf8f..12fdc23c313 100644 --- a/pkg/api/api_impl.go +++ b/pkg/api/api_impl.go @@ -898,11 +898,11 @@ func rebuildImpl( OriginalTargetEnv: targetEnv, JSX: config.JSXOptions{ Preserve: buildOpts.JSXMode == JSXModePreserve, + AutomaticRuntime: buildOpts.JSXMode == JSXModeAutomatic, Factory: validateJSXExpr(log, buildOpts.JSXFactory, "factory"), Fragment: validateJSXExpr(log, buildOpts.JSXFragment, "fragment"), - AutomaticRuntime: buildOpts.JSXRuntime == JSXRuntimeAutomatic, + Development: buildOpts.JSXDev, ImportSource: buildOpts.JSXImportSource, - Development: buildOpts.JSXDevelopment, }, Defines: defines, InjectedDefines: injectedDefines, @@ -1360,10 +1360,10 @@ func transformImpl(input string, transformOpts TransformOptions) TransformResult useDefineForClassFieldsTS := config.Unspecified jsx := config.JSXOptions{ Preserve: transformOpts.JSXMode == JSXModePreserve, + AutomaticRuntime: transformOpts.JSXMode == JSXModeAutomatic, Factory: validateJSXExpr(log, transformOpts.JSXFactory, "factory"), Fragment: validateJSXExpr(log, transformOpts.JSXFragment, "fragment"), - AutomaticRuntime: transformOpts.JSXRuntime == JSXRuntimeAutomatic, - Development: transformOpts.JSXDevelopment, + Development: transformOpts.JSXDev, ImportSource: transformOpts.JSXImportSource, } diff --git a/pkg/cli/cli_impl.go b/pkg/cli/cli_impl.go index 80323774069..08064021362 100644 --- a/pkg/cli/cli_impl.go +++ b/pkg/cli/cli_impl.go @@ -610,10 +610,12 @@ func parseOptionsImpl( mode = api.JSXModeTransform case "preserve": mode = api.JSXModePreserve + case "automatic": + mode = api.JSXModeAutomatic default: return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote( fmt.Sprintf("Invalid value %q in %q", value, arg), - "Valid values are \"transform\" or \"preserve\".", + "Valid values are \"transform\", \"automatic\", or \"preserve\".", ) } if buildOpts != nil { @@ -638,26 +640,6 @@ func parseOptionsImpl( transformOpts.JSXFragment = value } - case strings.HasPrefix(arg, "--jsx-runtime="): - value := arg[len("--jsx-runtime="):] - var runtime api.JSXRuntime - switch value { - case "classic": - runtime = api.JSXRuntimeClassic - case "automatic": - runtime = api.JSXRuntimeAutomatic - default: - return parseOptionsExtras{}, cli_helpers.MakeErrorWithNote( - fmt.Sprintf("Invalid value %q in %q", value, arg), - "Valid values are \"classic\" or \"automatic\".", - ) - } - if buildOpts != nil { - buildOpts.JSXRuntime = runtime - } else { - transformOpts.JSXRuntime = runtime - } - case strings.HasPrefix(arg, "--jsx-import-source="): value := arg[len("--jsx-import-source="):] if buildOpts != nil { @@ -666,13 +648,13 @@ func parseOptionsImpl( transformOpts.JSXImportSource = value } - case isBoolFlag(arg, "--jsx-development"): + case isBoolFlag(arg, "--jsx-dev"): if value, err := parseBoolFlag(arg, true); err != nil { return parseOptionsExtras{}, err } else if buildOpts != nil { - buildOpts.JSXDevelopment = value + buildOpts.JSXDev = value } else { - transformOpts.JSXDevelopment = value + transformOpts.JSXDev = value } case strings.HasPrefix(arg, "--banner=") && transformOpts != nil: @@ -771,6 +753,7 @@ func parseOptionsImpl( "allow-overwrite": true, "bundle": true, "ignore-annotations": true, + "jsx-dev": true, "keep-names": true, "minify-identifiers": true, "minify-syntax": true, @@ -798,6 +781,7 @@ func parseOptionsImpl( "ignore-annotations": true, "jsx-factory": true, "jsx-fragment": true, + "jsx-import-source": true, "jsx": true, "keep-names": true, "legal-comments": true, diff --git a/scripts/js-api-tests.js b/scripts/js-api-tests.js index 1a39597e8d9..f6eac4ff2dc 100644 --- a/scripts/js-api-tests.js +++ b/scripts/js-api-tests.js @@ -3915,17 +3915,17 @@ let transformTests = { }, async jsxRuntimeAutomatic({ esbuild }) { - const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic' }) + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic' }) assert.strictEqual(code, `import { jsx } from "react/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, - async jsxDevelopment({ esbuild }) { - const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxDevelopment: true }) + async jsxDev({ esbuild }) { + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic', jsxDev: true }) assert.strictEqual(code, `import { jsxDEV } from "react/jsx-dev-runtime";\nconsole.log(/* @__PURE__ */ jsxDEV("div", {}, void 0, false, {\n fileName: "",\n lineNumber: 1,\n columnNumber: 13\n}, this));\n`) }, async jsxImportSource({ esbuild }) { - const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsxRuntime: 'automatic', jsxImportSource: 'notreact' }) + const { code } = await esbuild.transform(`console.log(
)`, { loader: 'jsx', jsx: 'automatic', jsxImportSource: 'notreact' }) assert.strictEqual(code, `import { jsx } from "notreact/jsx-runtime";\nconsole.log(/* @__PURE__ */ jsx("div", {}));\n`) }, diff --git a/scripts/verify-source-map.js b/scripts/verify-source-map.js index 7814d212abc..c1c138f9339 100644 --- a/scripts/verify-source-map.js +++ b/scripts/verify-source-map.js @@ -788,13 +788,13 @@ async function main() { }), check('jsx-runtime' + suffix, testCaseJSXRuntime, toSearchJSXRuntime, { ext: 'js', - flags: flags.concat('--outfile=out.js', '--bundle', '--jsx-runtime=automatic', '--external:react/jsx-runtime'), + flags: flags.concat('--outfile=out.js', '--bundle', '--jsx=automatic', '--external:react/jsx-runtime'), entryPoints: ['entry.jsx'], crlf, }), check('jsx-dev-runtime' + suffix, testCaseJSXRuntime, toSearchJSXRuntime, { ext: 'js', - flags: flags.concat('--outfile=out.js', '--bundle', '--jsx-runtime=automatic', '--jsx-development', '--external:react/jsx-dev-runtime'), + flags: flags.concat('--outfile=out.js', '--bundle', '--jsx=automatic', '--jsx-dev', '--external:react/jsx-dev-runtime'), entryPoints: ['entry.jsx'], crlf, }), From 93f9ea48a054cf31cf2d60cfa4506f388c8a4066 Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 10:31:23 -0400 Subject: [PATCH 21/22] Add something to the release notes --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e23d141fd..ea475536c16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## Unreleased + +* Add support for React 17's `automatic` JSX transform ([#334](https://github.com/evanw/esbuild/issues/334), [#718](https://github.com/evanw/esbuild/issues/718), [#1172](https://github.com/evanw/esbuild/issues/1172), [#2318](https://github.com/evanw/esbuild/issues/2318), [#334](https://github.com/evanw/esbuild/pull/2349)) + + This adds support for the [new "automatic" JSX runtime from React 17+](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) to esbuild for both the build and transform APIs. + + **New CLI flags and API options:** + - `--jsx`, `jsx` — Set this to `"automatic"` to opt in to this new transform + - `--jsx-dev`, `jsxDev` — Toggles development mode for the automatic runtime + - `--jsx-import-source`, `jsxImportSource` — Overrides the root import for runtime functions (default `"react"`) + + **New JSX pragma comments:** + - `@jsxRuntime` — Sets the runtime (`automatic` or `classic`) + - `@jsxImportSource` — Sets the import source (only valid with automatic runtime) + + The existing `@jsxFragment` and `@jsxFactory` pragma comments are only valid with "classic" runtime. + + **TSConfig resolving:** + Along with accepting the new options directly via CLI or API, option inference from `tsconfig.json` compiler options was also implemented: + + - `"jsx": "preserve"` or `"jsx": "react-native"` → Same as `--jsx=preserve` in esbuild + - `"jsx": "react"` → Same as `--jsx=transform` in esbuild (which is the default behavior) + - `"jsx": "react-jsx"` → Same as `--jsx=automatic` in esbuild + - `"jsx": "react-jsxdev"` → Same as `--jsx=automatic --jsx-dev` in esbuild + + It also reads the value of `"jsxImportSource"` from `tsconfig.json` if specified. + + For `react-jsx` it's important to note that it doesn't implicitly disable `--jsx-dev`. This is to support the case where a user sets `"react-jsx"` in their `tsconfig.json` but then toggles development mode directly in esbuild. + + **esbuild vs Babel vs TS vs...** + + There are a few differences between the various technologies that implement automatic JSX runtimes. The JSX transform in esbuild follows a mix of Babel's and TypeScript's behavior: + + - When an element has `__source` or `__self` props: + - Babel: Print an error about a deprecated transform plugin + - TypeScript: Allow the props + - swc: Hard crash + - **esbuild**: Print an error — Following Babel was chosen for this one because this might help people catch configuration issues where JSX files are being parsed by multiple tools + + - Element has an "implicit true" key prop, e.g. ``: + - Babel: Print an error indicating that "key" props require an explicit value + - TypeScript: Silently omit the "key" prop + - swc: Hard crash + - **esbuild**: Print an error like Babel — This might help catch legitimate programming mistakes + + - Element has spread children, e.g. `{...children}` + - Babel: Print an error stating that React doesn't support spread children + - TypeScript: Use static jsx function and pass children as-is, including spread operator + - swc: same as Babel + - **esbuild**: Same as TypeScript + + Also note that TypeScript has some bugs regarding JSX development mode and the generation of `lineNumber` and `columnNumber` values. Babel's values are accurate though, so esbuild's line and column numbers match Babel. Both numbers are 1-based and columns are counted in terms of UTF-16 code units. + + This feature was contributed by [@jgoz](https://github.com/jgoz). + ## 0.14.50 * Emit `names` in source maps ([#1296](https://github.com/evanw/esbuild/issues/1296)) From c20f3c252e5a290c6c95b508fb07b4b4bb7e669e Mon Sep 17 00:00:00 2001 From: Evan Wallace Date: Thu, 28 Jul 2022 11:00:15 -0400 Subject: [PATCH 22/22] The automatic runtime implicitly sets strict mode --- internal/js_ast/js_ast.go | 1 + internal/js_parser/js_parser.go | 16 ++++++++++++++++ internal/js_parser/js_parser_lower.go | 7 +++++++ internal/js_parser/js_parser_test.go | 12 ++++++++++++ 4 files changed, 36 insertions(+) diff --git a/internal/js_ast/js_ast.go b/internal/js_ast/js_ast.go index f67a93f827f..55fc0c233f5 100644 --- a/internal/js_ast/js_ast.go +++ b/internal/js_ast/js_ast.go @@ -1525,6 +1525,7 @@ const ( ImplicitStrictModeClass ImplicitStrictModeESM ImplicitStrictModeTSAlwaysStrict + ImplicitStrictModeJSXAutomaticRuntime ) func (s *Scope) RecursiveSetStrictMode(kind StrictModeKind) { diff --git a/internal/js_parser/js_parser.go b/internal/js_parser/js_parser.go index 1c8aceea185..90b64074f03 100644 --- a/internal/js_parser/js_parser.go +++ b/internal/js_parser/js_parser.go @@ -226,6 +226,7 @@ type parser struct { latestArrowArgLoc logger.Loc forbidSuffixAfterAsLoc logger.Loc + firstJSXElementLoc logger.Loc fnOrArrowDataVisit fnOrArrowDataVisit @@ -4516,6 +4517,11 @@ func (p *parser) parseJSXTag() (logger.Range, string, js_ast.Expr) { } func (p *parser) parseJSXElement(loc logger.Loc) js_ast.Expr { + // Keep track of the location of the first JSX element for error messages + if p.firstJSXElementLoc.Start == -1 { + p.firstJSXElementLoc = loc + } + // Parse the tag startRange, startText, startTagOrNil := p.parseJSXTag() @@ -15609,6 +15615,7 @@ func newParser(log logger.Log, source logger.Source, lexer js_lexer.Lexer, optio promiseRef: js_ast.InvalidRef, regExpRef: js_ast.InvalidRef, afterArrowBodyLoc: logger.Loc{Start: -1}, + firstJSXElementLoc: logger.Loc{Start: -1}, importMetaRef: js_ast.InvalidRef, runtimePublicFieldImport: js_ast.InvalidRef, superCtorRef: js_ast.InvalidRef, @@ -16081,6 +16088,15 @@ func (p *parser) prepareForVisitPass() { } } } + + // Force-enable strict mode if the JSX "automatic" runtime is enabled and + // there is at least one JSX element. This is because the automatically- + // generated import statement turns the file into an ES module. This behavior + // matches TypeScript which also does this. See this PR for more information: + // https://github.com/microsoft/TypeScript/pull/39199 + if p.currentScope.StrictMode == js_ast.SloppyMode && p.options.jsx.AutomaticRuntime && p.firstJSXElementLoc.Start != -1 { + p.currentScope.StrictMode = js_ast.ImplicitStrictModeJSXAutomaticRuntime + } } func (p *parser) declareCommonJSSymbol(kind js_ast.SymbolKind, name string) js_ast.Ref { diff --git a/internal/js_parser/js_parser_lower.go b/internal/js_parser/js_parser_lower.go index ef1a4ab7b59..50daf17d34d 100644 --- a/internal/js_parser/js_parser_lower.go +++ b/internal/js_parser/js_parser_lower.go @@ -220,6 +220,13 @@ func (p *parser) markStrictModeFeature(feature strictModeFeature, r logger.Range notes = []logger.MsgData{t.MsgData(tsAlwaysStrict.Range, fmt.Sprintf( "TypeScript's %q setting was enabled here:", tsAlwaysStrict.Name))} + case js_ast.ImplicitStrictModeJSXAutomaticRuntime: + notes = []logger.MsgData{p.tracker.MsgData(logger.Range{Loc: p.firstJSXElementLoc, Len: 1}, + "This file is implicitly in strict mode due to the JSX element here:"), + {Text: "When React's \"automatic\" JSX transform is enabled, using a JSX element automatically inserts " + + "an \"import\" statement at the top of the file for the corresponding the JSX helper function. " + + "This means the file is considered an ECMAScript module, and all ECMAScript modules use strict mode."}} + case js_ast.ExplicitStrictMode: notes = []logger.MsgData{p.tracker.MsgData(p.source.RangeOfString(p.currentScope.UseStrictLoc), "Strict mode is triggered by the \"use strict\" directive here:")} diff --git a/internal/js_parser/js_parser_test.go b/internal/js_parser/js_parser_test.go index 76be17d0ff0..507b903b639 100644 --- a/internal/js_parser/js_parser_test.go +++ b/internal/js_parser/js_parser_test.go @@ -4823,6 +4823,18 @@ NOTE: Both "__source" and "__self" are set automatically by esbuild when using R expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Expected \">\" but found \":\"\n") expectParseErrorJSXAutomatic(t, p, "", ": ERROR: Expected identifier after \"x:\" in namespaced JSX name\n") } + + // Enabling the "automatic" runtime means that any JSX element will cause the + // file to be implicitly in strict mode due to the automatically-generated + // import statement. This is the same behavior as the TypeScript compiler. + strictModeError := ": ERROR: With statements cannot be used in strict mode\n" + + ": NOTE: This file is implicitly in strict mode due to the JSX element here:\n" + + "NOTE: When React's \"automatic\" JSX transform is enabled, using a JSX element automatically inserts an \"import\" statement at the top of the file " + + "for the corresponding the JSX helper function. This means the file is considered an ECMAScript module, and all ECMAScript modules use strict mode.\n" + expectPrintedJSX(t, "with (x) y()", "with (x)\n y(/* @__PURE__ */ React.createElement(\"z\", null));\n") + expectPrintedJSXAutomatic(t, p, "with (x) y", "with (x)\n y;\n") + expectParseErrorJSX(t, "with (x) y() // @jsxRuntime automatic", strictModeError) + expectParseErrorJSXAutomatic(t, p, "with (x) y()", strictModeError) } func TestJSXAutomaticPragmas(t *testing.T) {