Repository: vercel/next.js
Pull request: [turbopack] Lazily construct `resolved::Requests` for `EsmAssetReference`

Right now we construct `Request` objects eagerly when analyzing modules.   This is on the critical path for analysis and can be expensive if there are a lot of exports (See [thread](https://vercel.slack.com/archives/C06PPGZ0FD3/p1757517287294729))

Instead just store the string and `parse` when resolving the reference.  This should have identical caching semantics and by deferring the work we may be able to avoid it altogether if certain references never get resolved due to `sideEffects` configuration.


Closes PACK-5447

Changed files:
filename: turbopack/crates/turbopack-ecmascript/src/references/esm/base.rs
patch: @@ -43,10 +43,7 @@ use crate::{
     code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
     export::Liveness,
     magic_identifier,
-    references::{
-        esm::EsmExport,
-        util::{request_to_string, throw_module_not_found_expr},
-    },
+    references::{esm::EsmExport, util::throw_module_not_found_expr},
     runtime_functions::{TURBOPACK_EXTERNAL_IMPORT, TURBOPACK_EXTERNAL_REQUIRE, TURBOPACK_IMPORT},
     tree_shake::{TURBOPACK_PART_IMPORT_SOURCE, asset::EcmascriptModulePartAsset},
     utils::module_id_to_lit,
@@ -321,7 +318,8 @@ impl EsmAssetReferences {
 #[derive(Hash, Debug)]
 pub struct EsmAssetReference {
     pub origin: ResolvedVc<Box<dyn ResolveOrigin>>,
-    pub request: ResolvedVc<Request>,
+    // Request is a string to avoid eagerly parsing into a `Request` VC
+    pub request: RcStr,
     pub annotations: ImportAnnotations,
     pub issue_source: IssueSource,
     pub export_name: Option<ModulePart>,
@@ -342,7 +340,7 @@ impl EsmAssetReference {
 impl EsmAssetReference {
     pub fn new(
         origin: ResolvedVc<Box<dyn ResolveOrigin>>,
-        request: ResolvedVc<Request>,
+        request: RcStr,
         issue_source: IssueSource,
         annotations: ImportAnnotations,
         export_name: Option<ModulePart>,
@@ -361,7 +359,7 @@ impl EsmAssetReference {
 
     pub fn new_pure(
         origin: ResolvedVc<Box<dyn ResolveOrigin>>,
-        request: ResolvedVc<Request>,
+        request: RcStr,
         issue_source: IssueSource,
         annotations: ImportAnnotations,
         export_name: Option<ModulePart>,
@@ -424,8 +422,9 @@ impl ModuleReference for EsmAssetReference {
                 }
             }
         }
+        let request = Request::parse(self.request.clone().into());
 
-        if let Request::Module { module, .. } = &*self.request.await?
+        if let Request::Module { module, .. } = &*request.await?
             && module.is_match(TURBOPACK_PART_IMPORT_SOURCE)
         {
             if let Some(part) = &self.export_name {
@@ -445,7 +444,7 @@ impl ModuleReference for EsmAssetReference {
 
         let result = esm_resolve(
             self.get_origin().resolve().await?,
-            *self.request,
+            request,
             ty,
             false,
             Some(self.issue_source),
@@ -475,15 +474,8 @@ impl ModuleReference for EsmAssetReference {
 #[turbo_tasks::value_impl]
 impl ValueToString for EsmAssetReference {
     #[turbo_tasks::function]
-    async fn to_string(&self) -> Result<Vc<RcStr>> {
-        Ok(Vc::cell(
-            format!(
-                "import {} with {}",
-                self.request.to_string().await?,
-                self.annotations
-            )
-            .into(),
-        ))
+    fn to_string(&self) -> Vc<RcStr> {
+        Vc::cell(format!("import {} with {}", self.request, self.annotations).into())
     }
 }
 
@@ -537,9 +529,9 @@ impl EsmAssetReference {
                 ReferencedAsset::Unresolvable => {
                     // Insert code that throws immediately at time of import if a request is
                     // unresolvable
-                    let request = request_to_string(*this.request).await?.to_string();
+                    let request = &this.request;
                     let stmt = Stmt::Expr(ExprStmt {
-                        expr: Box::new(throw_module_not_found_expr(&request)),
+                        expr: Box::new(throw_module_not_found_expr(request)),
                         span: DUMMY_SP,
                     });
                     return Ok(CodeGeneration::hoisted_stmt(

filename: turbopack/crates/turbopack-ecmascript/src/references/mod.rs
patch: @@ -738,42 +738,38 @@ pub async fn analyse_ecmascript_module_internal(
             let mut should_add_evaluation = false;
             let reference = EsmAssetReference::new(
                 origin,
-                Request::parse(RcStr::from(&*r.module_path).into())
-                    .to_resolved()
-                    .await?,
+                RcStr::from(&*r.module_path),
                 r.issue_source
                     .unwrap_or_else(|| IssueSource::from_source_only(source)),
                 r.annotations.clone(),
-                match options.tree_shaking_mode {
-                    Some(TreeShakingMode::ModuleFragments) => match &r.imported_symbol {
-                        ImportedSymbol::ModuleEvaluation => {
-                            should_add_evaluation = true;
-                            Some(ModulePart::evaluation())
-                        }
-                        ImportedSymbol::Symbol(name) => Some(ModulePart::export((&**name).into())),
-                        ImportedSymbol::PartEvaluation(part_id) => {
-                            should_add_evaluation = true;
-                            Some(ModulePart::internal(*part_id))
-                        }
-                        ImportedSymbol::Part(part_id) => Some(ModulePart::internal(*part_id)),
-                        ImportedSymbol::Exports => Some(ModulePart::exports()),
-                    },
-                    _ => match &r.imported_symbol {
-                        ImportedSymbol::ModuleEvaluation => {
-                            should_add_evaluation = true;
-                            Some(ModulePart::evaluation())
-                        }
-                        ImportedSymbol::Symbol(name) => Some(ModulePart::export((&**name).into())),
-                        ImportedSymbol::PartEvaluation(_) | ImportedSymbol::Part(_) => {
+                match &r.imported_symbol {
+                    ImportedSymbol::ModuleEvaluation => {
+                        should_add_evaluation = true;
+                        Some(ModulePart::evaluation())
+                    }
+                    ImportedSymbol::Symbol(name) => Some(ModulePart::export((&**name).into())),
+                    ImportedSymbol::PartEvaluation(part_id) | ImportedSymbol::Part(part_id) => {
+                        if !matches!(
+                            options.tree_shaking_mode,
+                            Some(TreeShakingMode::ModuleFragments)
+                        ) {
                             bail!(
-                                "Internal imports doesn't exist in reexports only mode when \
+                                "Internal imports only exist in reexports only mode when \
                                  importing {:?} from {}",
                                 r.imported_symbol,
                                 r.module_path
                             );
                         }
-                        ImportedSymbol::Exports => None,
-                    },
+                        if matches!(&r.imported_symbol, ImportedSymbol::PartEvaluation(_)) {
+                            should_add_evaluation = true;
+                        }
+                        Some(ModulePart::internal(*part_id))
+                    }
+                    ImportedSymbol::Exports => matches!(
+                        options.tree_shaking_mode,
+                        Some(TreeShakingMode::ModuleFragments)
+                    )
+                    .then(ModulePart::exports),
                 },
                 import_externals,
             )
@@ -1406,7 +1402,7 @@ pub async fn analyse_ecmascript_module_internal(
                                         || {
                                             EsmAssetReference::new(
                                                 original_reference.origin,
-                                                original_reference.request,
+                                                original_reference.request.clone(),
                                                 original_reference.issue_source,
                                                 original_reference.annotations.clone(),
                                                 Some(ModulePart::export(export.clone())),
@@ -2668,7 +2664,7 @@ async fn handle_free_var_reference(
                         } else {
                             state.origin
                         },
-                        Request::parse(request.clone().into()).to_resolved().await?,
+                        request.clone(),
                         IssueSource::from_swc_offsets(
                             state.source,
                             span.lo.to_u32(),