Skip to content

Commit

Permalink
fix #2634: ignore "jsx": "preserve" in tsconfig
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 16, 2023
1 parent 6f2c029 commit e7481cd
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 16 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@

Some people requested that esbuild support TypeScript's `target` setting, so support for it was added (in [version 0.12.4](https://github.com/evanw/esbuild/releases/v0.12.4)). However, esbuild supports reading from multiple `tsconfig.json` files within a single build, which opens up the possibility that different files in the build have different language targets configured. There isn't really any reason to do this and it can lead to unexpected results. So with this release, the `target` setting in `tsconfig.json` will no longer affect esbuild's own `target` setting. You will have to use esbuild's own target setting instead (which is a single, global value).

* TypeScript's `jsx` setting no longer causes esbuild to preserve JSX syntax ([#2634](https://github.com/evanw/esbuild/issues/2634))

TypeScript has a setting called [`jsx`](https://www.typescriptlang.org/tsconfig#jsx) that controls how to transform JSX into JS. The tool-agnostic transform is called `react`, and the React-specific transform is called `react-jsx` (or `react-jsxdev`). There is also a setting called `preserve` which indicates JSX should be passed through untransformed. Previously people would run esbuild with `"jsx": "preserve"` in their `tsconfig.json` files and then be surprised when esbuild preserved their JSX. So with this release, esbuild will now ignore `"jsx": "preserve"` in `tsconfig.json` files. If you want to preserve JSX syntax with esbuild, you now have to use `--jsx=preserve`.

Note: Some people have suggested that esbuild's equivalent `jsx` setting override the one in `tsconfig.json`. However, some projects need to legitimately have different files within the same build use different transforms (i.e. `react` vs. `react-jsx`) and having esbuild's global `jsx` setting override `tsconfig.json` would prevent this from working. This release ignores `"jsx": "preserve"` but still allows other `jsx` values in `tsconfig.json` files to override esbuild's global `jsx` setting to keep the ability for multiple files within the same build to use different transforms.

* `useDefineForClassFields` behavior has changed ([#2584](https://github.com/evanw/esbuild/issues/2584), [#2993](https://github.com/evanw/esbuild/issues/2993))

Class fields in TypeScript look like this (`x` is a class field):
Expand Down
52 changes: 52 additions & 0 deletions internal/bundler_tests/bundler_tsconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,58 @@ func TestTsConfigNestedJSX(t *testing.T) {
})
}

func TestTsConfigPreserveJSX(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": "preserve" // This should be ignored
}
}
`,
},
entryPaths: []string{"/Users/user/project/entry.tsx"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
},
})
}

func TestTsConfigPreserveJSXAutomatic(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": "preserve" // This should be ignored
}
}
`,
},
entryPaths: []string{"/Users/user/project/entry.tsx"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputFile: "/Users/user/project/out.js",
JSX: config.JSXOptions{
AutomaticRuntime: true,
},
ExternalSettings: config.ExternalSettings{
PreResolve: config.ExternalMatchers{Exact: map[string]bool{
"react/jsx-runtime": true,
}},
},
},
})
}

func TestTsConfigReactJSX(t *testing.T) {
tsconfig_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
16 changes: 16 additions & 0 deletions internal/bundler_tests/snapshots/snapshots_tsconfig.txt
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,22 @@ function fib(input) {
// Users/user/project/entry.ts
console.log(fib(10));

================================================================================
TestTsConfigPreserveJSX
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
console.log(/* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", null), /* @__PURE__ */ React.createElement("div", null)));

================================================================================
TestTsConfigPreserveJSXAutomatic
---------- /Users/user/project/out.js ----------
// Users/user/project/entry.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
console.log(/* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", {}),
/* @__PURE__ */ jsx("div", {})
] }));

================================================================================
TestTsConfigReactJSX
---------- /Users/user/project/out.js ----------
Expand Down
38 changes: 22 additions & 16 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,38 @@ const (
TSJSXReactJSXDev
)

type TSOptions struct {
Config TSConfig
Parse bool
NoAmbiguousLessThan bool
}

type TSConfigJSX struct {
// If not empty, these should override the default values
JSXFactory []string // Default if empty: "React.createElement"
JSXFragmentFactory []string // Default if empty: "React.Fragment"
JSXImportSource string // Default if empty: "react"
JSX TSJSX
}

func (tsConfig *TSConfigJSX) ApplyTo(jsxOptions *JSXOptions) {
switch tsConfig.JSX {
case TSJSXPreserve, TSJSXReactNative:
jsxOptions.Preserve = true
// Deliberately don't set "Preserve = true" here. Some tools from Vercel
// apparently automatically set "jsx": "preserve" in "tsconfig.json" and
// people are then confused when esbuild preserves their JSX. Ignoring this
// value means you now have to explicitly pass "--jsx=preserve" to esbuild
// to get this behavior.

case TSJSXReact:
jsxOptions.AutomaticRuntime = false
jsxOptions.Development = false

case TSJSXReactJSX:
jsxOptions.AutomaticRuntime = true
// Don't set "Development = false" implicitly
// Deliberately don't set "Development = false" here. People want to be
// able to have "react-jsx" in their "tsconfig.json" file and then swap
// that to "react-jsxdev" by passing "--jsx-dev" to esbuild.

case TSJSXReactJSXDev:
jsxOptions.AutomaticRuntime = true
Expand All @@ -66,20 +86,6 @@ func (tsConfig *TSConfigJSX) ApplyTo(jsxOptions *JSXOptions) {
}
}

type TSOptions struct {
Config TSConfig
Parse bool
NoAmbiguousLessThan bool
}

type TSConfigJSX struct {
// If not empty, these should override the default values
JSXFactory []string // Default if empty: "React.createElement"
JSXFragmentFactory []string // Default if empty: "React.Fragment"
JSXImportSource string // Default if empty: "react"
JSX TSJSX
}

// Note: This can currently only contain primitive values. It's compared
// for equality using a structural equality comparison by the JS parser.
type TSConfig struct {
Expand Down

0 comments on commit e7481cd

Please sign in to comment.