Repository: vercel/next.js
Pull request: [sourcemaps] ignore-list Webpack runtime

The way this works in dev is by assigning a dummy sourcemap to the Webpack runtime that ignore-lists everything. The mappings will will be incorrect in development. Production sourcemaps will be correct since those emit real sourcemaps for the webpack runtime where we just start ignore-listing modules that look like Webpack runtime.

Chrome already has this heuristic where it ignore-lists a source if its sourcemap ignore-lists every source. We'll use the same heuristic in the terminal and Redbox in a follow-up. We'll implement this heuristic for the Redbox and terminal in a follow-up.


https://github.com/user-attachments/assets/b250e61d-c6f9-49b6-85c4-8bdd54275a01

Closes https://linear.app/vercel/issue/NEXT-4409/



Changed files:
filename: packages/next/src/build/webpack/plugins/devtools-ignore-list-plugin.ts
patch: @@ -31,6 +31,13 @@ function defaultIsSourceMapAsset(name: string): boolean {
   return name.endsWith('.map')
 }
 
+function isWebpackRuntimeModule(modulePath: string): boolean {
+  // webpack:///webpack/bootstrap
+  // webpack://_N_E/webpack/
+  // webpack://someOtherLibrary/webpack/
+  return /^webpack:\/\/([^/]*)\/webpack/.test(modulePath)
+}
+
 /**
  * This plugin adds a field to source maps that identifies which sources are
  * vendored or runtime-injected (aka third-party) sources. These are consumed by
@@ -77,6 +84,10 @@ export default class DevToolsIgnorePlugin {
             for (const [index, path] of sourcemap.sources.entries()) {
               if (this.options.shouldIgnorePath(path)) {
                 ignoreList.push(index)
+              } else if (isWebpackRuntimeModule(path)) {
+                // Just like our EvalSourceMapDevToolPlugin ignore-lists Webpack
+                // runtime by default, we do the same here.
+                ignoreList.push(index)
               }
             }
 

filename: packages/next/src/build/webpack/plugins/eval-source-map-dev-tool-plugin.ts
patch: @@ -228,10 +228,61 @@ export default class EvalSourceMapDevToolPlugin {
           'EvalDevToolModulePlugin',
           () => 'the eval-source-map devtool is used.'
         )
-        hooks.render.tap(
-          'EvalSourceMapDevToolPlugin',
-          (source) => new ConcatSource(devtoolWarning, source)
-        )
+        hooks.render.tap('EvalSourceMapDevToolPlugin', (source, context) => {
+          if (
+            context.chunk.id === 'webpack' ||
+            context.chunk.id === 'webpack-runtime'
+          ) {
+            const sourceURL = new URL(
+              'webpack-internal://nextjs/' + context.chunk.id + '.js'
+            )
+            // Webpack runtime chunks are not sourcemapped.
+            // We insert a dummy source map that ignore-lists everything.
+            // The mappings will be incorrect but end-users will almost never interact
+            // with the webpack runtime. Users who do, can follow the instructions
+            // in the sources content and disable sourcemaps in their debugger.
+            const sourceMappingURL = new URL(
+              'data:application/json;charset=utf-8;base64,' +
+                Buffer.from(
+                  JSON.stringify({
+                    version: 3,
+                    ignoreList: [0],
+                    // Minimal, parseable mappings.
+                    mappings: 'AAAA',
+                    sources: [
+                      // TODO: This should be the original source URL so that CTRL+Click works in the terminal.
+                      sourceURL.toString(),
+                    ],
+                    sourcesContent: [
+                      '' +
+                        '// This source was generated by Next.js based off of the generated Webpack runtime.\n' +
+                        '// The mappings are incorrect.\n' +
+                        '// To get the correct line/column mappings, turn off sourcemaps in your debugger.\n' +
+                        '\n' +
+                        source.source(),
+                    ],
+                  })
+                ).toString('base64')
+            )
+
+            return new ConcatSource(
+              source,
+              new RawSource(
+                '\n//# sourceMappingURL=' +
+                  sourceMappingURL +
+                  // Webpack will add a trailing semicolon. Adding a newline
+                  // ensures the semicolon is not part of the sourceURL and actually
+                  // terminates the previous statement.
+                  '\n'
+              )
+              // We don't need to add a sourceURL here because that's handled by
+              // whatever is running this script. It'll be the file in Node.js
+              // and the static asset path in the browser.
+            )
+          } else {
+            return new ConcatSource(devtoolWarning, source)
+          }
+        })
         hooks.chunkHash.tap('EvalSourceMapDevToolPlugin', (_chunk, hash) => {
           hash.update('EvalSourceMapDevToolPlugin')
           hash.update('2')