mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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:
parent
ac394f0ec3
commit
9f467b8479
2 changed files with 48 additions and 25 deletions
|
|
@ -24,6 +24,13 @@ pub const MAX_INLINE_LEN: usize = 16;
|
||||||
#[cfg_attr(feature = "serialize", serde(transparent))]
|
#[cfg_attr(feature = "serialize", serde(transparent))]
|
||||||
pub struct Atom<'a>(&'a str);
|
pub struct Atom<'a>(&'a str);
|
||||||
|
|
||||||
|
impl Atom<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub const fn empty() -> Self {
|
||||||
|
Atom("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Atom<'a> {
|
impl<'a> Atom<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn as_str(&self) -> &'a str {
|
pub fn as_str(&self) -> &'a str {
|
||||||
|
|
|
||||||
|
|
@ -416,12 +416,13 @@ impl<'a> ReactJsx<'a> {
|
||||||
let is_development = self.options.development;
|
let is_development = self.options.development;
|
||||||
|
|
||||||
let mut arguments = self.ast().new_vec();
|
let mut arguments = self.ast().new_vec();
|
||||||
arguments.push(Argument::from(match e {
|
let (argument_expr, fragment_needs_update) = match e {
|
||||||
JSXElementOrFragment::Element(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),
|
JSXElementOrFragment::Fragment(_) => self.get_fragment(ctx),
|
||||||
}));
|
};
|
||||||
|
arguments.push(Argument::from(argument_expr));
|
||||||
|
|
||||||
// The key prop in `<div key={true} />`
|
// The key prop in `<div key={true} />`
|
||||||
let mut key_prop = None;
|
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);
|
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);
|
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 {
|
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::Automatic => {
|
||||||
// "_reactJsxRuntime" and "_Fragment" here are temporary. Will be over-written
|
// Use existing import if exists. Otherwise create temporary identifiers,
|
||||||
// in `update_fragment` after import is added and correct var name is known,
|
// and signal to over-write them later in `update_fragment` after import is added
|
||||||
// and correct `reference_id` will be set then.
|
// 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,
|
// We have to do like this so that imports are in same order as Babel's output,
|
||||||
// in order to pass Babel's tests.
|
// in order to pass Babel's tests.
|
||||||
// TODO(improve-on-babel): Remove this workaround if output doesn't need to match
|
// TODO(improve-on-babel): Remove this workaround if output doesn't need to match
|
||||||
// Babel's exactly.
|
// Babel's exactly.
|
||||||
if self.is_script() {
|
if self.is_script() {
|
||||||
create_static_member_expression(
|
if let Some(id) = self.import_jsx.as_ref() {
|
||||||
create_read_identifier_reference(
|
let expr = create_static_member_expression(
|
||||||
SPAN,
|
id.create_read_reference(ctx),
|
||||||
Atom::from("_reactJsxRuntime"),
|
Atom::from("Fragment"),
|
||||||
None,
|
ctx,
|
||||||
),
|
);
|
||||||
Atom::from("Fragment"),
|
(expr, false) // false = does not need update
|
||||||
ctx,
|
} 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 {
|
} else {
|
||||||
let ident =
|
#[allow(clippy::collapsible_else_if)]
|
||||||
create_read_identifier_reference(SPAN, Atom::from("_Fragment"), None);
|
if let Some(id) = self.import_fragment.as_ref() {
|
||||||
self.ast().identifier_reference_expression(ident)
|
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>) {
|
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 (id, local_id) = if self.is_script() {
|
||||||
let Argument::StaticMemberExpression(member_expr) = arg else { unreachable!() };
|
let Argument::StaticMemberExpression(member_expr) = arg else { unreachable!() };
|
||||||
let Expression::Identifier(id) = &mut member_expr.object else {
|
let Expression::Identifier(id) = &mut member_expr.object else {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue