Skip to content

Rebase merge of dev/jsx to dev/v2 #10718

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 7 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/fast-hairs-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
swc_common: major
---

refactor(es/react): Split jsx into automatic/classic
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions bindings/binding_core_wasm/__tests__/simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ describe("parse", () => {
"ctxt": 2,
"optional": false,
"span": {
"end": 254,
"start": 251,
"end": 114,
"start": 111,
},
"type": "Identifier",
"value": "Foo",
},
"implements": [],
"isAbstract": false,
"span": {
"end": 257,
"start": 245,
"end": 117,
"start": 105,
},
"superClass": null,
"superTypeParams": null,
Expand All @@ -133,8 +133,8 @@ describe("parse", () => {
],
"interpreter": null,
"span": {
"end": 257,
"start": 245,
"end": 117,
"start": 105,
},
"type": "Module",
}
Expand All @@ -159,17 +159,17 @@ describe("parse", () => {
"ctxt": 2,
"optional": false,
"span": {
"end": 267,
"start": 264,
"end": 127,
"start": 124,
},
"type": "Identifier",
"value": "Foo",
},
"implements": [],
"isAbstract": false,
"span": {
"end": 270,
"start": 258,
"end": 130,
"start": 118,
},
"superClass": null,
"superTypeParams": null,
Expand All @@ -179,8 +179,8 @@ describe("parse", () => {
],
"interpreter": null,
"span": {
"end": 270,
"start": 258,
"end": 130,
"start": 118,
},
"type": "Module",
}
Expand Down
8 changes: 5 additions & 3 deletions bindings/binding_core_wasm/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -793,14 +793,16 @@ export interface EsParserConfig {
importAssertions?: boolean;
}

type JSXPreset = "react" | "react-jsx" | "react-jsxdev" | "preserve" | "react-native";

/**
* Options for transform.
*/
export interface TransformConfig {
/**
* Effective only if `syntax` supports ƒ.
* Effective only if `syntax` supports.
*/
react?: ReactConfig;
react?: JSXPreset | ReactConfig;

constModules?: ConstModulesConfig;

Expand Down Expand Up @@ -889,7 +891,7 @@ export interface ReactConfig {
/**
* jsx runtime
*/
runtime?: 'automatic' | 'classic'
runtime?: 'automatic' | 'classic' | 'preserve';

/**
* Declares the module specifier to be used for importing the `jsx` and `jsxs` factory functions when using `runtime` 'automatic'
Expand Down
13 changes: 13 additions & 0 deletions crates/jsdoc/tests/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,17 @@ impl Comments for SwcComments {
leading.push(pure_comment);
}
}

fn for_each(&self, f: &mut dyn FnMut(&Comment)) {
for entry in self.leading.iter() {
for cmt in entry.value() {
f(cmt);
}
}
for entry in self.trailing.iter() {
for cmt in entry.value() {
f(cmt);
}
}
}
}
7 changes: 5 additions & 2 deletions crates/swc/benches/oxc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,11 @@ fn full_group(c: &mut Criterion) {
})),
transform: Some(TransformConfig {
react: swc_ecma_transforms::react::Options {
runtime: Some(Runtime::Automatic),
development: Some(react_dev),
runtime: Runtime::Automatic(Default::default()),
common: swc_ecma_transforms::react::CommonConfig {
development: react_dev.into(),
..Default::default()
},
..Default::default()
},
..Default::default()
Expand Down
81 changes: 35 additions & 46 deletions crates/swc/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use swc_ecma_transforms::{
decorators, explicit_resource_management::explicit_resource_management,
export_default_from, import_attributes,
},
react::{self, default_pragma, default_pragma_frag},
react::{self},
resolver,
typescript::{self, TsImportExportAssignConfig},
Assumptions,
Expand Down Expand Up @@ -295,16 +295,42 @@ impl Options {

let mut transform = transform.into_inner().unwrap_or_default();

// Do a resolver pass before everything.
//
// We do this before creating custom passes, so custom passses can use the
// variable management system based on the syntax contexts.
if syntax.typescript() {
assumptions.set_class_methods |= !transform.use_define_for_class_fields.into_bool();
}

assumptions.set_public_class_fields |= !transform.use_define_for_class_fields.into_bool();

// Do a resolver pass before everything.
//
// We do this before creating custom passes, so custom passses can use the
// variable management system based on the syntax contexts.
//
// Exception: Classic JSX transformation runs before the resolver
// because it's a context-free syntactic transformation that doesn't require
// scope information.
// Running resolver first would mark identifiers (like JSX pragma functions)
// with syntax contexts, making it harder for the JSX transform to
// handle pragma references that should resolve to local scope declarations.

let jsx_pass = 'jsx_pass: {
if !syntax.jsx() {
break 'jsx_pass None;
}

let (mut before_resolver, after_resolver) = react::react(
cm.clone(),
comments.cloned(),
transform.react,
top_level_mark,
unresolved_mark,
);

program.mutate(&mut before_resolver);

Some(after_resolver)
};

program.visit_mut_with(&mut resolver(
unresolved_mark,
top_level_mark,
Expand Down Expand Up @@ -782,54 +808,17 @@ impl Options {
..Default::default()
};

(
Optional::new(
typescript::typescript(ts_config, unresolved_mark, top_level_mark),
syntax.typescript() && !syntax.jsx(),
),
Optional::new(
typescript::tsx::<Option<&dyn Comments>>(
cm.clone(),
ts_config,
typescript::TsxConfig {
pragma: Some(
transform
.react
.pragma
.clone()
.unwrap_or_else(default_pragma),
),
pragma_frag: Some(
transform
.react
.pragma_frag
.clone()
.unwrap_or_else(default_pragma_frag),
),
},
comments.map(|v| v as _),
unresolved_mark,
top_level_mark,
),
syntax.typescript() && syntax.jsx(),
),
Optional::new(
typescript::typescript(ts_config, unresolved_mark, top_level_mark),
syntax.typescript(),
)
},
),
(
plugin_transforms.take(),
custom_before_pass(&program),
// handle jsx
Optional::new(
react::react::<&dyn Comments>(
cm.clone(),
comments.map(|v| v as _),
transform.react,
top_level_mark,
unresolved_mark,
),
syntax.jsx(),
),
jsx_pass,
built_pass,
Optional::new(jest::jest(), transform.hidden.jest.into_bool()),
Optional::new(
Expand Down
3 changes: 2 additions & 1 deletion crates/swc/tests/fixture/codegen/jsx-1/output/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export default /*#__PURE__*/ React.createElement(A, {
import { jsx as _jsx } from "react/jsx-runtime";
export default /*#__PURE__*/ _jsx(A, {
className: b,
header: "C",
subheader: "D E"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
var _require = require("foo/jsx-runtime"), _jsx = _require.jsx;
var _require = require("react/jsx-runtime"), _jsx = _require.jsx;
;
_jsx("a", {});
25 changes: 25 additions & 0 deletions crates/swc/tests/fixture/issues-10xxx/10553/input/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"exportDefaultFrom": true,
"jsx": true
},
"target": "es5",
"loose": false,
"minify": {
"compress": false,
"mangle": false
},
"transform": {
"react": {
"runtime": "classic"
}
}
},
"module": {
"type": "es6"
},
"minify": false,
"isModule": true
}
3 changes: 3 additions & 0 deletions crates/swc/tests/fixture/issues-10xxx/10553/input/input.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Home(React) {
return <div>Hello World {count}</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function Home(React) {
return React.createElement("div", null, "Hello World ", count);
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
var _require = require("react/jsx-runtime"), _jsx = _require.jsx, _Fragment = _require.Fragment;
var a = function() {
return /*#__PURE__*/ React.createElement(React.Fragment, null, "\xb7");
return /*#__PURE__*/ _jsx(_Fragment, {
children: "\xb7"
});
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var _require = require("react/jsx-runtime"), _jsx = _require.jsx;
function Component() {
return /*#__PURE__*/ React.createElement("div", {
return /*#__PURE__*/ _jsx("div", {
name: "A B"
});
}
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
const Span = /*#__PURE__*/ React.createElement("span", null, "with ");
const { jsx: _jsx } = require("react/jsx-runtime");
const Span = /*#__PURE__*/ _jsx("span", {
children: "with "
});
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
const Span = /*#__PURE__*/ React.createElement("span", null, "with&nbsp");
const { jsx: _jsx } = require("react/jsx-runtime");
const Span = /*#__PURE__*/ _jsx("span", {
children: "with&nbsp"
});
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
/*#__PURE__*/ React.createElement(React.Fragment, null, /*#__PURE__*/ React.createElement("span", null, "Hello something long to not trigger line break"), " ");
/*#__PURE__*/ const { jsx: _jsx, jsxs: _jsxs, Fragment: _Fragment } = require("react/jsx-runtime");
_jsxs(_Fragment, {
children: [
/*#__PURE__*/ _jsx("span", {
children: "Hello something long to not trigger line break"
}),
" "
]
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const a = /*#__PURE__*/ React.createElement("div", {
const { jsx: _jsx } = require("react/jsx-runtime");
const a = /*#__PURE__*/ _jsx("div", {
id: "abc>"
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var a = /*#__PURE__*/ React.createElement("div", {
var _require = require("react/jsx-runtime"), _jsx = _require.jsx;
var a = /*#__PURE__*/ _jsx("div", {
id: "abc>"
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"parser": {
"syntax": "typescript",
"tsx": true
},
"transform": {
"react": "react"
}
},
"module": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
console.log(/*#__PURE__*/ React.createElement("h1", {
value: "abc\\nas"
}, "s"));
var _require = require("react/jsx-runtime"), _jsx = _require.jsx;
console.log(/*#__PURE__*/ _jsx("h1", {
value: "abc\\nas",
children: "s"
}));
6 changes: 2 additions & 4 deletions crates/swc/tests/fixture/issues-1xxx/1687/.input/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@
"dynamicImport": true
},
"transform": {
"react": {
"runtime": "automatic"
}
"react": "react-jsx"
},
"externalHelpers": true
},
"env": {
"coreJs": "3",
"mode": "usage"
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
var foo = /* 1 */ /*#__PURE__*/ React.createElement("h1", null, bar /* 3 */ ); /* 4 */
var _require = require("react/jsx-runtime"), _jsx = _require.jsx;
var foo = /* 1 */ /*#__PURE__*/ _jsx("h1", {
children: bar /* 3 */
}); /* 4 */
Loading
Loading