Repository: vercel/next.js
Pull request: Current behavior for module evaluation sourcemapping

Looks pretty bad. Most of it is due to added functions by bundlers that aren't sourcemapped.

Changed files:
filename: test/e2e/app-dir/non-root-project-monorepo/non-root-project-monorepo.test.ts
patch: @@ -82,13 +82,6 @@ describe('non-root-project-monorepo', () => {
 
   if (isNextDev) {
     describe('source-maps', () => {
-      function normalizeStackTrace(stack: string[] | null): string | null {
-        if (stack === null) {
-          return null
-        }
-        return stack.join('\n')
-      }
-
       it('should work on RSC', async () => {
         const browser = await next.browser('/source-maps-rsc')
         await assertHasRedbox(browser)
@@ -102,17 +95,13 @@ describe('non-root-project-monorepo', () => {
                |       ^
              2 |"
           `)
-          // TODO stacktrace-parser breaks in some cases with the rsc:// protocol
-          expect(
-            normalizeStackTrace(await getRedboxCallStack(browser)).replace(
-              /\/apps_web_\w+._.js /,
-              '/apps_web_XXXXXX._.js '
-            )
-          ).toMatchInlineSnapshot(`
-           "[project]/apps/web/app/separate-file.ts [app-rsc] (ecmascript) app/separate-file.ts (1:7)
-           innerArrowFunction app/source-maps-rsc/page.tsx (13:28)
-           innerFunction app/source-maps-rsc/page.tsx (10:3)
-           Page app/source-maps-rsc/page.tsx (4:5)"
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "[project]/apps/web/app/separate-file.ts [app-rsc] (ecmascript) app/separate-file.ts (1:7)",
+             "innerArrowFunction app/source-maps-rsc/page.tsx (13:28)",
+             "innerFunction app/source-maps-rsc/page.tsx (10:3)",
+             "Page app/source-maps-rsc/page.tsx (4:5)",
+           ]
           `)
         } else {
           // TODO the function name is incorrect
@@ -123,16 +112,17 @@ describe('non-root-project-monorepo', () => {
                |           ^
              2 |"
           `)
-          // TODO webpack runtime code shouldn't be included in stack trace
+          // TODO(veil): webpack runtime code shouldn't be included in stack trace (see https://linear.app/vercel/issue/NDX-509)
           // TODO(veil): https://linear.app/vercel/issue/NDX-677
-          expect(normalizeStackTrace(await getRedboxCallStack(browser)))
-            .toMatchInlineSnapshot(`
-           "eval app/separate-file.ts (1:11)
-           <FIXME-file-protocol>
-           <FIXME-file-protocol>
-           innerArrowFunction app/source-maps-rsc/page.tsx (14:3)
-           innerFunction app/source-maps-rsc/page.tsx (10:3)
-           Page app/source-maps-rsc/page.tsx (4:5)"
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "eval app/separate-file.ts (1:11)",
+             "<FIXME-file-protocol>",
+             "<FIXME-file-protocol>",
+             "innerArrowFunction app/source-maps-rsc/page.tsx (14:3)",
+             "innerFunction app/source-maps-rsc/page.tsx (10:3)",
+             "Page app/source-maps-rsc/page.tsx (4:5)",
+           ]
           `)
         }
         await browser.close()
@@ -151,12 +141,13 @@ describe('non-root-project-monorepo', () => {
                |       ^
              2 |"
           `)
-          expect(normalizeStackTrace(await getRedboxCallStack(browser)))
-            .toMatchInlineSnapshot(`
-           "[project]/apps/web/app/separate-file.ts [app-client] (ecmascript) app/separate-file.ts (1:7)
-           innerArrowFunction app/source-maps-ssr/page.tsx (15:28)
-           innerFunction app/source-maps-ssr/page.tsx (12:3)
-           Page app/source-maps-ssr/page.tsx (6:5)"
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "[project]/apps/web/app/separate-file.ts [app-client] (ecmascript) app/separate-file.ts (1:7)",
+             "innerArrowFunction app/source-maps-ssr/page.tsx (15:28)",
+             "innerFunction app/source-maps-ssr/page.tsx (12:3)",
+             "Page app/source-maps-ssr/page.tsx (6:5)",
+           ]
           `)
         } else {
           // TODO the function name should be hidden
@@ -167,17 +158,18 @@ describe('non-root-project-monorepo', () => {
                 |       ^
               2 |"
           `)
-          // TODO webpack runtime code shouldn't be included in stack trace
-          expect(normalizeStackTrace(await getRedboxCallStack(browser)))
-            .toMatchInlineSnapshot(`
-           "eval app/separate-file.ts (1:7)
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           innerArrowFunction app/source-maps-ssr/page.tsx (16:3)
-           innerFunction app/source-maps-ssr/page.tsx (12:3)
-           Page app/source-maps-ssr/page.tsx (6:5)"
+          // TODO(veil): webpack runtime code shouldn't be included in stack trace (see https://linear.app/vercel/issue/NDX-509)
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "eval app/separate-file.ts (1:7)",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "innerArrowFunction app/source-maps-ssr/page.tsx (16:3)",
+             "innerFunction app/source-maps-ssr/page.tsx (12:3)",
+             "Page app/source-maps-ssr/page.tsx (6:5)",
+           ]
           `)
         }
         await browser.close()
@@ -196,12 +188,13 @@ describe('non-root-project-monorepo', () => {
                |       ^
              2 |"
           `)
-          expect(normalizeStackTrace(await getRedboxCallStack(browser)))
-            .toMatchInlineSnapshot(`
-           "[project]/apps/web/app/separate-file.ts [app-client] (ecmascript) app/separate-file.ts (1:7)
-           innerArrowFunction app/source-maps-client/page.tsx (16:28)
-           innerFunction app/source-maps-client/page.tsx (13:3)
-           effectCallback app/source-maps-client/page.tsx (7:5)"
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "[project]/apps/web/app/separate-file.ts [app-client] (ecmascript) app/separate-file.ts (1:7)",
+             "innerArrowFunction app/source-maps-client/page.tsx (16:28)",
+             "innerFunction app/source-maps-client/page.tsx (13:3)",
+             "effectCallback app/source-maps-client/page.tsx (7:5)",
+           ]
           `)
         } else {
           // TODO the function name should be hidden
@@ -212,17 +205,18 @@ describe('non-root-project-monorepo', () => {
                 |       ^
               2 |"
           `)
-          // TODO webpack runtime code shouldn't be included in stack trace
-          expect(normalizeStackTrace(await getRedboxCallStack(browser)))
-            .toMatchInlineSnapshot(`
-           "eval app/separate-file.ts (1:7)
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           <FIXME-next-dist-dir>
-           innerArrowFunction app/source-maps-client/page.tsx (17:3)
-           innerFunction app/source-maps-client/page.tsx (13:3)
-           effectCallback app/source-maps-client/page.tsx (7:5)"
+          // TODO(veil): webpack runtime code shouldn't be included in stack trace (see https://linear.app/vercel/issue/NDX-509)
+          expect(await getRedboxCallStack(browser)).toMatchInlineSnapshot(`
+           [
+             "eval app/separate-file.ts (1:7)",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "<FIXME-next-dist-dir>",
+             "innerArrowFunction app/source-maps-client/page.tsx (17:3)",
+             "innerFunction app/source-maps-client/page.tsx (13:3)",
+             "effectCallback app/source-maps-client/page.tsx (7:5)",
+           ]
           `)
         }
         await browser.close()

filename: test/e2e/app-dir/server-source-maps/fixtures/default/app/module-evaluation/module.js
patch: @@ -0,0 +1 @@
+export const error = new Error('module-evaluation')

filename: test/e2e/app-dir/server-source-maps/fixtures/default/app/module-evaluation/page.js
patch: @@ -0,0 +1,6 @@
+import { error } from './module'
+
+export default function Page() {
+  console.error(error)
+  return null
+}

filename: test/e2e/app-dir/server-source-maps/server-source-maps.test.ts
patch: @@ -414,23 +414,128 @@ describe('app-dir - server source maps', () => {
           ).length - 1
         ).toEqual(3)
       }
+    } else {
+      // Bundlers silently drop invalid sourcemaps.
+      expect(
+        normalizeCliOutput(next.cliOutput).split('Invalid source map.').length -
+          1
+      ).toEqual(0)
+    }
+  })
+
+  it('sourcemaps errors during module evaluation', async () => {
+    const outputIndex = next.cliOutput.length
+    const browser = await next.browser('/module-evaluation')
+
+    if (isNextDev) {
+      await retry(() => {
+        expect(next.cliOutput.slice(outputIndex)).toContain(
+          'Error: module-evaluation'
+        )
+      })
+      const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
+      if (isTurbopack) {
+        expect(cliOutput).toContain(
+          '' +
+            '\nError: module-evaluation' +
+            // TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
+            '\n    at [project]/app/module-evaluation/module.js [app-rsc] (ecmascript) (app/module-evaluation/module.js:1:21)' +
+            // TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
+            '\n    at [project]/app/module-evaluation/page.js [app-rsc] (ecmascript) (app/module-evaluation/page.js:1:0)' +
+            '\n    at [project]/app/module-evaluation/page.js [app-rsc] (ecmascript, Next.js Server Component) (.next'
+        )
+      } else {
+        expect(cliOutput).toContain(
+          '' +
+            '\nError: module-evaluation' +
+            // TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
+            // TODO(veil): Location should be sourcemapped
+            '\n    at eval (app/module-evaluation/module.js:1:21)' +
+            // TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
+            '\n    at <unknown> (rsc)/.'
+        )
+      }
+
+      expect(cliOutput).toContain(
+        '' +
+          "\n> 1 | export const error = new Error('module-evaluation')" +
+          '\n    |                     ^'
+      )
+
+      if (isTurbopack) {
+        // TODO(veil): Ignore-list Turbopack runtime
+        await expect(browser).toDisplayCollapsedRedbox(`
+         {
+           "description": "module-evaluation",
+           "environmentLabel": "Prerender",
+           "label": "Console Error",
+           "source": "app/module-evaluation/module.js (1:22) @ [project]/app/module-evaluation/module.js [app-rsc] (ecmascript)
+         > 1 | export const error = new Error('module-evaluation')
+             |                      ^",
+           "stack": [
+             "[project]/app/module-evaluation/module.js [app-rsc] (ecmascript) app/module-evaluation/module.js (1:22)",
+             "[project]/app/module-evaluation/page.js [app-rsc] (ecmascript) app/module-evaluation/page.js (1:1)",
+             "[project]/app/module-evaluation/page.js [app-rsc] (ecmascript, Next.js Server Component) app/module-evaluation/page.js (6:1)",
+             "<FIXME-next-dist-dir>",
+             "Page <anonymous>",
+           ],
+         }
+        `)
+      } else {
+        // TODO(veil): Ignore-list Webpack runtime
+        await expect(browser).toDisplayCollapsedRedbox(`
+         {
+           "description": "module-evaluation",
+           "environmentLabel": "Prerender",
+           "label": "Console Error",
+           "source": "app/module-evaluation/module.js (1:22) @ eval
+         > 1 | export const error = new Error('module-evaluation')
+             |                      ^",
+           "stack": [
+             "eval app/module-evaluation/module.js (1:22)",
+             "<FIXME-file-protocol>",
+             "<FIXME-file-protocol>",
+             "eval rsc:/Prerender/webpack-internal:///(rsc)/app/module-evaluation/page.js (5:65)",
+             "<FIXME-file-protocol>",
+             "<FIXME-file-protocol>",
+             "Function.all <anonymous>",
+             "Function.all <anonymous>",
+             "Page <anonymous>",
+           ],
+         }
+        `)
+      }
     } else {
       if (isTurbopack) {
-        // Expect the invalid sourcemap warning only once per render.
         expect(
-          normalizeCliOutput(next.cliOutput).split('Invalid source map.')
-            .length - 1
-        ).toEqual(
-          // >= 20
-          // behavior in Node.js 20+ is intended
-          process.versions.node.startsWith('18') ? 0 : 2
+          normalizeCliOutput(next.cliOutput).replaceAll(
+            /at \d+ /g,
+            'at <TurbopackModuleID> '
+          )
+        ).toContain(
+          '' +
+            '\nError: module-evaluation' +
+            '\n    at <TurbopackModuleID> (turbopack:///[project]/app/module-evaluation/module.js:1:21)' +
+            // TODO(veil): Why are there unsourcemapped module frames below?
+            '\n    at <TurbopackModuleID> (.next/'
+        )
+        expect(normalizeCliOutput(next.cliOutput)).toContain(
+          '' +
+            "\n> 1 | export const error = new Error('module-evaluation')" +
+            '\n    |                     ^'
         )
       } else {
-        // Webpack is silent about invalid sourcemaps for next build.
         expect(
-          normalizeCliOutput(next.cliOutput).split('Invalid source map.')
-            .length - 1
-        ).toEqual(0)
+          normalizeCliOutput(next.cliOutput).replaceAll(
+            /at \d+ /g,
+            'at <WebpackModuleID> '
+          )
+        ).toContain(
+          '' +
+            '\nError: module-evaluation' +
+            // TODO(veil): column numbers are flaky in Webpack
+            '\n    at <WebpackModuleID> (webpack:///app/module-evaluation/module.js:1:'
+        )
       }
     }
   })