mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
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:
parent
f3a755c3dc
commit
91519d9e67
2 changed files with 44 additions and 45 deletions
|
|
@ -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 = {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue