Skip to content

Commit

Permalink
feat(transformer/react): support development mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Dunqing committed May 10, 2024
1 parent 0ba7778 commit dba8395
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 68 deletions.
149 changes: 117 additions & 32 deletions crates/oxc_transformer/src/react/jsx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,15 @@ impl<'a> ReactJsx<'a> {
let default_runtime = options.runtime;
let jsx_runtime_importer =
if options.import_source == "react" || default_runtime.is_classic() {
CompactStr::from("react/jsx-runtime")
let source =
if options.development { "react/jsx-dev-runtime" } else { "react/jsx-runtime" };
CompactStr::from(source)
} else {
CompactStr::from(format!("{}/jsx-runtime", options.import_source))
CompactStr::from(format!(
"{}/jsx-{}runtime",
options.import_source,
if options.development { "dev-" } else { "" }
))
};

Self {
Expand Down Expand Up @@ -107,6 +113,11 @@ impl<'a> ReactJsx<'a> {
if self.options.import_source != "react" {
self.ctx.error(ImportSourceCannotBeSet);
}

if self.options.is_jsx_source_plugin_enabled() {
program.body.insert(0, self.jsx_source.get_var_file_name_statement());
}

return;
}

Expand All @@ -118,11 +129,19 @@ impl<'a> ReactJsx<'a> {
}

let imports = self.ctx.module_imports.get_import_statements();
let index = program
let mut index = program
.body
.iter()
.rposition(|stmt| matches!(stmt, Statement::ImportDeclaration(_)))
.map_or(0, |i| i + 1);

if self.options.is_jsx_source_plugin_enabled() {
program.body.insert(index, self.jsx_source.get_var_file_name_statement());
if self.ctx.source_type.is_module() {
index += 1;
}
}

program.body.splice(index..index, imports);
}

Expand Down Expand Up @@ -153,17 +172,17 @@ impl<'a> ReactJsx<'a> {
fn add_require_jsx_runtime(&mut self) {
if !self.require_jsx_runtime {
self.require_jsx_runtime = true;
self.add_require_statement(
"_reactJsxRuntime",
self.jsx_runtime_importer.clone(),
false,
);
let variable_name =
if self.options.development { "_reactJsxDevRuntime" } else { "_reactJsxRuntime" };
self.add_require_statement(variable_name, self.jsx_runtime_importer.clone(), false);
}
}

fn add_import_jsx(&mut self) {
if self.is_script() {
self.add_require_jsx_runtime();
} else if self.options.development {
self.add_import_jsx_dev();
} else if !self.import_jsx {
self.import_jsx = true;
self.add_import_statement("jsx", "_jsx", self.jsx_runtime_importer.clone());
Expand All @@ -173,12 +192,23 @@ impl<'a> ReactJsx<'a> {
fn add_import_jsxs(&mut self) {
if self.is_script() {
self.add_require_jsx_runtime();
} else if self.options.development {
self.add_import_jsx_dev();
} else if !self.import_jsxs {
self.import_jsxs = true;
self.add_import_statement("jsxs", "_jsxs", self.jsx_runtime_importer.clone());
}
}

fn add_import_jsx_dev(&mut self) {
if self.is_script() {
self.add_require_jsx_runtime();
} else if !self.import_jsx {
self.import_jsx = true;
self.add_import_statement("jsxDEV", "_jsxDEV", self.jsx_runtime_importer.clone());
}
}

fn add_import_fragment(&mut self) {
if self.is_script() {
self.add_require_jsx_runtime();
Expand Down Expand Up @@ -262,6 +292,7 @@ impl<'a> ReactJsx<'a> {
fn transform_jsx<'b>(&mut self, e: &JSXElementOrFragment<'a, 'b>) -> Expression<'a> {
let is_classic = self.default_runtime.is_classic();
let is_automatic = self.default_runtime.is_automatic();
let is_fragment = matches!(e, JSXElementOrFragment::Fragment(_));
let has_key_after_props_spread = e.has_key_after_props_spread();

let mut arguments = self.ast().new_vec();
Expand All @@ -278,12 +309,6 @@ impl<'a> ReactJsx<'a> {
let attributes = e.attributes();
let attributes_len = attributes.map_or(0, |attrs| attrs.len());

// Add `null` to second argument in classic mode
if is_classic && attributes_len == 0 {
let null_expr = self.ast().literal_null_expression(NullLiteral::new(SPAN));
arguments.push(Argument::from(null_expr));
}

// The object properties for the second argument of `React.createElement`
let mut properties = self.ast().new_vec();

Expand Down Expand Up @@ -364,32 +389,78 @@ impl<'a> ReactJsx<'a> {
}
}

if self.options.is_jsx_self_plugin_enabled() {
if let Some(span) = self_attr_span {
self.jsx_self.report_error(span);
} else {
properties.push(self.jsx_self.get_object_property_kind_for_jsx_plugin());
if has_key_after_props_spread || (!is_fragment && self.options.runtime.is_classic()) {
if self.options.is_jsx_self_plugin_enabled() {
if let Some(span) = self_attr_span {
self.jsx_self.report_error(span);
} else {
properties.push(self.jsx_self.get_object_property_kind_for_jsx_plugin());
}
}
}
if self.options.is_jsx_source_plugin_enabled() {
if let Some(span) = source_attr_span {
self.jsx_source.report_error(span);
} else {
let (line, column) = get_line_column(e.span().start, self.ctx.source_text);
properties
.push(self.jsx_source.get_object_property_kind_for_jsx_plugin(line, column));

if self.options.is_jsx_source_plugin_enabled() {
if let Some(span) = source_attr_span {
self.jsx_source.report_error(span);
} else {
let (line, column) = get_line_column(e.span().start, self.ctx.source_text);
properties.push(
self.jsx_source.get_object_property_kind_for_jsx_plugin(line, column),
);
}
}
}

self.add_import(e, has_key_after_props_spread, need_jsxs);

// Add `null` to second argument in classic mode
if is_classic && properties.len() == 0 && arguments.len() == 1 {
let null_expr = self.ast().literal_null_expression(NullLiteral::new(SPAN));
arguments.push(Argument::from(null_expr));
}

if !properties.is_empty() || is_automatic {
let object_expression = self.ast().object_expression(SPAN, properties, None);
arguments.push(Argument::from(object_expression));
}

if is_automatic && key_prop.is_some() {
arguments.push(Argument::from(self.transform_jsx_attribute_value(key_prop)));
if is_automatic {
if key_prop.is_some() {
arguments.push(Argument::from(self.transform_jsx_attribute_value(key_prop)));
} else if self.options.development && !has_key_after_props_spread {
arguments.push(Argument::from(self.ctx.ast.void_0()));
}

if !has_key_after_props_spread {
if self.options.development {
let literal = self.ctx.ast.boolean_literal(
SPAN,
if is_fragment { false } else { children.len() > 1 },
);
arguments
.push(Argument::from(self.ctx.ast.literal_boolean_expression(literal)));
}

if !is_fragment {
if self.options.is_jsx_source_plugin_enabled() {
if let Some(span) = source_attr_span {
self.jsx_source.report_error(span);
} else {
let (line, column) =
get_line_column(e.span().start, self.ctx.source_text);
let expr = self.jsx_source.get_source_object(line, column);
arguments.push(Argument::from(expr));
}
}

if self.options.is_jsx_self_plugin_enabled() {
if let Some(span) = self_attr_span {
self.jsx_self.report_error(span);
} else {
arguments.push(Argument::from(self.ctx.ast.this_expression(SPAN)));
}
}
}
}
}

if is_classic && !children.is_empty() {
Expand Down Expand Up @@ -445,7 +516,12 @@ impl<'a> ReactJsx<'a> {
}
ReactJsxRuntime::Automatic => {
if self.is_script() {
self.get_static_member_expression("_reactJsxRuntime", "Fragment")
let object_name = if self.options.development {
"_reactJsxDevRuntime"
} else {
"_reactJsxRuntime"
};
self.get_static_member_expression(object_name, "Fragment")
} else {
let ident = IdentifierReference::new(SPAN, "_Fragment".into());
self.ast().identifier_reference_expression(ident)
Expand All @@ -469,21 +545,30 @@ impl<'a> ReactJsx<'a> {
let name = if self.is_script() {
if has_key_after_props_spread {
"createElement"
} else if self.options.development {
"jsxDEV"
} else if jsxs {
"jsxs"
} else {
"jsx"
}
} else if has_key_after_props_spread {
"_createElement"
} else if self.options.development {
"_jsxDEV"
} else if jsxs {
"_jsxs"
} else {
"_jsx"
};
if self.is_script() {
let object_ident_name =
if has_key_after_props_spread { "_react" } else { "_reactJsxRuntime" };
let object_ident_name = if has_key_after_props_spread {
"_react"
} else if self.options.development {
"_reactJsxDevRuntime"
} else {
"_reactJsxRuntime"
};
self.get_static_member_expression(object_ident_name, name)
} else {
let ident = IdentifierReference::new(SPAN, name.into());
Expand Down
20 changes: 3 additions & 17 deletions crates/oxc_transformer/src/react/jsx_source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,11 @@ const FILE_NAME_VAR: &str = "_jsxFileName";
/// TODO: get lineNumber and columnNumber from somewhere
pub struct ReactJsxSource<'a> {
ctx: Ctx<'a>,

/// Has `var _jsxFileName = "";` been added to program.statements?
should_add_jsx_file_name_variable: bool,
}

impl<'a> ReactJsxSource<'a> {
pub fn new(ctx: &Ctx<'a>) -> Self {
Self { ctx: Rc::clone(ctx), should_add_jsx_file_name_variable: false }
}

pub fn transform_program_on_exit(&mut self, program: &mut Program<'a>) {
if !self.should_add_jsx_file_name_variable {
return;
}
let statement = self.get_var_file_name_statement();
program.body.insert(0, statement);
Self { ctx: Rc::clone(ctx) }
}

pub fn transform_jsx_opening_element(&mut self, elem: &mut JSXOpeningElement<'a>) {
Expand All @@ -54,7 +43,6 @@ impl<'a> ReactJsxSource<'a> {
line: usize,
column: usize,
) -> ObjectPropertyKind<'a> {
self.should_add_jsx_file_name_variable = true;
let kind = PropertyKind::Init;
let ident = IdentifierName::new(SPAN, SOURCE.into());
let key = self.ctx.ast.property_key_identifier(ident);
Expand Down Expand Up @@ -84,8 +72,6 @@ impl<'a> ReactJsxSource<'a> {
}
}

self.should_add_jsx_file_name_variable = true;

let key = JSXAttributeName::Identifier(
self.ctx.ast.alloc(self.ctx.ast.jsx_identifier(SPAN, SOURCE.into())),
);
Expand All @@ -98,7 +84,7 @@ impl<'a> ReactJsxSource<'a> {
}

#[allow(clippy::cast_precision_loss)]
fn get_source_object(&self, line: usize, column: usize) -> Expression<'a> {
pub fn get_source_object(&mut self, line: usize, column: usize) -> Expression<'a> {
let kind = PropertyKind::Init;

let filename = {
Expand Down Expand Up @@ -142,7 +128,7 @@ impl<'a> ReactJsxSource<'a> {
self.ctx.ast.object_expression(SPAN, properties, None)
}

fn get_var_file_name_statement(&self) -> Statement<'a> {
pub fn get_var_file_name_statement(&self) -> Statement<'a> {
let var_kind = VariableDeclarationKind::Var;
let id = {
let ident = BindingIdentifier::new(SPAN, FILE_NAME_VAR.into());
Expand Down
5 changes: 0 additions & 5 deletions crates/oxc_transformer/src/react/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,9 @@ impl<'a> React<'a> {
// Transforms
impl<'a> React<'a> {
pub fn transform_program_on_exit(&mut self, program: &mut Program<'a>) {
// TODO: PERF: These two transforms reallocathe program.statements,
// they should be combined so that allocation is computed only once for program.statements.
if self.options.is_jsx_plugin_enabled() {
self.jsx.transform_program_on_exit(program);
}
if self.options.is_jsx_source_plugin_enabled() {
self.jsx.jsx_source.transform_program_on_exit(program);
}
}

pub fn transform_expression(&mut self, expr: &mut Expression<'a>) {
Expand Down
14 changes: 3 additions & 11 deletions tasks/transform_conformance/babel.snap.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Passed: 296/362
Passed: 303/362

# All Passed:
* babel-preset-react
* babel-plugin-transform-react-display-name
* babel-plugin-transform-react-jsx-source

Expand Down Expand Up @@ -62,9 +63,6 @@ Passed: 296/362
* optimize-const-enums/merged-exported/input.ts
* regression/15768/input.ts

# babel-preset-react (8/9)
* preset-options/development-runtime-automatic/input.js

# babel-plugin-transform-react-jsx (141/143)
* autoImport/complicated-scope-module/input.js
* react-automatic/should-throw-when-filter-is-specified/input.js
Expand All @@ -73,13 +71,7 @@ Passed: 296/362
* react-source/arrow-function/input.js
* react-source/disable-with-super/input.js

# babel-plugin-transform-react-jsx-development (2/12)
* cross-platform/auto-import-dev/input.js
* cross-platform/classic-runtime/input.js
* cross-platform/fragments/input.js
* cross-platform/handle-fragments-with-key/input.js
* cross-platform/handle-nonstatic-children/input.js
* cross-platform/handle-static-children/input.js
# babel-plugin-transform-react-jsx-development (8/12)
* cross-platform/self-inside-arrow/input.mjs
* cross-platform/source-and-self-defined/input.js
* cross-platform/within-derived-classes-constructor/input.js
Expand Down
11 changes: 8 additions & 3 deletions tasks/transform_conformance/src/test_case.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,14 @@ fn transform_options(options: &BabelOptions) -> serde_json::Result<TransformOpti
get_options::<ReactOptions>(options)?
} else {
let jsx_plugin = options.get_plugin("transform-react-jsx");
let has_jsx_plugin = jsx_plugin.as_ref().is_some();
let mut react_options =
jsx_plugin.map(get_options::<ReactOptions>).transpose()?.unwrap_or_default();
let jsx_development_plugin = options.get_plugin("transform-react-jsx-development");
let has_jsx_plugin =
jsx_plugin.as_ref().is_some() || jsx_development_plugin.as_ref().is_some();
let mut react_options = jsx_plugin
.map(get_options::<ReactOptions>)
.or_else(|| jsx_development_plugin.map(get_options::<ReactOptions>))
.transpose()?
.unwrap_or_default();
react_options.development = options.get_plugin("transform-react-jsx-development").is_some();
react_options.jsx_plugin = has_jsx_plugin;
react_options.display_name_plugin =
Expand Down

0 comments on commit dba8395

Please sign in to comment.