Repository: vercel/next.js
Pull request: Fix `--no-mangling` for `"use cache"` functions

When mangling is disabled with `next build --no-mangling`, the function names of `"use cache"` functions are currently still mangled.

The reason is that those functions are transformed to inline function expressions whose names are removed by the SWC minifier even if `mangle` is `false`.

Original:

```js
'use cache'

export async function getCachedData() {
  return functionThatMightThrow()
}
```

Transformed by the Next.js compiler during `next dev`:

```js
export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "<id>", 0, async function getCachedData() {
  return functionThatMightThrow()
});
```

Transformed by the Next.js compiler during `next build`:

```js
export var $$RSC_SERVER_CACHE_0 = $$cache__("default", "<id>", 0, async function () {
  return functionThatMightThrow()
});
```

By setting `keep_fnames` to `true` in SWC's `compress` options, we can ensure that those function expression names are preserved when building with `--no-mangling`, which is helpful when looking at error stacks or traces.

Changed files:
filename: packages/next/src/build/webpack/plugins/minify-webpack-plugin/src/index.ts
patch: @@ -145,6 +145,8 @@ export class MinifyPlugin {
                 global_defs: {
                   'process.env.__NEXT_PRIVATE_MINIMIZE_MACRO_FALSE': false,
                 },
+                keep_classnames: this.options.noMangling,
+                keep_fnames: this.options.noMangling,
               },
               mangle,
               module: 'unknown',

filename: test/production/app-dir/no-mangling/app/use-cache/page.tsx
patch: @@ -0,0 +1,6 @@
+'use cache'
+
+export default async function CachedPage() {
+  throw new Error('Kaputt!')
+  return <p>hello world</p>
+}

filename: test/production/app-dir/no-mangling/next.config.js
patch: @@ -1,6 +1,11 @@
 /**
  * @type {import('next').NextConfig}
  */
-const nextConfig = {}
+const nextConfig = {
+  experimental: {
+    useCache: true,
+    prerenderEarlyExit: false,
+  },
+}
 
 module.exports = nextConfig

filename: test/production/app-dir/no-mangling/no-mangling.test.ts
patch: @@ -46,5 +46,21 @@ Error: Kaputt!
 Error: Kaputt!
     at Page`)
     })
+
+    describe('with "use cache" functions', () => {
+      it('should show original function names in stack traces', async () => {
+        try {
+          await next.build()
+        } catch {
+          // we expect the build to fail
+        }
+
+        // `CachedPage` is the original function name that would be mangled if
+        // `next build` was called without `--no-mangling`.
+        expect(next.cliOutput).toInclude(`
+Error: Kaputt!
+    at CachedPage`)
+      })
+    })
   })
 })

filename: turbopack/crates/turbopack-ecmascript/src/minify.rs
patch: @@ -85,6 +85,8 @@ pub fn minify(code: &Code, source_maps: bool, mangle: Option<MangleType>) -> Res
                             // Only run 2 passes, this is a tradeoff between performance and
                             // compression size. Default is 3 passes.
                             passes: 2,
+                            keep_classnames: mangle.is_none(),
+                            keep_fnames: mangle.is_none(),
                             ..Default::default()
                         }),
                         mangle: mangle.map(|mangle| {