Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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;
Copy link
Member Author

@magic-akari magic-akari Jun 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The react field now supports both object-style configuration (maintaining Babel compatibility) and string-based presets (like TypeScript's JSX presets).
However, the current config "react": "react" appears slightly odd. I propose renaming this field to jsx during future configuration refinements.


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
4 changes: 1 addition & 3 deletions crates/swc/tests/fixture/issues-1xxx/1525/case1/input/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
"tsx": true
},
"transform": {
"react": {
"runtime": "classic"
}
"react": "react"
}
},
"module": {
Expand Down
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"
}
}
}
5 changes: 1 addition & 4 deletions crates/swc/tests/fixture/issues-8xxx/8210/input/.swcrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
"mangle": false
},
"transform": {
"react": {
"development": true,
"runtime": "automatic"
}
"react": "react-jsxdev"
}
},
"module": {
Expand Down
38 changes: 4 additions & 34 deletions crates/swc/tests/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,40 +142,10 @@ fn matrix(input: &Path) -> Vec<TestUnitData> {
.collect();

for value in values {
match value.as_str() {
"react-jsx" => {
let options = react::Options {
runtime: react::Runtime::Automatic(Default::default()),
..Default::default()
};
react.push(options);
}
"react-jsxdev" => {
let options = react::Options {
runtime: react::Runtime::Automatic(Default::default()),
common: react::CommonConfig {
development: true.into(),
..Default::default()
},
..Default::default()
};
react.push(options);
}
"preserve" => {
react.push(react::Options {
runtime: react::Runtime::Preserve,
..Default::default()
});
}
"react" => {
let options = react::Options {
runtime: react::Runtime::Classic(Default::default()),
..Default::default()
};
react.push(options);
}
_ => {}
}
let Ok(options) = react::Options::try_from(value.as_str()) else {
continue;
};
react.push(options);
}
}
"removecomments" => {}
Expand Down
196 changes: 111 additions & 85 deletions crates/swc_ecma_transforms_react/src/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ impl Default for ClassicConfig {
}
}

#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "RuntimeRaw")]
pub enum Runtime {
Classic(ClassicConfig),
Automatic(AutomaticConfig),
Expand All @@ -95,97 +96,53 @@ impl Merge for Runtime {
}
}

impl<'de> Deserialize<'de> for Runtime {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use std::fmt;

use serde::de::{Error, Visitor};

struct RuntimeVisitor;

impl<'de> Visitor<'de> for RuntimeVisitor {
type Value = Runtime;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string or an object for runtime configuration")
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: Error,
{
match value {
"automatic" => Ok(Runtime::Automatic(AutomaticConfig::default())),
"classic" => Ok(Runtime::Classic(ClassicConfig::default())),
"preserve" => Ok(Runtime::Preserve),
_ => Err(Error::unknown_variant(
value,
&["automatic", "classic", "preserve"],
)),
}
}

fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct ConfigHelper {
runtime: Option<String>,
pragma: Option<BytesStr>,
pragma_frag: Option<BytesStr>,
import_source: Option<Atom>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RuntimeRaw {
#[serde(default)]
runtime: Option<String>,
#[serde(default)]
pragma: Option<BytesStr>,
#[serde(default)]
pragma_frag: Option<BytesStr>,
#[serde(default)]
import_source: Option<Atom>,
}

let helper: ConfigHelper =
Deserialize::deserialize(serde::de::value::MapAccessDeserializer::new(map))?;

match helper.runtime.as_deref() {
Some("automatic") => {
let config = AutomaticConfig {
import_source: helper
.import_source
.unwrap_or_else(default_import_source),
};
Ok(Runtime::Automatic(config))
}
Some("classic") => {
let config = ClassicConfig {
pragma: helper.pragma.unwrap_or_else(default_pragma),
pragma_frag: helper.pragma_frag.unwrap_or_else(default_pragma_frag),
};
Ok(Runtime::Classic(config))
}
Some("preserve") => Ok(Runtime::Preserve),
Some(other) => Err(Error::unknown_variant(
other,
&["automatic", "classic", "preserve"],
)),
None => match (helper.pragma, helper.pragma_frag, helper.import_source) {
(pragma @ Some(..), pragma_frag, None)
| (pragma, pragma_frag @ Some(..), None) => {
Ok(Runtime::Classic(ClassicConfig {
pragma: pragma.unwrap_or_else(default_pragma),
pragma_frag: pragma_frag.unwrap_or_else(default_pragma_frag),
}))
}
(_, _, import_source) => Ok(Runtime::Automatic(AutomaticConfig {
import_source: import_source.unwrap_or_else(default_import_source),
})),
},
impl TryFrom<RuntimeRaw> for Runtime {
type Error = String;

fn try_from(raw: RuntimeRaw) -> Result<Self, Self::Error> {
match raw.runtime.as_deref() {
Some("automatic") => Ok(Runtime::Automatic(AutomaticConfig {
import_source: raw.import_source.unwrap_or_else(default_import_source),
})),
Some("classic") => Ok(Runtime::Classic(ClassicConfig {
pragma: raw.pragma.unwrap_or_else(default_pragma),
pragma_frag: raw.pragma_frag.unwrap_or_else(default_pragma_frag),
})),
Some("preserve") => Ok(Runtime::Preserve),
Some(other) => Err(format!(
"unknown runtime variant `{other}`, expected one of `automatic`, `classic`, \
`preserve`"
)),
None => match (raw.pragma, raw.pragma_frag, raw.import_source) {
(pragma @ Some(..), pragma_frag, None) | (pragma, pragma_frag @ Some(..), None) => {
Ok(Runtime::Classic(ClassicConfig {
pragma: pragma.unwrap_or_else(default_pragma),
pragma_frag: pragma_frag.unwrap_or_else(default_pragma_frag),
}))
}
}
(_, _, import_source) => Ok(Runtime::Automatic(AutomaticConfig {
import_source: import_source.unwrap_or_else(default_import_source),
})),
},
}

deserializer.deserialize_any(RuntimeVisitor)
}
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, Merge)]
#[serde(try_from = "OptionsRaw")]
pub struct Options {
#[serde(flatten)]
pub runtime: Runtime,
Expand All @@ -198,6 +155,75 @@ pub struct Options {
pub refresh: Option<RefreshOptions>,
}

impl TryFrom<&str> for Options {
type Error = String;

fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"react" => Ok(Options {
runtime: Runtime::Classic(ClassicConfig::default()),
common: CommonConfig::default(),
refresh: None,
}),
"react-jsx" => Ok(Options {
runtime: Runtime::Automatic(AutomaticConfig::default()),
common: CommonConfig::default(),
refresh: None,
}),
"react-jsxdev" => Ok(Options {
runtime: Runtime::Automatic(AutomaticConfig::default()),
common: CommonConfig {
development: true.into(),
..CommonConfig::default()
},
refresh: None,
}),
"preserve" | "react-native" => Ok(Options {
runtime: Runtime::Preserve,
common: CommonConfig::default(),
refresh: None,
}),
other => Err(format!(
"unknown preset `{other}`, expected one of `react`, `react-jsx`, `react-jsxdev`, \
`preserve`, `react-native`"
)),
}
}
}

#[derive(Deserialize)]
#[serde(untagged)]
enum OptionsRaw {
Preset(String),
Object {
#[serde(flatten)]
runtime: Runtime,
#[serde(flatten)]
common: CommonConfig,
#[serde(default, deserialize_with = "deserialize_refresh")]
refresh: Option<RefreshOptions>,
},
}

impl TryFrom<OptionsRaw> for Options {
type Error = String;

fn try_from(raw: OptionsRaw) -> Result<Self, Self::Error> {
match raw {
OptionsRaw::Preset(preset) => preset.as_str().try_into(),
OptionsRaw::Object {
runtime,
common,
refresh,
} => Ok(Options {
runtime,
common,
refresh,
}),
}
}
}

#[cfg(feature = "concurrent")]
macro_rules! static_str {
($s:expr) => {{
Expand Down
8 changes: 5 additions & 3 deletions packages/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -836,14 +836,16 @@ export interface EsParserConfig {
explicitResourceManagement?: 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 @@ -946,7 +948,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
Loading