Repository: vercel/next.js
Pull request: Turbopack: fix duplicate unsupported edge import modules

1. Add the import in question to the module ident: `[project]/test/e2e/on-request-error/isr (unsupported edge import fs)`
2. Wrap the source creation into a separate turbotask, so that `fs` and `fs/promises` get the same Source cell (instead of two different Sources with the same ident)

Closes PACK-4386

Changed files:
filename: crates/next-core/src/next_edge/unsupported.rs
patch: @@ -1,9 +1,11 @@
 use anyhow::Result;
 use indoc::formatdoc;
+use turbo_rcstr::RcStr;
 use turbo_tasks::{ResolvedVc, Vc};
 use turbo_tasks_fs::{File, FileSystemPath};
 use turbopack_core::{
     asset::AssetContent,
+    ident::AssetIdent,
     resolve::{
         options::{ImportMapResult, ImportMappingReplacement, ReplacedImportMapping},
         parse::Request,
@@ -13,30 +15,20 @@ use turbopack_core::{
     virtual_source::VirtualSource,
 };
 use turbopack_ecmascript::runtime_functions::TURBOPACK_EXPORT_NAMESPACE;
-use turbopack_node::execution_context::ExecutionContext;
 
 /// Intercepts requests for the given request to `unsupported` error messages
 /// by returning a VirtualSource proxies to any import request to raise a
 /// runtime error.
 ///
 /// This can be used by import map alias, refer `next_import_map` for the setup.
 #[turbo_tasks::value(shared)]
-pub struct NextEdgeUnsupportedModuleReplacer {
-    project_path: ResolvedVc<FileSystemPath>,
-    execution_context: ResolvedVc<ExecutionContext>,
-}
+pub struct NextEdgeUnsupportedModuleReplacer {}
 
 #[turbo_tasks::value_impl]
 impl NextEdgeUnsupportedModuleReplacer {
     #[turbo_tasks::function]
-    pub fn new(
-        project_path: ResolvedVc<FileSystemPath>,
-        execution_context: ResolvedVc<ExecutionContext>,
-    ) -> Vc<Self> {
-        Self::cell(NextEdgeUnsupportedModuleReplacer {
-            project_path,
-            execution_context,
-        })
+    pub fn new() -> Vc<Self> {
+        NextEdgeUnsupportedModuleReplacer {}.cell()
     }
 }
 
@@ -50,25 +42,37 @@ impl ImportMappingReplacement for NextEdgeUnsupportedModuleReplacer {
     #[turbo_tasks::function]
     async fn result(
         &self,
-        root_path: Vc<FileSystemPath>,
+        lookup_path: Vc<FileSystemPath>,
         request: Vc<Request>,
     ) -> Result<Vc<ImportMapResult>> {
         let request = &*request.await?;
         if let Request::Module { module, .. } = request {
-            // packages/next/src/server/web/globals.ts augments global with
-            // `__import_unsupported` and necessary functions.
-            let code = formatdoc! {
-              r#"
-              {TURBOPACK_EXPORT_NAMESPACE}(__import_unsupported(`{module}`));
-              "#
-            };
-            let content = AssetContent::file(File::from(code).into());
-            let source = VirtualSource::new(root_path, content).to_resolved().await?;
-            return Ok(
-                ImportMapResult::Result(ResolveResult::source(ResolvedVc::upcast(source))).cell(),
-            );
-        };
-
-        Ok(ImportMapResult::NoEntry.cell())
+            // Call out to separate `unsupported_module_source` to only have a single Source cell
+            // for requests with different subpaths: `fs` and `fs/promises`.
+            let source = unsupported_module_source(lookup_path.root(), module.clone())
+                .to_resolved()
+                .await?;
+            Ok(ImportMapResult::Result(ResolveResult::source(ResolvedVc::upcast(source))).cell())
+        } else {
+            Ok(ImportMapResult::NoEntry.cell())
+        }
     }
 }
+
+#[turbo_tasks::function]
+fn unsupported_module_source(root_path: Vc<FileSystemPath>, module: RcStr) -> Vc<VirtualSource> {
+    // packages/next/src/server/web/globals.ts augments global with
+    // `__import_unsupported` and necessary functions.
+    let code = formatdoc! {
+        r#"
+        {TURBOPACK_EXPORT_NAMESPACE}(__import_unsupported(`{module}`));
+        "#
+    };
+    let content = AssetContent::file(File::from(code).into());
+    VirtualSource::new_with_ident(
+        AssetIdent::from_path(root_path).with_modifier(Vc::cell(
+            format!("unsupported edge import {}", module).into(),
+        )),
+        content,
+    )
+}

filename: crates/next-core/src/next_import_map.rs
patch: @@ -485,12 +485,7 @@ pub async fn get_next_edge_import_map(
         | ServerContextType::Pages { .. }
         | ServerContextType::PagesData { .. }
         | ServerContextType::PagesApi { .. } => {
-            insert_unsupported_node_internal_aliases(
-                &mut import_map,
-                *project_path,
-                execution_context,
-            )
-            .await?;
+            insert_unsupported_node_internal_aliases(&mut import_map).await?;
         }
     }
 
@@ -500,13 +495,9 @@ pub async fn get_next_edge_import_map(
 /// Insert default aliases for the node.js's internal to raise unsupported
 /// runtime errors. User may provide polyfills for their own by setting user
 /// config's alias.
-async fn insert_unsupported_node_internal_aliases(
-    import_map: &mut ImportMap,
-    project_path: Vc<FileSystemPath>,
-    execution_context: Vc<ExecutionContext>,
-) -> Result<()> {
+async fn insert_unsupported_node_internal_aliases(import_map: &mut ImportMap) -> Result<()> {
     let unsupported_replacer = ImportMapping::Dynamic(ResolvedVc::upcast(
-        NextEdgeUnsupportedModuleReplacer::new(project_path, execution_context)
+        NextEdgeUnsupportedModuleReplacer::new()
             .to_resolved()
             .await?,
     ))