perf(transformer): React JSX reduce allocations (#3522)

React JSX transform was creating `CompactStr`s (which can involve heap allocations) and then converting them back to `Atom`s to insert into AST.

This PR removes the intermediate `CompactStr`s and allocates strings directly into the arena without intermediate heap allocations.
This commit is contained in:
overlookmotel 2024-06-04 03:02:42 +00:00
parent f3a755c3dc
commit 91519d9e67
2 changed files with 44 additions and 45 deletions

View file

@ -3,15 +3,15 @@ use std::cell::RefCell;
use oxc_allocator::{Allocator, Vec}; use oxc_allocator::{Allocator, Vec};
use oxc_ast::{ast::*, AstBuilder}; use oxc_ast::{ast::*, AstBuilder};
use oxc_span::{CompactStr, SPAN}; use oxc_span::{Atom, SPAN};
pub struct NamedImport { pub struct NamedImport<'a> {
imported: CompactStr, imported: Atom<'a>,
local: Option<CompactStr>, // Not used in `require` local: Option<Atom<'a>>, // Not used in `require`
} }
impl NamedImport { impl<'a> NamedImport<'a> {
pub fn new(imported: CompactStr, local: Option<CompactStr>) -> NamedImport { pub fn new(imported: Atom<'a>, local: Option<Atom<'a>>) -> Self {
Self { imported, local } Self { imported, local }
} }
} }
@ -23,13 +23,13 @@ pub enum ImportKind {
} }
#[derive(Hash, Eq, PartialEq)] #[derive(Hash, Eq, PartialEq)]
pub struct ImportType { pub struct ImportType<'a> {
kind: ImportKind, kind: ImportKind,
source: CompactStr, source: Atom<'a>,
} }
impl ImportType { impl<'a> ImportType<'a> {
fn new(kind: ImportKind, source: CompactStr) -> Self { fn new(kind: ImportKind, source: Atom<'a>) -> Self {
Self { kind, source } Self { kind, source }
} }
} }
@ -39,7 +39,7 @@ impl ImportType {
pub struct ModuleImports<'a> { pub struct ModuleImports<'a> {
ast: AstBuilder<'a>, ast: AstBuilder<'a>,
imports: RefCell<IndexMap<ImportType, std::vec::Vec<NamedImport>>>, imports: RefCell<IndexMap<ImportType<'a>, std::vec::Vec<NamedImport<'a>>>>,
} }
impl<'a> ModuleImports<'a> { impl<'a> ModuleImports<'a> {
@ -49,7 +49,7 @@ impl<'a> ModuleImports<'a> {
} }
/// Add `import { named_import } from 'source'` /// Add `import { named_import } from 'source'`
pub fn add_import(&self, source: CompactStr, import: NamedImport) { pub fn add_import(&self, source: Atom<'a>, import: NamedImport<'a>) {
self.imports self.imports
.borrow_mut() .borrow_mut()
.entry(ImportType::new(ImportKind::Import, source)) .entry(ImportType::new(ImportKind::Import, source))
@ -58,7 +58,7 @@ impl<'a> ModuleImports<'a> {
} }
/// Add `var named_import from 'source'` /// Add `var named_import from 'source'`
pub fn add_require(&self, source: CompactStr, import: NamedImport, front: bool) { pub fn add_require(&self, source: Atom<'a>, import: NamedImport<'a>, front: bool) {
let len = self.imports.borrow().len(); let len = self.imports.borrow().len();
self.imports self.imports
.borrow_mut() .borrow_mut()
@ -73,55 +73,54 @@ impl<'a> ModuleImports<'a> {
pub fn get_import_statements(&self) -> Vec<'a, Statement<'a>> { pub fn get_import_statements(&self) -> Vec<'a, Statement<'a>> {
self.ast.new_vec_from_iter(self.imports.borrow_mut().drain(..).map( self.ast.new_vec_from_iter(self.imports.borrow_mut().drain(..).map(
|(import_type, names)| match import_type.kind { |(import_type, names)| match import_type.kind {
ImportKind::Import => self.get_named_import(&import_type.source, names), ImportKind::Import => self.get_named_import(import_type.source, names),
ImportKind::Require => self.get_require(&import_type.source, names), ImportKind::Require => self.get_require(import_type.source, names),
}, },
)) ))
} }
fn get_named_import( fn get_named_import(
&self, &self,
source: &CompactStr, source: Atom<'a>,
names: std::vec::Vec<NamedImport>, names: std::vec::Vec<NamedImport<'a>>,
) -> Statement<'a> { ) -> Statement<'a> {
let specifiers = self.ast.new_vec_from_iter(names.into_iter().map(|name| { let specifiers = self.ast.new_vec_from_iter(names.into_iter().map(|name| {
let local = name.local.unwrap_or_else(|| name.imported.clone());
ImportDeclarationSpecifier::ImportSpecifier(self.ast.alloc(ImportSpecifier { ImportDeclarationSpecifier::ImportSpecifier(self.ast.alloc(ImportSpecifier {
span: SPAN, span: SPAN,
imported: ModuleExportName::Identifier(IdentifierName::new( imported: ModuleExportName::Identifier(IdentifierName::new(SPAN, name.imported)),
SPAN, local: BindingIdentifier::new(SPAN, local),
self.ast.new_atom(name.imported.as_str()),
)),
local: BindingIdentifier::new(
SPAN,
self.ast.new_atom(name.local.unwrap_or(name.imported).as_str()),
),
import_kind: ImportOrExportKind::Value, import_kind: ImportOrExportKind::Value,
})) }))
})); }));
let import_stmt = self.ast.import_declaration( let import_stmt = self.ast.import_declaration(
SPAN, SPAN,
Some(specifiers), Some(specifiers),
self.ast.string_literal(SPAN, source.as_str()), StringLiteral::new(SPAN, source),
None, None,
ImportOrExportKind::Value, ImportOrExportKind::Value,
); );
self.ast.module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt)) self.ast.module_declaration(ModuleDeclaration::ImportDeclaration(import_stmt))
} }
fn get_require(&self, source: &CompactStr, names: std::vec::Vec<NamedImport>) -> Statement<'a> { fn get_require(
&self,
source: Atom<'a>,
names: std::vec::Vec<NamedImport<'a>>,
) -> Statement<'a> {
let var_kind = VariableDeclarationKind::Var; let var_kind = VariableDeclarationKind::Var;
let callee = { let callee = {
let ident = IdentifierReference::new(SPAN, "require".into()); let ident = IdentifierReference::new(SPAN, Atom::from("require"));
self.ast.identifier_reference_expression(ident) self.ast.identifier_reference_expression(ident)
}; };
let args = { let args = {
let string = self.ast.string_literal(SPAN, source.as_str()); let string = StringLiteral::new(SPAN, source);
let arg = Argument::from(self.ast.literal_string_expression(string)); let arg = Argument::from(self.ast.literal_string_expression(string));
self.ast.new_vec_single(arg) self.ast.new_vec_single(arg)
}; };
let name = names.into_iter().next().unwrap(); let name = names.into_iter().next().unwrap();
let id = { let id = {
let ident = BindingIdentifier::new(SPAN, self.ast.new_atom(&name.imported)); let ident = BindingIdentifier::new(SPAN, name.imported);
self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false) self.ast.binding_pattern(self.ast.binding_pattern_identifier(ident), None, false)
}; };
let decl = { let decl = {

View file

@ -4,7 +4,7 @@ use std::rc::Rc;
use oxc_allocator::Vec; use oxc_allocator::Vec;
use oxc_ast::{ast::*, AstBuilder}; use oxc_ast::{ast::*, AstBuilder};
use oxc_span::{Atom, CompactStr, GetSpan, Span, SPAN}; use oxc_span::{Atom, GetSpan, Span, SPAN};
use oxc_syntax::{ use oxc_syntax::{
identifier::{is_irregular_whitespace, is_line_terminator}, identifier::{is_irregular_whitespace, is_line_terminator},
symbol::SymbolFlags, symbol::SymbolFlags,
@ -40,7 +40,7 @@ pub struct ReactJsx<'a> {
pub(super) jsx_source: ReactJsxSource<'a>, pub(super) jsx_source: ReactJsxSource<'a>,
// States // States
jsx_runtime_importer: CompactStr, jsx_runtime_importer: Atom<'a>,
// Doubles as var name for require react // Doubles as var name for require react
import_create_element: Option<Atom<'a>>, import_create_element: Option<Atom<'a>>,
@ -60,9 +60,9 @@ impl<'a> ReactJsx<'a> {
if options.import_source == "react" || default_runtime.is_classic() { if options.import_source == "react" || default_runtime.is_classic() {
let source = let source =
if options.development { "react/jsx-dev-runtime" } else { "react/jsx-runtime" }; if options.development { "react/jsx-dev-runtime" } else { "react/jsx-runtime" };
CompactStr::from(source) Atom::from(source)
} else { } else {
CompactStr::from(format!( ctx.ast.new_atom(&format!(
"{}/jsx-{}runtime", "{}/jsx-{}runtime",
options.import_source, options.import_source,
if options.development { "dev-" } else { "" } if options.development { "dev-" } else { "" }
@ -234,11 +234,11 @@ impl<'a> ReactJsx<'a> {
fn add_import_create_element(&mut self, ctx: &mut TraverseCtx<'a>) { fn add_import_create_element(&mut self, ctx: &mut TraverseCtx<'a>) {
if self.import_create_element.is_none() { if self.import_create_element.is_none() {
let source = self.options.import_source.as_ref(); let source = ctx.ast.new_atom(&self.options.import_source);
let var_name = if self.is_script() { let var_name = if self.is_script() {
self.add_require_statement("react", source.into(), true, ctx) self.add_require_statement("react", source, true, ctx)
} else { } else {
self.add_import_statement("createElement", source.into(), ctx) self.add_import_statement("createElement", source, ctx)
}; };
self.import_create_element = Some(var_name); self.import_create_element = Some(var_name);
} }
@ -246,34 +246,34 @@ impl<'a> ReactJsx<'a> {
fn add_import_statement( fn add_import_statement(
&mut self, &mut self,
name: &str, name: &'static str,
source: CompactStr, source: Atom<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Atom<'a> { ) -> Atom<'a> {
let root_scope_id = ctx.scopes().root_scope_id(); let root_scope_id = ctx.scopes().root_scope_id();
let symbol_id = ctx.generate_uid(name, root_scope_id, SymbolFlags::FunctionScopedVariable); let symbol_id = ctx.generate_uid(name, root_scope_id, SymbolFlags::FunctionScopedVariable);
let local = &ctx.symbols().names[symbol_id]; let local = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
let import = NamedImport::new(name.into(), Some(local.clone())); let import = NamedImport::new(Atom::from(name), Some(local.clone()));
self.ctx.module_imports.add_import(source, import); self.ctx.module_imports.add_import(source, import);
ctx.ast.new_atom(local) local
} }
fn add_require_statement( fn add_require_statement(
&mut self, &mut self,
variable_name: &str, variable_name: &str,
source: CompactStr, source: Atom<'a>,
front: bool, front: bool,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Atom<'a> { ) -> Atom<'a> {
let root_scope_id = ctx.scopes().root_scope_id(); let root_scope_id = ctx.scopes().root_scope_id();
let symbol_id = let symbol_id =
ctx.generate_uid(variable_name, root_scope_id, SymbolFlags::FunctionScopedVariable); ctx.generate_uid(variable_name, root_scope_id, SymbolFlags::FunctionScopedVariable);
let variable_name = &ctx.symbols().names[symbol_id]; let variable_name = ctx.ast.new_atom(&ctx.symbols().names[symbol_id]);
let import = NamedImport::new(variable_name.clone(), None); let import = NamedImport::new(variable_name.clone(), None);
self.ctx.module_imports.add_require(source, import, front); self.ctx.module_imports.add_require(source, import, front);
ctx.ast.new_atom(variable_name) variable_name
} }
} }