mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
perf(transformer): JSX parse pragma only once (#3534)
Currently React JSX transform in classic mode with a custom `options.pragma` value parses `options.pragma` on every JSX node, and inserts new `Atom`s into arena every time. This PR makes it parse `options.pragma` only once at the start of the transform pass, and re-use the same `Atom`s for each JSX node.
This commit is contained in:
parent
6978269be0
commit
ac394f0ec3
1 changed files with 56 additions and 48 deletions
|
|
@ -50,6 +50,9 @@ pub struct ReactJsx<'a> {
|
|||
import_jsxs: Option<BoundIdentifier<'a>>,
|
||||
import_fragment: Option<BoundIdentifier<'a>>,
|
||||
|
||||
pragma: Option<Pragma<'a>>,
|
||||
pragma_frag: Option<Pragma<'a>>,
|
||||
|
||||
can_add_filename_statement: bool,
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +73,32 @@ impl<'a> BoundIdentifier<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
struct Pragma<'a> {
|
||||
object: Atom<'a>,
|
||||
property: Option<Atom<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Pragma<'a> {
|
||||
fn parse(pragma: &str, ast: &AstBuilder<'a>) -> Self {
|
||||
let mut parts = pragma.split('.');
|
||||
let object = ast.new_atom(parts.next().unwrap());
|
||||
let property = parts.next().map(|property| {
|
||||
assert!(parts.next().is_none(), "Invalid pragma");
|
||||
ast.new_atom(property)
|
||||
});
|
||||
Self { object, property }
|
||||
}
|
||||
|
||||
fn create_expression(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||
let object = get_read_identifier_reference(SPAN, self.object.clone(), ctx);
|
||||
if let Some(property) = self.property.as_ref() {
|
||||
create_static_member_expression(object, property.clone(), ctx)
|
||||
} else {
|
||||
ctx.ast.identifier_reference_expression(object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transforms
|
||||
impl<'a> ReactJsx<'a> {
|
||||
pub fn new(options: &Rc<ReactOptions>, ctx: &Ctx<'a>) -> Self {
|
||||
|
|
@ -87,6 +116,16 @@ impl<'a> ReactJsx<'a> {
|
|||
))
|
||||
};
|
||||
|
||||
// Parse pragmas
|
||||
let (pragma, pragma_frag) = match options.runtime {
|
||||
ReactJsxRuntime::Classic => {
|
||||
let pragma = Pragma::parse(&options.pragma, &ctx.ast);
|
||||
let pragma_frag = Pragma::parse(&options.pragma_frag, &ctx.ast);
|
||||
(Some(pragma), Some(pragma_frag))
|
||||
}
|
||||
ReactJsxRuntime::Automatic => (None, None),
|
||||
};
|
||||
|
||||
Self {
|
||||
options: Rc::clone(options),
|
||||
ctx: Rc::clone(ctx),
|
||||
|
|
@ -97,6 +136,8 @@ impl<'a> ReactJsx<'a> {
|
|||
import_jsx: None,
|
||||
import_jsxs: None,
|
||||
import_fragment: None,
|
||||
pragma,
|
||||
pragma_frag,
|
||||
can_add_filename_statement: false,
|
||||
}
|
||||
}
|
||||
|
|
@ -600,16 +641,7 @@ impl<'a> ReactJsx<'a> {
|
|||
|
||||
fn get_fragment(&self, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
||||
match self.options.runtime {
|
||||
ReactJsxRuntime::Classic => {
|
||||
if self.options.pragma_frag == "React.Fragment" {
|
||||
self.get_static_member_expression(
|
||||
get_read_identifier_reference(SPAN, Atom::from("React"), ctx),
|
||||
Atom::from("Fragment"),
|
||||
)
|
||||
} else {
|
||||
self.get_call_expression_callee(self.options.pragma_frag.as_ref(), ctx)
|
||||
}
|
||||
}
|
||||
ReactJsxRuntime::Classic => self.pragma_frag.as_ref().unwrap().create_expression(ctx),
|
||||
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,
|
||||
|
|
@ -619,13 +651,14 @@ impl<'a> ReactJsx<'a> {
|
|||
// TODO(improve-on-babel): Remove this workaround if output doesn't need to match
|
||||
// Babel's exactly.
|
||||
if self.is_script() {
|
||||
self.get_static_member_expression(
|
||||
create_static_member_expression(
|
||||
create_read_identifier_reference(
|
||||
SPAN,
|
||||
Atom::from("_reactJsxRuntime"),
|
||||
None,
|
||||
),
|
||||
Atom::from("Fragment"),
|
||||
ctx,
|
||||
)
|
||||
} else {
|
||||
let ident =
|
||||
|
|
@ -667,16 +700,7 @@ impl<'a> ReactJsx<'a> {
|
|||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
match self.options.runtime {
|
||||
ReactJsxRuntime::Classic => {
|
||||
if self.options.pragma == "React.createElement" {
|
||||
self.get_static_member_expression(
|
||||
get_read_identifier_reference(SPAN, Atom::from("React"), ctx),
|
||||
Atom::from("createElement"),
|
||||
)
|
||||
} else {
|
||||
self.get_call_expression_callee(self.options.pragma.as_ref(), ctx)
|
||||
}
|
||||
}
|
||||
ReactJsxRuntime::Classic => self.pragma.as_ref().unwrap().create_expression(ctx),
|
||||
ReactJsxRuntime::Automatic => {
|
||||
if self.is_script() {
|
||||
let (object_id, property_name) = if has_key_after_props_spread {
|
||||
|
|
@ -692,7 +716,7 @@ impl<'a> ReactJsx<'a> {
|
|||
(self.import_jsx.as_ref().unwrap(), property_name)
|
||||
};
|
||||
let ident = object_id.create_read_reference(ctx);
|
||||
self.get_static_member_expression(ident, property_name)
|
||||
create_static_member_expression(ident, property_name, ctx)
|
||||
} else {
|
||||
let id = if has_key_after_props_spread {
|
||||
self.import_create_element.as_ref().unwrap()
|
||||
|
|
@ -708,32 +732,6 @@ impl<'a> ReactJsx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_static_member_expression(
|
||||
&self,
|
||||
object_ident: IdentifierReference<'a>,
|
||||
property_name: Atom<'a>,
|
||||
) -> Expression<'a> {
|
||||
let object = self.ast().identifier_reference_expression(object_ident);
|
||||
let property = IdentifierName::new(SPAN, property_name);
|
||||
self.ast().static_member_expression(SPAN, object, property, false)
|
||||
}
|
||||
|
||||
/// Get the callee from `pragma` and `pragmaFrag`
|
||||
fn get_call_expression_callee(
|
||||
&self,
|
||||
literal_callee: &str,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let mut callee = literal_callee.split('.');
|
||||
let member_name = self.ast().new_atom(callee.next().unwrap());
|
||||
let member = get_read_identifier_reference(SPAN, member_name, ctx);
|
||||
if let Some(property_name) = callee.next() {
|
||||
self.get_static_member_expression(member, self.ast().new_atom(property_name))
|
||||
} else {
|
||||
self.ast().identifier_reference_expression(member)
|
||||
}
|
||||
}
|
||||
|
||||
fn transform_jsx_member_expression(
|
||||
&self,
|
||||
expr: &JSXMemberExpression<'a>,
|
||||
|
|
@ -972,3 +970,13 @@ fn create_read_identifier_reference(
|
|||
reference_flag: ReferenceFlag::Read,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_static_member_expression<'a>(
|
||||
object_ident: IdentifierReference<'a>,
|
||||
property_name: Atom<'a>,
|
||||
ctx: &TraverseCtx<'a>,
|
||||
) -> Expression<'a> {
|
||||
let object = ctx.ast.identifier_reference_expression(object_ident);
|
||||
let property = IdentifierName::new(SPAN, property_name);
|
||||
ctx.ast.static_member_expression(SPAN, object, property, false)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue