Skip to content

fix #334: support automatic JSX runtime #2349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/esbuild/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,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
}
Expand Down
82 changes: 82 additions & 0 deletions internal/bundler/bundler_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(<div jsx={jsx}/>, <><Fragment/></>)
`,
"/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(<div jsx={jsx}/>, <><Fragment/></>)
`,
"/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(<div/>)
`,
},
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{
Expand Down
85 changes: 85 additions & 0 deletions internal/bundler/bundler_tsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(<><div/><div/></>)
`,
"/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(<><div/><div/></>)
`,
"/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(<><div/><div/></>)
`,
"/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{
Expand Down
42 changes: 42 additions & 0 deletions internal/bundler/snapshots/snapshots_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,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 ----------
Expand Down
70 changes: 70 additions & 0 deletions internal/bundler/snapshots/snapshots_tsconfig.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ----------
Expand Down
38 changes: 34 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,39 @@ 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 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 {
Expand Down Expand Up @@ -276,6 +305,7 @@ type Options struct {
WriteToStdout bool

OmitRuntimeForTests bool
OmitJSXRuntimeForTests bool
UnusedImportFlagsTS UnusedImportFlagsTS
UseDefineForClassFields MaybeBool
ASCIIOnly bool
Expand Down
Loading