perf(transformer): parse options from comments only once (#6152)

Both React and TypeScript transforms were repeating the same work - iterating through comments to find `@jsx` pragmas.

Instead, perform this search only once, using the optimized pragma search introduced in #6151.
This commit is contained in:
overlookmotel 2024-10-01 09:57:46 +00:00
parent 54c1c53e69
commit 788e444c1f
5 changed files with 32 additions and 53 deletions

View file

@ -87,7 +87,7 @@ impl<'a> Transformer<'a> {
} }
pub fn build_with_symbols_and_scopes( pub fn build_with_symbols_and_scopes(
self, mut self,
symbols: SymbolTable, symbols: SymbolTable,
scopes: ScopeTree, scopes: ScopeTree,
program: &mut Program<'a>, program: &mut Program<'a>,
@ -95,8 +95,10 @@ impl<'a> Transformer<'a> {
let allocator = self.allocator; let allocator = self.allocator;
let ast_builder = AstBuilder::new(allocator); let ast_builder = AstBuilder::new(allocator);
react::update_options_with_comments(&mut self.options, &self.ctx);
let mut transformer = TransformerImpl { let mut transformer = TransformerImpl {
x0_typescript: TypeScript::new(self.options.typescript, &self.ctx), x0_typescript: TypeScript::new(&self.options.typescript, &self.ctx),
x1_react: React::new(self.options.react, ast_builder, &self.ctx), x1_react: React::new(self.options.react, ast_builder, &self.ctx),
x2_es2021: ES2021::new(self.options.es2021, &self.ctx), x2_es2021: ES2021::new(self.options.es2021, &self.ctx),
x2_es2020: ES2020::new(self.options.es2020, &self.ctx), x2_es2020: ES2020::new(self.options.es2020, &self.ctx),

View file

@ -1,7 +1,9 @@
use std::borrow::Cow;
use oxc_ast::{Comment, CommentKind}; use oxc_ast::{Comment, CommentKind};
use oxc_syntax::identifier::is_irregular_whitespace; use oxc_syntax::identifier::is_irregular_whitespace;
use crate::{JsxOptions, JsxRuntime, TransformCtx}; use crate::{JsxRuntime, TransformCtx, TransformOptions};
/// Scan through all comments and find the following pragmas: /// Scan through all comments and find the following pragmas:
/// ///
@ -14,23 +16,32 @@ use crate::{JsxOptions, JsxRuntime, TransformCtx};
/// otherwise `JSDoc` could be used instead. /// otherwise `JSDoc` could be used instead.
/// ///
/// This behavior is aligned with Babel. /// This behavior is aligned with Babel.
pub(crate) fn update_options_with_comments(options: &mut JsxOptions, ctx: &TransformCtx) { pub(crate) fn update_options_with_comments(options: &mut TransformOptions, ctx: &TransformCtx) {
for comment in ctx.trivias.comments() { for comment in ctx.trivias.comments() {
update_options_with_comment(options, comment, ctx.source_text); update_options_with_comment(options, comment, ctx.source_text);
} }
} }
fn update_options_with_comment(options: &mut JsxOptions, comment: &Comment, source_text: &str) { fn update_options_with_comment(
options: &mut TransformOptions,
comment: &Comment,
source_text: &str,
) {
let Some((keyword, remainder)) = find_jsx_pragma(comment, source_text) else { return }; let Some((keyword, remainder)) = find_jsx_pragma(comment, source_text) else { return };
match keyword { match keyword {
// @jsx // @jsx
"" => { "" => {
options.pragma = Some(remainder.to_string()); // Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new`
if options.react.jsx_plugin || options.react.development {
options.react.pragma = Some(remainder.to_string());
}
options.typescript.jsx_pragma = Cow::from(remainder.to_string());
} }
// @jsxRuntime // @jsxRuntime
"Runtime" => { "Runtime" => {
options.runtime = match remainder { options.react.runtime = match remainder {
"classic" => JsxRuntime::Classic, "classic" => JsxRuntime::Classic,
"automatic" => JsxRuntime::Automatic, "automatic" => JsxRuntime::Automatic,
_ => return, _ => return,
@ -38,11 +49,16 @@ fn update_options_with_comment(options: &mut JsxOptions, comment: &Comment, sour
} }
// @jsxImportSource // @jsxImportSource
"ImportSource" => { "ImportSource" => {
options.import_source = Some(remainder.to_string()); options.react.import_source = Some(remainder.to_string());
} }
// @jsxFrag // @jsxFrag
"Frag" => { "Frag" => {
options.pragma_frag = Some(remainder.to_string()); // Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new`
if options.react.jsx_plugin || options.react.development {
options.react.pragma_frag = Some(remainder.to_string());
}
options.typescript.jsx_pragma_frag = Cow::from(remainder.to_string());
} }
_ => {} _ => {}
} }

View file

@ -20,7 +20,7 @@ pub use self::{
}; };
use crate::TransformCtx; use crate::TransformCtx;
use comments::update_options_with_comments; pub(crate) use comments::update_options_with_comments;
/// [Preset React](https://babel.dev/docs/babel-preset-react) /// [Preset React](https://babel.dev/docs/babel-preset-react)
/// ///
@ -45,7 +45,6 @@ pub struct React<'a, 'ctx> {
impl<'a, 'ctx> React<'a, 'ctx> { impl<'a, 'ctx> React<'a, 'ctx> {
pub fn new(mut options: JsxOptions, ast: AstBuilder<'a>, ctx: &'ctx TransformCtx<'a>) -> Self { pub fn new(mut options: JsxOptions, ast: AstBuilder<'a>, ctx: &'ctx TransformCtx<'a>) -> Self {
if options.jsx_plugin || options.development { if options.jsx_plugin || options.development {
update_options_with_comments(&mut options, ctx);
options.conform(); options.conform();
} }
let JsxOptions { let JsxOptions {

View file

@ -49,15 +49,14 @@ pub struct TypeScript<'a, 'ctx> {
} }
impl<'a, 'ctx> TypeScript<'a, 'ctx> { impl<'a, 'ctx> TypeScript<'a, 'ctx> {
pub fn new(mut options: TypeScriptOptions, ctx: &'ctx TransformCtx<'a>) -> Self { pub fn new(options: &TypeScriptOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
options.update_with_comments(ctx);
Self { Self {
ctx, ctx,
annotations: TypeScriptAnnotations::new(&options, ctx), annotations: TypeScriptAnnotations::new(options, ctx),
r#enum: TypeScriptEnum::new(), r#enum: TypeScriptEnum::new(),
namespace: TypeScriptNamespace::new(&options, ctx), namespace: TypeScriptNamespace::new(options, ctx),
module: TypeScriptModule::new(ctx), module: TypeScriptModule::new(ctx),
rewrite_extensions: TypeScriptRewriteExtensions::new(&options), rewrite_extensions: TypeScriptRewriteExtensions::new(options),
} }
} }
} }

View file

@ -5,8 +5,6 @@ use serde::{
Deserialize, Deserializer, Deserialize, Deserializer,
}; };
use crate::context::TransformCtx;
fn default_for_jsx_pragma() -> Cow<'static, str> { fn default_for_jsx_pragma() -> Cow<'static, str> {
Cow::Borrowed("React.createElement") Cow::Borrowed("React.createElement")
} }
@ -60,41 +58,6 @@ pub struct TypeScriptOptions {
pub rewrite_import_extensions: Option<RewriteExtensionsMode>, pub rewrite_import_extensions: Option<RewriteExtensionsMode>,
} }
impl TypeScriptOptions {
/// Scan through all comments and find the following pragmas
///
/// * @jsx React.createElement
/// * @jsxFrag React.Fragment
///
/// The comment does not need to be a jsdoc,
/// otherwise `JSDoc` could be used instead.
///
/// This behavior is aligned with babel.
pub(crate) fn update_with_comments(&mut self, ctx: &TransformCtx) {
for comment in ctx.trivias.comments() {
let mut comment = comment.span.source_text(ctx.source_text).trim_start();
// strip leading jsdoc comment `*` and then whitespaces
while let Some(cur_comment) = comment.strip_prefix('*') {
comment = cur_comment.trim_start();
}
// strip leading `@`
let Some(comment) = comment.strip_prefix('@') else { continue };
// read jsxFrag
if let Some(pragma_frag) = comment.strip_prefix("jsxFrag").map(str::trim) {
self.jsx_pragma_frag = Cow::from(pragma_frag.to_string());
continue;
}
// Put this condition at the end to avoid breaking @jsxXX
// read jsx
if let Some(pragma) = comment.strip_prefix("jsx").map(str::trim) {
self.jsx_pragma = Cow::from(pragma.to_string());
}
}
}
}
impl Default for TypeScriptOptions { impl Default for TypeScriptOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {