From f0e452a5995230457c00fa9717d710e22988a991 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Tue, 7 Nov 2023 16:42:08 +0800 Subject: [PATCH] feat(transformer): support importSource option in react_jsx (#1115) --- crates/oxc_transformer/src/lib.rs | 2 +- crates/oxc_transformer/src/options.rs | 2 +- crates/oxc_transformer/src/react_jsx/mod.rs | 30 +++++++++++++++---- .../oxc_transformer/src/react_jsx/options.rs | 13 ++++++-- crates/oxc_transformer/src/tester.rs | 3 +- tasks/transform_conformance/babel.snap.md | 5 ++-- 6 files changed, 41 insertions(+), 14 deletions(-) diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 03b63d909..0524b3cec 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -79,7 +79,6 @@ impl<'a> Transformer<'a> { Self { // TODO: pass verbatim_module_syntax from user config typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast), ctx.clone(), false)), - react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), &ctx, options)), regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options), es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options), es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), ctx.clone(), &options), @@ -88,6 +87,7 @@ impl<'a> Transformer<'a> { es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), ctx.clone(), &options), es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options), es2015_template_literals: TemplateLiterals::new(Rc::clone(&ast), &options), + react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), &ctx, options)), } } diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index 9fc19a8cf..e916c96c2 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -2,7 +2,7 @@ use oxc_syntax::assumptions::CompilerAssumptions; use crate::{es2020::NullishCoalescingOperatorOptions, react_jsx::ReactJsxOptions}; -#[derive(Debug, Default, Clone, Copy)] +#[derive(Debug, Default, Clone)] pub struct TransformOptions { pub target: TransformTarget, pub assumptions: CompilerAssumptions, diff --git a/crates/oxc_transformer/src/react_jsx/mod.rs b/crates/oxc_transformer/src/react_jsx/mod.rs index 148fce8d1..6b5d39260 100644 --- a/crates/oxc_transformer/src/react_jsx/mod.rs +++ b/crates/oxc_transformer/src/react_jsx/mod.rs @@ -27,6 +27,8 @@ pub struct ReactJsx<'a> { import_jsxs: bool, import_fragment: bool, import_create_element: bool, + // Will be store jsx runtime importer, like `react/jsx-runtime` + jsx_runtime_importer: Atom, } enum JSXElementOrFragment<'a, 'b> { @@ -75,10 +77,19 @@ impl<'a> ReactJsx<'a> { ) -> Self { let imports = ast.new_vec(); let options = options.with_comments(&ctx.semantic()); + + let jsx_runtime_importer = + if options.import_source == "react" || options.runtime.is_classic() { + Atom::new_inline("react/jsx-runtime") + } else { + Atom::from(format!("{}/jsx-runtime", options.import_source)) + }; + Self { ast, options, imports, + jsx_runtime_importer, import_jsx: false, import_jsxs: false, import_fragment: false, @@ -111,6 +122,10 @@ impl<'a> ReactJsx<'a> { program.body.splice(index..index, imports); } + fn new_string_literal(name: &str) -> StringLiteral { + StringLiteral::new(SPAN, name.into()) + } + fn add_import<'b>( &mut self, e: &JSXElementOrFragment<'a, 'b>, @@ -133,21 +148,24 @@ impl<'a> ReactJsx<'a> { fn add_import_jsx(&mut self) { if !self.import_jsx { self.import_jsx = true; - self.add_import_statement("jsx", "_jsx", "react/jsx-runtime"); + let source = Self::new_string_literal(self.jsx_runtime_importer.as_str()); + self.add_import_statement("jsx", "_jsx", source); } } fn add_import_jsxs(&mut self) { if !self.import_jsxs { self.import_jsxs = true; - self.add_import_statement("jsxs", "_jsxs", "react/jsx-runtime"); + let source = Self::new_string_literal(self.jsx_runtime_importer.as_str()); + self.add_import_statement("jsxs", "_jsxs", source); } } fn add_import_fragment(&mut self) { if !self.import_fragment { self.import_fragment = true; - self.add_import_statement("Fragment", "_Fragment", "react/jsx-runtime"); + let source = Self::new_string_literal(self.jsx_runtime_importer.as_str()); + self.add_import_statement("Fragment", "_Fragment", source); self.add_import_jsx(); } } @@ -155,11 +173,12 @@ impl<'a> ReactJsx<'a> { fn add_import_create_element(&mut self) { if !self.import_create_element { self.import_create_element = true; - self.add_import_statement("createElement", "_createElement", "react"); + let source = Self::new_string_literal(self.options.import_source.as_ref()); + self.add_import_statement("createElement", "_createElement", source); } } - fn add_import_statement(&mut self, imported: &str, local: &str, source: &str) { + fn add_import_statement(&mut self, imported: &str, local: &str, source: StringLiteral) { let mut specifiers = self.ast.new_vec_with_capacity(1); specifiers.push(ImportDeclarationSpecifier::ImportSpecifier(ImportSpecifier { span: SPAN, @@ -167,7 +186,6 @@ impl<'a> ReactJsx<'a> { local: BindingIdentifier::new(SPAN, local.into()), import_kind: ImportOrExportKind::Value, })); - let source = StringLiteral::new(SPAN, source.into()); let import_statement = self.ast.import_declaration( SPAN, Some(specifiers), diff --git a/crates/oxc_transformer/src/react_jsx/options.rs b/crates/oxc_transformer/src/react_jsx/options.rs index 75cd93f57..73a1efd26 100644 --- a/crates/oxc_transformer/src/react_jsx/options.rs +++ b/crates/oxc_transformer/src/react_jsx/options.rs @@ -1,14 +1,23 @@ -use serde::Deserialize; +use std::borrow::Cow; use oxc_semantic::Semantic; +use serde::Deserialize; -#[derive(Debug, Default, Clone, Copy, Deserialize)] +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ReactJsxOptions { /// Decides which runtime to use. pub runtime: ReactJsxRuntime, /// Toggles whether or not to throw an error if an XML namespaced tag name is used. e.g. `` /// Though the JSX spec allows this, it is disabled by default since React's JSX does not currently have support for it. pub throw_if_namespace: Option, + /// Replaces the import source when importing functions. default to `react` + #[serde(default = "default_import_source")] + pub import_source: Cow<'static, str>, +} + +fn default_import_source() -> Cow<'static, str> { + Cow::Borrowed("react") } #[derive(Debug, Default, Clone, Copy, Deserialize)] diff --git a/crates/oxc_transformer/src/tester.rs b/crates/oxc_transformer/src/tester.rs index ec80ee156..c3419945d 100644 --- a/crates/oxc_transformer/src/tester.rs +++ b/crates/oxc_transformer/src/tester.rs @@ -32,7 +32,8 @@ impl Tester { let program = Parser::new(&self.allocator, source_text, self.source_type).parse().program; let semantic = SemanticBuilder::new(source_text, self.source_type).build(&program).semantic; let program = self.allocator.alloc(program); - Transformer::new(&self.allocator, self.source_type, semantic, self.options).build(program); + Transformer::new(&self.allocator, self.source_type, semantic, self.options.clone()) + .build(program); Codegen::::new(source_text.len(), CodegenOptions).build(program) } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index eebfd1e66..a61f6b26e 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 255/1113 +Passed: 256/1113 # All Passed: * babel-plugin-transform-numeric-separator @@ -825,14 +825,13 @@ Passed: 255/1113 * regression/11061/input.mjs * variable-declaration/non-null-in-optional-chain/input.ts -# babel-plugin-transform-react-jsx (93/170) +# babel-plugin-transform-react-jsx (94/170) * autoImport/after-polyfills-compiled-to-cjs/input.mjs * autoImport/after-polyfills-script-not-supported/input.js * autoImport/auto-import-react-source-type-module/input.js * autoImport/auto-import-react-source-type-script/input.js * autoImport/complicated-scope-module/input.js * autoImport/complicated-scope-script/input.js -* autoImport/import-source/input.js * autoImport/import-source-pragma/input.js * pure/false-default-pragma-automatic-runtime/input.js * pure/false-pragma-comment-automatic-runtime/input.js