perf(transformer): avoid fragment update where possible (#3535)

Due to needing to align output with Babel, React JSX transform has to
perform imports for fragments after dealing with the fragments'
children.

Transform generates a dummy identifier initially, and then generates a
UID later on and replaces the dummy.

The PR avoids that process 2-stage process unless we have to. If the UID
was already generated for a previous fragment, we can just use it
straight away.
This commit is contained in:
overlookmotel 2024-06-05 12:35:07 +01:00 committed by GitHub
parent ac394f0ec3
commit 9f467b8479
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 25 deletions

View file

@ -24,6 +24,13 @@ pub const MAX_INLINE_LEN: usize = 16;
#[cfg_attr(feature = "serialize", serde(transparent))]
pub struct Atom<'a>(&'a str);
impl Atom<'static> {
#[inline]
pub const fn empty() -> Self {
Atom("")
}
}
impl<'a> Atom<'a> {
#[inline]
pub fn as_str(&self) -> &'a str {

View file

@ -416,12 +416,13 @@ impl<'a> ReactJsx<'a> {
let is_development = self.options.development;
let mut arguments = self.ast().new_vec();
arguments.push(Argument::from(match e {
let (argument_expr, fragment_needs_update) = match e {
JSXElementOrFragment::Element(e) => {
self.transform_element_name(&e.opening_element.name, ctx)
(self.transform_element_name(&e.opening_element.name, ctx), false)
}
JSXElementOrFragment::Fragment(_) => self.get_fragment(ctx),
}));
};
arguments.push(Argument::from(argument_expr));
// The key prop in `<div key={true} />`
let mut key_prop = None;
@ -536,7 +537,7 @@ impl<'a> ReactJsx<'a> {
self.add_import(e, has_key_after_props_spread, need_jsxs, ctx);
if is_fragment {
if fragment_needs_update {
self.update_fragment(arguments.first_mut().unwrap(), ctx);
}
@ -639,41 +640,56 @@ impl<'a> ReactJsx<'a> {
}
}
fn get_fragment(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
/// Create fragment expression.
/// `bool` returned is flag for whether identifier is temporary and `update_fragment`
/// needs to be called later.
fn get_fragment(&mut self, ctx: &mut TraverseCtx<'a>) -> (Expression<'a>, bool) {
match self.options.runtime {
ReactJsxRuntime::Classic => self.pragma_frag.as_ref().unwrap().create_expression(ctx),
ReactJsxRuntime::Classic => {
let expr = self.pragma_frag.as_ref().unwrap().create_expression(ctx);
(expr, false) // false = does not need update
}
ReactJsxRuntime::Automatic => {
// "_reactJsxRuntime" and "_Fragment" here are temporary. Will be over-written
// in `update_fragment` after import is added and correct var name is known,
// and correct `reference_id` will be set then.
// Use existing import if exists. Otherwise create temporary identifiers,
// and signal to over-write them later in `update_fragment` after import is added
// and correct var name is known. Correct `reference_id` will also be set then.
// We have to do like this so that imports are in same order as Babel's output,
// in order to pass Babel's tests.
// TODO(improve-on-babel): Remove this workaround if output doesn't need to match
// Babel's exactly.
if self.is_script() {
create_static_member_expression(
create_read_identifier_reference(
SPAN,
Atom::from("_reactJsxRuntime"),
None,
),
Atom::from("Fragment"),
ctx,
)
if let Some(id) = self.import_jsx.as_ref() {
let expr = create_static_member_expression(
id.create_read_reference(ctx),
Atom::from("Fragment"),
ctx,
);
(expr, false) // false = does not need update
} else {
let expr = create_static_member_expression(
create_read_identifier_reference(SPAN, Atom::empty(), None),
Atom::from("Fragment"),
ctx,
);
(expr, true) // true = needs update
}
} else {
let ident =
create_read_identifier_reference(SPAN, Atom::from("_Fragment"), None);
self.ast().identifier_reference_expression(ident)
#[allow(clippy::collapsible_else_if)]
if let Some(id) = self.import_fragment.as_ref() {
let ident = id.create_read_reference(ctx);
let expr = ctx.ast.identifier_reference_expression(ident);
(expr, false) // false = does not need update
} else {
let ident = create_read_identifier_reference(SPAN, Atom::empty(), None);
let expr = ctx.ast.identifier_reference_expression(ident);
(expr, true) // true = needs update
}
}
}
}
}
fn update_fragment(&self, arg: &mut Argument<'a>, ctx: &mut TraverseCtx<'a>) {
if self.options.runtime != ReactJsxRuntime::Automatic {
return;
}
let (id, local_id) = if self.is_script() {
let Argument::StaticMemberExpression(member_expr) = arg else { unreachable!() };
let Expression::Identifier(id) = &mut member_expr.object else {