Skip to content

Commit e4710f5

Browse files
committed
Add jsx option
1 parent f46fc01 commit e4710f5

File tree

4 files changed

+68
-6
lines changed

4 files changed

+68
-6
lines changed

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct Options {
3434
pub resolve_remote_module: Option<bool>,
3535
pub is_dev: Option<bool>,
3636
pub source_map: Option<bool>,
37+
pub jsx: Option<String>,
3738
pub jsx_pragma: Option<String>,
3839
pub jsx_pragma_frag: Option<String>,
3940
pub jsx_import_source: Option<String>,
@@ -116,6 +117,7 @@ pub fn transform(specifier: &str, code: &str, options: JsValue) -> Result<JsValu
116117
resolver.clone(),
117118
&EmitOptions {
118119
target,
120+
jsx: options.jsx,
119121
jsx_pragma: options.jsx_pragma,
120122
jsx_pragma_frag: options.jsx_pragma_frag,
121123
jsx_import_source: options.jsx_import_source,

src/swc.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use swc_ecmascript::visit::{as_folder, Fold, FoldWith};
2323
#[derive(Clone)]
2424
pub struct EmitOptions {
2525
pub target: EsVersion,
26+
pub jsx: Option<String>,
2627
pub jsx_pragma: Option<String>,
2728
pub jsx_pragma_frag: Option<String>,
2829
pub jsx_import_source: Option<String>,
@@ -36,6 +37,7 @@ impl Default for EmitOptions {
3637
fn default() -> Self {
3738
EmitOptions {
3839
target: EsVersion::Es2022,
40+
jsx: None,
3941
jsx_pragma: None,
4042
jsx_pragma_frag: None,
4143
jsx_import_source: None,
@@ -58,7 +60,6 @@ pub struct SWC {
5860
impl SWC {
5961
/// parse source code.
6062
pub fn parse(specifier: &str, source: &str, target: EsVersion, lang: Option<String>) -> Result<Self, anyhow::Error> {
61-
print!("--- {} {:?} {:?}\n", specifier, target, lang);
6263
let source_map = SourceMap::default();
6364
let source_file = source_map.new_source_file(FileName::Real(Path::new(specifier).to_path_buf()), source.into());
6465
let sm = &source_map;
@@ -112,14 +113,19 @@ impl SWC {
112113
let unresolved_mark = Mark::new();
113114
let top_level_mark = Mark::fresh(Mark::root());
114115
let specifier_is_remote = resolver.borrow().specifier_is_remote;
116+
let extname = Path::new(&self.specifier)
117+
.extension()
118+
.unwrap_or_default()
119+
.to_ascii_lowercase();
115120
let is_dev = resolver.borrow().is_dev;
116-
let is_ts =
117-
self.specifier.ends_with(".ts") || self.specifier.ends_with(".mts") || self.specifier.ends_with(".tsx");
118-
let is_jsx = self.specifier.ends_with(".tsx") || self.specifier.ends_with(".jsx");
119-
let react_options = if let Some(jsx_import_source) = &options.jsx_import_source {
121+
let is_ts = extname == "ts" || extname == "mts" || extname == "tsx";
122+
let jsxt = options.jsx.as_deref().unwrap_or("classic");
123+
let is_jsx = jsxt != "preserve" && (extname == "jsx" || extname == "tsx");
124+
let react_options = if jsxt == "automatic" {
120125
let mut resolver = resolver.borrow_mut();
126+
let import_source = options.jsx_import_source.as_deref().unwrap_or("react");
121127
let runtime = if is_dev { "/jsx-dev-runtime" } else { "/jsx-runtime" };
122-
let import_source = resolver.resolve(&(jsx_import_source.to_owned() + runtime), false, None);
128+
let import_source = resolver.resolve(&(import_source.to_owned() + runtime), false, None);
123129
let import_source = import_source
124130
.split("?")
125131
.next()

src/tests.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,56 @@ fn import_resolving() {
146146
assert!(code.contains("new Worker(\"/-/esm.sh/asksomeonelse\")"));
147147
}
148148

149+
#[test]
150+
fn jsx_preserve() {
151+
let source = r#"
152+
export default function App() {
153+
return (
154+
<>
155+
<h1 className="title">Hello world!</h1>
156+
</>
157+
)
158+
}
159+
"#;
160+
let (code, _) = transform(
161+
"./app.tsx",
162+
source,
163+
false,
164+
&EmitOptions {
165+
jsx: Some("preserve".into()),
166+
..Default::default()
167+
},
168+
);
169+
assert!(code.contains("<h1 className=\"title\">Hello world!</h1>"));
170+
assert!(code.contains("<>"));
171+
assert!(code.contains("</>"));
172+
}
173+
174+
#[test]
175+
fn jsx_classic() {
176+
let source = r#"
177+
import React from "react"
178+
export default function App() {
179+
return (
180+
<>
181+
<h1 className="title">Hello world!</h1>
182+
</>
183+
)
184+
}
185+
"#;
186+
let (code, _) = transform(
187+
"./app.tsx",
188+
source,
189+
false,
190+
&EmitOptions {
191+
jsx: Some("classic".into()),
192+
..Default::default()
193+
},
194+
);
195+
assert!(code.contains("React.createElement(\"h1\""));
196+
assert!(code.contains("React.createElement(React.Fragment,"));
197+
}
198+
149199
#[test]
150200
fn jsx_automtic() {
151201
let source = r#"
@@ -163,6 +213,7 @@ fn jsx_automtic() {
163213
source,
164214
false,
165215
&EmitOptions {
216+
jsx: Some("automatic".into()),
166217
jsx_import_source: Some("https://esm.sh/react@18".to_owned()),
167218
..Default::default()
168219
},
@@ -194,6 +245,7 @@ fn react_refresh() {
194245
true,
195246
&EmitOptions {
196247
react_refresh: true,
248+
jsx: Some("automatic".into()),
197249
jsx_import_source: Some("https://esm.sh/react@18".to_owned()),
198250
..Default::default()
199251
},
@@ -264,6 +316,7 @@ fn strip_data_export() {
264316
false,
265317
&EmitOptions {
266318
strip_data_export: true,
319+
jsx: Some("automatic".into()),
267320
jsx_import_source: Some("https://esm.sh/react@18".to_owned()),
268321
..Default::default()
269322
},

types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type TransformOptions = {
2020
isDev?: boolean;
2121
reactRefresh?: boolean;
2222
sourceMap?: boolean;
23+
jsx?: "automatic" | "classic" | "preserve";
2324
jsxPragma?: string;
2425
jsxPragmaFrag?: string;
2526
jsxImportSource?: string;

0 commit comments

Comments
 (0)