Repository: vercel/next.js
Pull request: Turbopack: defer sourcemaps processing to codegen

Closes PACK-3434

```
    // TODO This is too eagerly generating the source map. We should store a GenerateSourceMap
    // instead and only actually generate the SourceMap when it's needed. This would allow to avoid
    // generating the source map when a module is never included in the final bundle. It allows
    // analysis to finish earlier which makes references available earlier which benefits
    // parallelism. When SourceMaps are emitted it moves that generation work to the code generation
    // phase which is more parallelizable.
```

Changed files:
filename: turbopack/crates/turbopack-ecmascript/src/lib.rs
patch: @@ -86,7 +86,7 @@ use turbopack_core::{
         FindContextFileResult,
     },
     source::Source,
-    source_map::OptionStringifiedSourceMap,
+    source_map::GenerateSourceMap,
 };
 // TODO remove this
 pub use turbopack_resolve::ecmascript as resolve;
@@ -819,7 +819,7 @@ pub struct EcmascriptModuleContentOptions {
     code_generation: ResolvedVc<CodeGens>,
     async_module: ResolvedVc<OptionAsyncModule>,
     generate_source_map: bool,
-    original_source_map: ResolvedVc<OptionStringifiedSourceMap>,
+    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
     exports: ResolvedVc<EcmascriptExports>,
     async_module_info: Option<ResolvedVc<AsyncModuleInfo>>,
 }
@@ -945,7 +945,7 @@ impl EcmascriptModuleContent {
             specified_module_type,
             vec![],
             generate_source_map,
-            OptionStringifiedSourceMap::none().to_resolved().await?,
+            None,
         )
         .await?;
         emit_content(content).await
@@ -958,7 +958,7 @@ struct CodeGenResult {
     comments: Either<ImmutableComments, Arc<ImmutableComments>>,
     is_esm: bool,
     generate_source_map: bool,
-    original_source_map: Option<ResolvedVc<OptionStringifiedSourceMap>>,
+    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
 }
 
 async fn process_parse_result(
@@ -967,7 +967,7 @@ async fn process_parse_result(
     specified_module_type: SpecifiedModuleType,
     code_gens: Vec<CodeGeneration>,
     generate_source_map: bool,
-    original_source_map: ResolvedVc<OptionStringifiedSourceMap>,
+    original_source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
 ) -> Result<CodeGenResult> {
     let parsed = parsed.final_read_hint().await?;
 
@@ -1025,7 +1025,7 @@ async fn process_parse_result(
                 comments,
                 is_esm,
                 generate_source_map,
-                original_source_map: Some(original_source_map),
+                original_source_map,
             }
         }
         ParseResult::Unparseable { messages } => {
@@ -1130,7 +1130,7 @@ async fn emit_content(content: CodeGenResult) -> Result<Vc<EcmascriptModuleConte
             Some(generate_js_source_map(
                 source_map.clone(),
                 mappings,
-                original_source_map.await?.as_ref(),
+                original_source_map.generate_source_map().await?.as_ref(),
             )?)
         } else {
             Some(generate_js_source_map(source_map.clone(), mappings, None)?)

filename: turbopack/crates/turbopack-ecmascript/src/references/mod.rs
patch: @@ -56,7 +56,7 @@ use turbo_tasks::{
     trace::TraceRawVcs, FxIndexMap, FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TaskInput,
     TryJoinIterExt, Upcast, Value, ValueToString, Vc,
 };
-use turbo_tasks_fs::{rope::Rope, FileSystemPath};
+use turbo_tasks_fs::FileSystemPath;
 use turbopack_core::{
     compile_time_info::{
         CompileTimeInfo, DefineableNameSegment, FreeVarReference, FreeVarReferences,
@@ -76,9 +76,7 @@ use turbopack_core::{
         resolve, FindContextFileResult, ModulePart,
     },
     source::Source,
-    source_map::{
-        utils::resolve_source_map_sources, GenerateSourceMap, OptionStringifiedSourceMap,
-    },
+    source_map::GenerateSourceMap,
 };
 use turbopack_resolve::{
     ecmascript::{apply_cjs_specific_options, cjs_resolve_source},
@@ -148,6 +146,7 @@ use crate::{
         node::PackageJsonReference,
         require_context::{RequireContextAssetReference, RequireContextMap},
         type_issue::SpecifiedModuleTypeIssue,
+        util::InlineSourceMap,
     },
     runtime_functions::{
         TUBROPACK_RUNTIME_FUNCTION_SHORTCUTS, TURBOPACK_EXPORT_NAMESPACE, TURBOPACK_EXPORT_VALUE,
@@ -174,7 +173,7 @@ pub struct AnalyzeEcmascriptModuleResult {
     pub has_side_effect_free_directive: bool,
     /// `true` when the analysis was successful.
     pub successful: bool,
-    pub source_map: ResolvedVc<OptionStringifiedSourceMap>,
+    pub source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
 }
 
 #[turbo_tasks::value_impl]
@@ -223,7 +222,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder {
     exports: EcmascriptExports,
     async_module: ResolvedVc<OptionAsyncModule>,
     successful: bool,
-    source_map: Option<ResolvedVc<OptionStringifiedSourceMap>>,
+    source_map: Option<ResolvedVc<Box<dyn GenerateSourceMap>>>,
     has_side_effect_free_directive: bool,
 }
 
@@ -289,7 +288,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
     }
 
     /// Sets the analysis result ES export.
-    pub fn set_source_map(&mut self, source_map: ResolvedVc<OptionStringifiedSourceMap>) {
+    pub fn set_source_map(&mut self, source_map: ResolvedVc<Box<dyn GenerateSourceMap>>) {
         self.source_map = Some(source_map);
     }
 
@@ -401,12 +400,6 @@ impl AnalyzeEcmascriptModuleResultBuilder {
 
         let references: Vec<_> = self.references.into_iter().collect();
 
-        let source_map = if let Some(source_map) = self.source_map {
-            source_map
-        } else {
-            OptionStringifiedSourceMap::none().to_resolved().await?
-        };
-
         self.code_gens.shrink_to_fit();
         Ok(AnalyzeEcmascriptModuleResult::cell(
             AnalyzeEcmascriptModuleResult {
@@ -424,7 +417,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
                 async_module: self.async_module,
                 has_side_effect_free_directive: self.has_side_effect_free_directive,
                 successful: self.successful,
-                source_map,
+                source_map: self.source_map,
             },
         ))
     }
@@ -705,27 +698,24 @@ pub(crate) async fn analyse_ecmascript_module_internal(
                         .to_resolved()
                         .await?;
                     analysis.add_reference(reference);
-                    let source_map = reference.generate_source_map();
-                    analysis.set_source_map(source_map.to_resolved().await?);
+                    analysis.set_source_map(ResolvedVc::upcast(reference));
                     source_map_from_comment = true;
                 } else if JSON_DATA_URL_BASE64.is_match(path) {
-                    let source_map = maybe_decode_data_url(path.into());
-                    let source_map =
-                        resolve_source_map_sources(source_map.as_ref(), origin_path).await?;
-                    analysis.set_source_map(ResolvedVc::cell(source_map));
+                    analysis.set_source_map(ResolvedVc::upcast(
+                        InlineSourceMap {
+                            origin_path: origin_path.to_resolved().await?,
+                            source_map: path.into(),
+                        }
+                        .resolved_cell(),
+                    ));
                     source_map_from_comment = true;
                 }
             }
             if !source_map_from_comment {
                 if let Some(generate_source_map) =
                     ResolvedVc::try_sidecast::<Box<dyn GenerateSourceMap>>(source)
                 {
-                    analysis.set_source_map(
-                        generate_source_map
-                            .generate_source_map()
-                            .to_resolved()
-                            .await?,
-                    );
+                    analysis.set_source_map(generate_source_map);
                 }
             }
             anyhow::Ok(())
@@ -3665,16 +3655,3 @@ fn is_invoking_node_process_eval(args: &[JsValue]) -> bool {
 
     false
 }
-
-fn maybe_decode_data_url(url: RcStr) -> Option<Rope> {
-    const DATA_PREAMBLE: &str = "data:application/json;base64,";
-
-    if !url.starts_with(DATA_PREAMBLE) {
-        return None;
-    }
-    let data_b64 = &url[DATA_PREAMBLE.len()..];
-    data_encoding::BASE64
-        .decode(data_b64.as_bytes())
-        .ok()
-        .map(Rope::from)
-}

filename: turbopack/crates/turbopack-ecmascript/src/references/util.rs
patch: @@ -1,8 +1,14 @@
 use anyhow::Result;
 use swc_core::{ecma::ast::Expr, quote};
 use turbo_rcstr::RcStr;
-use turbo_tasks::Vc;
-use turbopack_core::resolve::parse::Request;
+use turbo_tasks::{ResolvedVc, Vc};
+use turbo_tasks_fs::{rope::Rope, FileSystemPath};
+use turbopack_core::{
+    resolve::parse::Request,
+    source_map::{
+        utils::resolve_source_map_sources, GenerateSourceMap, OptionStringifiedSourceMap,
+    },
+};
 
 /// Creates a IIFE expression that throws a "Cannot find module" error for the
 /// given request string
@@ -36,3 +42,35 @@ pub async fn request_to_string(request: Vc<Request>) -> Result<Vc<RcStr>> {
             .unwrap_or_else(|| "unknown".into()),
     ))
 }
+
+#[turbo_tasks::value(shared)]
+#[derive(Debug, Clone)]
+pub struct InlineSourceMap {
+    /// The file path of the module containing the sourcemap data URL
+    pub origin_path: ResolvedVc<FileSystemPath>,
+    /// The Base64 encoded JSON sourcemap string
+    pub source_map: RcStr,
+}
+
+#[turbo_tasks::value_impl]
+impl GenerateSourceMap for InlineSourceMap {
+    #[turbo_tasks::function]
+    pub async fn generate_source_map(&self) -> Result<Vc<OptionStringifiedSourceMap>> {
+        let source_map = maybe_decode_data_url(&self.source_map);
+        let source_map = resolve_source_map_sources(source_map.as_ref(), *self.origin_path).await?;
+        Ok(Vc::cell(source_map))
+    }
+}
+
+fn maybe_decode_data_url(url: &str) -> Option<Rope> {
+    const DATA_PREAMBLE: &str = "data:application/json;base64,";
+
+    if !url.starts_with(DATA_PREAMBLE) {
+        return None;
+    }
+    let data_b64 = &url[DATA_PREAMBLE.len()..];
+    data_encoding::BASE64
+        .decode(data_b64.as_bytes())
+        .ok()
+        .map(Rope::from)
+}

filename: turbopack/crates/turbopack-ecmascript/src/side_effect_optimization/facade/module.rs
patch: @@ -11,7 +11,6 @@ use turbopack_core::{
     module_graph::ModuleGraph,
     reference::ModuleReferences,
     resolve::ModulePart,
-    source_map::OptionStringifiedSourceMap,
 };
 
 use super::chunk_item::EcmascriptModuleFacadeChunkItem;
@@ -224,7 +223,7 @@ impl EcmascriptAnalyzable for EcmascriptModuleFacadeModule {
             code_generation: CodeGens::empty().to_resolved().await?,
             async_module: ResolvedVc::cell(Some(self.async_module().to_resolved().await?)),
             generate_source_map: false,
-            original_source_map: OptionStringifiedSourceMap::none().to_resolved().await?,
+            original_source_map: None,
             exports: self.get_exports().to_resolved().await?,
             async_module_info,
         }