Repository: vercel/next.js
Pull request: Turbopack: ignore static asset imports for Edge

Closes PACK-4072 

Ignore static asset imports in Edge, these are really intended for the client (i.e. for
pages and not routes, for example `import logo from "./logo.svg"`), while still allowing `new URL(..., import.meta.url)`.

This replicates what Webpack does. A proper solution for assets on Edge would require some change on the Next.js side to actually tell the bundler if an asset is intended for the server (e.g. for next/og: wasm, fonts), or for the browser.


Changed files:
filename: crates/next-core/src/next_server/transforms.rs
patch: @@ -3,6 +3,7 @@ use next_custom_transforms::transforms::strip_page_exports::ExportFilter;
 use turbo_rcstr::RcStr;
 use turbo_tasks::{ResolvedVc, Vc};
 use turbopack::module_options::{ModuleRule, ModuleRuleEffect, RuleCondition};
+use turbopack_core::reference_type::{ReferenceType, UrlReferenceSubType};
 
 use crate::{
     mode::NextMode,
@@ -193,6 +194,31 @@ pub async fn get_next_server_transforms_rules(
                 matches!(*mode.await?, NextMode::Build),
             ));
         }
+
+        if matches!(context_ty, ServerContextType::AppRoute { .. }) {
+            // Ignore static asset imports in Edge routes, these are really intended for the client
+            // (i.e. for pages), while still allowing `new URL(..., import.meta.url)`
+            rules.push(ModuleRule::new(
+                RuleCondition::all(vec![
+                    RuleCondition::not(RuleCondition::ReferenceType(ReferenceType::Url(
+                        UrlReferenceSubType::Undefined,
+                    ))),
+                    RuleCondition::any(vec![
+                        RuleCondition::ResourcePathEndsWith(".apng".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".avif".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".gif".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".ico".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".jpg".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".jpeg".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".png".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".svg".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".webp".to_string()),
+                        RuleCondition::ResourcePathEndsWith(".woff2".to_string()),
+                    ]),
+                ]),
+                vec![ModuleRuleEffect::Ignore],
+            ));
+        }
     }
 
     Ok(rules)

filename: turbopack/crates/turbopack-ecmascript/src/references/esm/binding.rs
patch: @@ -18,7 +18,7 @@ use super::EsmAssetReference;
 use crate::{
     code_gen::{CodeGen, CodeGeneration},
     create_visitor,
-    references::AstPath,
+    references::{esm::base::ReferencedAsset, AstPath},
 };
 
 #[derive(Hash, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs, NonLocalValue)]
@@ -50,7 +50,20 @@ impl EsmBinding {
 
         let export = self.export.clone();
         let imported_module = self.reference.get_referenced_asset();
-        let imported_module = imported_module.await?.get_ident(chunking_context).await?;
+
+        enum ImportedIdent {
+            Module(String),
+            None,
+            Unresolvable,
+        }
+
+        let imported_ident = match &*imported_module.await? {
+            ReferencedAsset::None => ImportedIdent::None,
+            imported_module => imported_module
+                .get_ident(chunking_context)
+                .await?
+                .map_or(ImportedIdent::Unresolvable, ImportedIdent::Module),
+        };
 
         let mut ast_path = self.ast_path.0.clone();
         loop {
@@ -60,17 +73,34 @@ impl 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().into()),
-                                    value: Box::new(make_expr(imported_ident, 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.
+                                match &imported_ident {
+                                    ImportedIdent::Module(imported_ident) => {
+                                        *prop = Prop::KeyValue(KeyValueProp {
+                                            key: PropName::Ident(ident.clone().into()),
+                                            value: Box::new(make_expr(
+                                                imported_ident,
+                                                export.as_deref(),
+                                                ident.span,
+                                                false,
+                                            )),
+                                        });
+                                    }
+                                    ImportedIdent::None => {
+                                        *prop = Prop::KeyValue(KeyValueProp {
+                                            key: PropName::Ident(ident.clone().into()),
+                                            value: Expr::undefined(ident.span),
+                                        });
+                                    }
+                                    ImportedIdent::Unresolvable => {
+                                        // Do nothing, the reference will insert a throw
+                                    }
+                                }
                             }
-                        }
-                    }));
+                        }),
+                    );
                     break;
                 }
                 // Any other expression can be replaced with the import accessor.
@@ -85,13 +115,18 @@ impl 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, export.as_deref(), expr.span(), in_call);
+                            use swc_core::common::Spanned;
+                            match &imported_ident {
+                                ImportedIdent::Module(imported_ident) => {
+                                    *expr = make_expr(imported_ident, export.as_deref(), expr.span(), in_call);
+                                }
+                                ImportedIdent::None => {
+                                    *expr = *Expr::undefined(expr.span());
+                                }
+                                ImportedIdent::Unresolvable => {
+                                    // Do nothing, the reference will insert a throw
+                                }
                             }
-                            // 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;
@@ -111,14 +146,22 @@ impl EsmBinding {
 
                         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, export.as_deref(), l.span(), false) {
+                            use swc_core::common::Spanned;
+                            match &imported_ident {
+                                ImportedIdent::Module(imported_ident) => {
+                                    *l = match make_expr(imported_ident, export.as_deref(), l.span(), false) {
                                         Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()),
                                         Expr::Member(member) => SimpleAssignTarget::Member(member),
                                         _ => unreachable!(),
                                     };
                                 }
+                                ImportedIdent::None => {
+                                    // Do nothing, cannot assign to `undefined`
+                                }
+                                ImportedIdent::Unresolvable => {
+                                    // Do nothing, the reference will insert a throw
+                                }
+                            }
                         }));
                         break;
                     }