Repository: vercel/next.js
Pull request: Turbopack: Skip ssr processing when next/dynamic ssr: false

### What?

Ensuring server-side processing for the loader function of `next/dynamic` with `ssr: false` is properly skipped in Turbopack.

### Why?
Previously, client components imported through `next/dynamic` with `ssr: false` were not skipped, while they are skipped in webpack. This fix ensures consistent behavior between Webpack and Turbopack. This is a compilation performance win for applications that leverage this pattern heavily, as it can skip work it doesn't need to process.

Was looking at fixing one of the failing tests in the list of remaining tests. Found it checked for this behavior and fixed it.

### How?
- Added the same `should_skip_ssr_compile` logic to the Turbopack state that was already implemented for Webpack
- When `ssr: false` is specified and we're in the server compiler we replace the dynamic import with an empty async function to avoid bundling the client component on the server

Closes PACK-4230

Changed files:
filename: crates/next-custom-transforms/src/transforms/dynamic.rs
patch: @@ -306,6 +306,11 @@ impl Fold for NextDynamicPatcher {
                         }
                     }
 
+                    let should_skip_ssr_compile = has_ssr_false
+                        && self.is_server_compiler
+                        && !self.is_react_server_layer
+                        && self.prefer_esm;
+
                     match &self.state {
                         NextDynamicPatcherState::Webpack => {
                             // Only use `require.resolveWebpack` to decouple modules for webpack,
@@ -316,11 +321,7 @@ impl Fold for NextDynamicPatcher {
                             //
                             // Also transforming it to `require.resolveWeak` doesn't work with ESM
                             // imports ( i.e. require.resolveWeak(esm asset)).
-                            if has_ssr_false
-                                && self.is_server_compiler
-                                && !self.is_react_server_layer
-                                && self.prefer_esm
-                            {
+                            if should_skip_ssr_compile {
                                 // if it's server components SSR layer
                                 // Transform 1st argument `expr.args[0]` aka the module loader from:
                                 // dynamic(() => import('./client-mod'), { ssr: false }))`
@@ -368,11 +369,34 @@ impl Fold for NextDynamicPatcher {
                             dynamic_transition_name,
                             ..
                         } => {
-                            // Add `{with:{turbopack-transition: ...}}` to the dynamic import
-                            let mut visitor = DynamicImportTransitionAdder {
-                                transition_name: dynamic_transition_name,
-                            };
-                            expr.args[0].visit_mut_with(&mut visitor);
+                            // When `ssr: false`
+                            // if it's server components SSR layer
+                            // Transform 1st argument `expr.args[0]` aka the module loader from:
+                            // dynamic(() => import('./client-mod'), { ssr: false }))`
+                            // into:
+                            // dynamic(async () => {}, { ssr: false }))`
+                            if should_skip_ssr_compile {
+                                let side_effect_free_loader_arg = Expr::Arrow(ArrowExpr {
+                                    span: DUMMY_SP,
+                                    params: vec![],
+                                    body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
+                                        span: DUMMY_SP,
+                                        stmts: vec![],
+                                        ..Default::default()
+                                    })),
+                                    is_async: true,
+                                    is_generator: false,
+                                    ..Default::default()
+                                });
+
+                                expr.args[0] = side_effect_free_loader_arg.as_arg();
+                            } else {
+                                // Add `{with:{turbopack-transition: ...}}` to the dynamic import
+                                let mut visitor = DynamicImportTransitionAdder {
+                                    transition_name: dynamic_transition_name,
+                                };
+                                expr.args[0].visit_mut_with(&mut visitor);
+                            }
                         }
                     }
 

filename: test/turbopack-build-tests-manifest.json
patch: @@ -2146,11 +2146,10 @@
       "app dir - next/dynamic should handle next/dynamic in hydration correctly",
       "app dir - next/dynamic should handle ssr: false in pages when appDir is enabled",
       "app dir - next/dynamic should not render loading by default",
-      "app dir - next/dynamic should render loading by default if loading is specified and loader is slow"
-    ],
-    "failed": [
+      "app dir - next/dynamic should render loading by default if loading is specified and loader is slow",
       "app dir - next/dynamic no SSR should not render client component imported through ssr: false in client components in edge runtime"
     ],
+    "failed": [],
     "pending": [],
     "flakey": [],
     "runtimeError": false