From 5af68d67b675003d9c8c08006b2df86bc86b0b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Thu, 11 Jul 2024 15:37:16 +0900 Subject: [PATCH] perf: Merge multiple `EsmBinding` (vercel/turbo#8699) ### Description Merge multiple `EsmBinding` into single `CodeGeneratable` ### Testing Instructions This is a pure optimization, so if the behavior is same it's all good. Closes PACK-3147 --- .../src/references/esm/binding.rs | 201 ++++++++++-------- .../src/references/mod.rs | 19 +- 2 files changed, 125 insertions(+), 95 deletions(-) diff --git a/crates/turbopack-ecmascript/src/references/esm/binding.rs b/crates/turbopack-ecmascript/src/references/esm/binding.rs index e1aac2cf3e50c..c817b5dcfe512 100644 --- a/crates/turbopack-ecmascript/src/references/esm/binding.rs +++ b/crates/turbopack-ecmascript/src/references/esm/binding.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use serde::{Deserialize, Serialize}; use swc_core::{ common::{Span, SyntaxContext}, ecma::{ @@ -6,95 +7,64 @@ use swc_core::{ ComputedPropName, Expr, Ident, KeyValueProp, Lit, MemberExpr, MemberProp, Number, Prop, PropName, SeqExpr, SimpleAssignTarget, Str, }, - visit::fields::{CalleeField, PropField}, + visit::{ + fields::{CalleeField, PropField}, + AstParentKind, + }, }, }; -use turbo_tasks::{RcStr, Vc}; +use turbo_tasks::{trace::TraceRawVcs, RcStr, TaskInput, Vc}; use turbopack_core::chunk::ChunkingContext; use super::EsmAssetReference; use crate::{ - code_gen::{CodeGenerateable, CodeGeneration}, + code_gen::{CodeGenerateable, CodeGeneration, VisitorFactory}, create_visitor, references::AstPath, }; #[turbo_tasks::value(shared)] #[derive(Hash, Debug)] +pub struct EsmBindings { + pub bindings: Vec, +} + +#[turbo_tasks::value_impl] +impl EsmBindings { + #[turbo_tasks::function] + pub fn new(bindings: Vec) -> Vc { + EsmBindings { bindings }.cell() + } +} + +#[derive(Hash, Clone, Debug, TaskInput, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs)] pub struct EsmBinding { pub reference: Vc, pub export: Option, pub ast_path: Vc, } -#[turbo_tasks::value_impl] impl EsmBinding { - #[turbo_tasks::function] pub fn new( reference: Vc, export: Option, ast_path: Vc, - ) -> Vc { + ) -> Self { EsmBinding { reference, export, ast_path, } - .cell() } -} -#[turbo_tasks::value_impl] -impl CodeGenerateable for EsmBinding { - #[turbo_tasks::function] - async fn code_generation( - self: Vc, - _context: Vc>, - ) -> Result> { - let this = self.await?; - let mut visitors = Vec::new(); - let imported_module = this.reference.get_referenced_asset(); - - fn make_expr( - imported_module: &str, - export: Option<&str>, - span: Span, - in_call: bool, - ) -> Expr { - let span = span.with_ctxt(SyntaxContext::empty()); - if let Some(export) = export { - let mut expr = Expr::Member(MemberExpr { - span, - obj: Box::new(Expr::Ident(Ident::new(imported_module.into(), span))), - prop: MemberProp::Computed(ComputedPropName { - span, - expr: Box::new(Expr::Lit(Lit::Str(Str { - span, - value: export.into(), - raw: None, - }))), - }), - }); - if in_call { - expr = Expr::Seq(SeqExpr { - exprs: vec![ - Box::new(Expr::Lit(Lit::Num(Number { - span, - value: 0.0, - raw: None, - }))), - Box::new(expr), - ], - span, - }); - } - expr - } else { - Expr::Ident(Ident::new(imported_module.into(), span)) - } - } + async fn to_visitors( + &self, + visitors: &mut Vec<(Vec, Box)>, + ) -> Result<()> { + let item = self.clone(); + let imported_module = self.reference.get_referenced_asset(); - let mut ast_path = this.ast_path.await?.clone_value(); + let mut ast_path = item.ast_path.await?.clone_value(); let imported_module = imported_module.await?.get_ident().await?; loop { @@ -104,18 +74,17 @@ impl CodeGenerateable for EsmBinding { Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => { ast_path.pop(); visitors.push( - create_visitor!(exact ast_path, visit_mut_prop(prop: &mut Prop) { - if let Prop::Shorthand(ident) = prop { - // TODO: Merge with the above condition when https://rust-lang.github.io/rfcs/2497-if-let-chains.html lands. - if let Some(imported_ident) = imported_module.as_deref() { - *prop = Prop::KeyValue(KeyValueProp { - key: PropName::Ident(ident.clone()), - value: Box::new(make_expr(imported_ident, this.export.as_deref(), ident.span, false)) - }); - } + create_visitor!(exact ast_path, visit_mut_prop(prop: &mut Prop) { + if let Prop::Shorthand(ident) = prop { + // TODO: Merge with the above condition when https://rust-lang.github.io/rfcs/2497-if-let-chains.html lands. + if let Some(imported_ident) = imported_module.as_deref() { + *prop = Prop::KeyValue(KeyValueProp { + key: PropName::Ident(ident.clone()), + value: Box::new(make_expr(imported_ident, item.export.as_deref(), ident.span, false)) + }); } - }), - ); + } + })); break; } // Any other expression can be replaced with the import accessor. @@ -129,16 +98,15 @@ impl CodeGenerateable for EsmBinding { ); visitors.push( - create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) { - if let Some(ident) = imported_module.as_deref() { - use swc_core::common::Spanned; - *expr = make_expr(ident, this.export.as_deref(), expr.span(), in_call); - } - // If there's no identifier for the imported module, - // resolution failed and will insert code that throws - // before this expression is reached. Leave behind the original identifier. - }), - ); + create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) { + if let Some(ident) = imported_module.as_deref() { + use swc_core::common::Spanned; + *expr = make_expr(ident, item.export.as_deref(), expr.span(), in_call); + } + // If there's no identifier for the imported module, + // resolution failed and will insert code that throws + // before this expression is reached. Leave behind the original identifier. + })); break; } Some(swc_core::ecma::visit::AstParentKind::BindingIdent( @@ -155,18 +123,16 @@ impl CodeGenerateable for EsmBinding { ast_path.pop(); visitors.push( - create_visitor!(exact ast_path, visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) { - if let Some(ident) = imported_module.as_deref() { - use swc_core::common::Spanned; - *l = match make_expr(ident, this.export.as_deref(), l.span(), false) { - Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()), - Expr::Member(member) => SimpleAssignTarget::Member(member), - _ => unreachable!(), - }; - } - }), - ); - + create_visitor!(exact ast_path, visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) { + if let Some(ident) = imported_module.as_deref() { + use swc_core::common::Spanned; + *l = match make_expr(ident, item.export.as_deref(), l.span(), false) { + Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()), + Expr::Member(member) => SimpleAssignTarget::Member(member), + _ => unreachable!(), + }; + } + })); break; } } @@ -177,6 +143,59 @@ impl CodeGenerateable for EsmBinding { } } + Ok(()) + } +} + +#[turbo_tasks::value_impl] +impl CodeGenerateable for EsmBindings { + #[turbo_tasks::function] + async fn code_generation( + self: Vc, + _context: Vc>, + ) -> Result> { + let this = self.await?; + let mut visitors = Vec::new(); + let bindings = this.bindings.clone(); + + for item in bindings.into_iter() { + item.to_visitors(&mut visitors).await?; + } + Ok(CodeGeneration { visitors }.into()) } } + +fn make_expr(imported_module: &str, export: Option<&str>, span: Span, in_call: bool) -> Expr { + let span = span.with_ctxt(SyntaxContext::empty()); + if let Some(export) = export { + let mut expr = Expr::Member(MemberExpr { + span, + obj: Box::new(Expr::Ident(Ident::new(imported_module.into(), span))), + prop: MemberProp::Computed(ComputedPropName { + span, + expr: Box::new(Expr::Lit(Lit::Str(Str { + span, + value: export.into(), + raw: None, + }))), + }), + }); + if in_call { + expr = Expr::Seq(SeqExpr { + exprs: vec![ + Box::new(Expr::Lit(Lit::Num(Number { + span, + value: 0.0, + raw: None, + }))), + Box::new(expr), + ], + span, + }); + } + expr + } else { + Expr::Ident(Ident::new(imported_module.into(), span)) + } +} diff --git a/crates/turbopack-ecmascript/src/references/mod.rs b/crates/turbopack-ecmascript/src/references/mod.rs index 91184015c70cf..e284992e9ad58 100644 --- a/crates/turbopack-ecmascript/src/references/mod.rs +++ b/crates/turbopack-ecmascript/src/references/mod.rs @@ -85,8 +85,8 @@ use self::{ }, cjs::CjsAssetReference, esm::{ - export::EsmExport, EsmAssetReference, EsmAsyncAssetReference, EsmExports, EsmModuleItem, - ImportMetaBinding, ImportMetaRef, UrlAssetReference, + binding::EsmBindings, export::EsmExport, EsmAssetReference, EsmAsyncAssetReference, + EsmExports, EsmModuleItem, ImportMetaBinding, ImportMetaRef, UrlAssetReference, }, node::DirAssetReference, raw::FileSourceReference, @@ -164,6 +164,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder { async_module: Vc, successful: bool, source_map: Option>, + bindings: Vec, } impl AnalyzeEcmascriptModuleResultBuilder { @@ -178,6 +179,7 @@ impl AnalyzeEcmascriptModuleResultBuilder { async_module: Vc::cell(None), successful: false, source_map: None, + bindings: Vec::new(), } } @@ -238,6 +240,10 @@ impl AnalyzeEcmascriptModuleResultBuilder { ))); } + pub fn add_binding(&mut self, binding: EsmBinding) { + self.bindings.push(binding); + } + /// Sets the analysis result ES export. pub fn set_source_map(&mut self, source_map: Vc) { self.source_map = Some(source_map); @@ -264,6 +270,11 @@ impl AnalyzeEcmascriptModuleResultBuilder { mut self, track_reexport_references: bool, ) -> Result> { + let bindings = EsmBindings::new(take(&mut self.bindings)); + if !bindings.await?.bindings.is_empty() { + self.add_code_gen(bindings); + } + let mut references: Vec<_> = self.references.into_iter().collect(); for r in references.iter_mut() { *r = r.resolve().await?; @@ -1063,7 +1074,7 @@ pub(crate) async fn analyse_ecmascript_module_internal( .add_reference(EsmModuleIdAssetReference::new(*r, Vc::cell(ast_path))) } else { analysis.add_local_reference(*r); - analysis.add_code_gen(EsmBinding::new(*r, export, Vc::cell(ast_path))); + analysis.add_binding(EsmBinding::new(*r, export, Vc::cell(ast_path))); } } } @@ -1966,7 +1977,7 @@ async fn handle_free_var_reference( .resolve() .await?; analysis.add_reference(esm_reference); - analysis.add_code_gen(EsmBinding::new( + analysis.add_binding(EsmBinding::new( esm_reference, export.clone(), Vc::cell(ast_path.to_vec()),