Repository: vercel/next.js
Pull request: Turbopack: don’t warn about webpack being configured when `experimental.turbo` is set

This is a regression introduced in 15.3.0 by #77850, when `config.experimental.turbo` was moved to `config.turbopack`. We still support `config.experimental.turbo` as well, so don’t warn when that is used.

Test Plan: Added tests for this warning message and each of the unset/turbopack/experimental.turbo cases


Closes PACK-4425

Changed files:
filename: packages/next/src/lib/turbopack-warning.ts
patch: @@ -144,7 +144,7 @@ export async function validateTurboNextConfig({
       if (key.startsWith('webpack') && rawNextConfig.webpack) {
         hasWebpackConfig = true
       }
-      if (key.startsWith('turbopack')) {
+      if (key.startsWith('turbopack') || key.startsWith('experimental.turbo')) {
         hasTurboConfig = true
       }
 
@@ -180,7 +180,7 @@ export async function validateTurboNextConfig({
       `Webpack is configured while Turbopack is not, which may cause problems.`
     )
     Log.warn(
-      `See instructions if you need to configure Turbopack:\n  https://nextjs.org/docs/app/api-reference/next-config-js/turbo\n`
+      `See instructions if you need to configure Turbopack:\n  https://nextjs.org/docs/app/api-reference/next-config-js/turbopack\n`
     )
   }
 

filename: test/e2e/config-turbopack/index.test.ts
patch: @@ -0,0 +1,96 @@
+/* eslint-disable jest/no-standalone-expect */
+import { nextTestSetup } from 'e2e-utils'
+
+const WARNING_MESSAGE = 'Webpack is configured while Turbopack is not'
+
+const itif = (condition: boolean) => (condition ? it : it.skip)
+
+describe('config-turbopack', () => {
+  describe('when webpack is configured but Turbopack is not', () => {
+    const { next, isTurbopack } = nextTestSetup({
+      files: {
+        'app/page.js': `
+          export default function Page() {
+            return <p>hello world</p>
+          }
+        `,
+        'next.config.js': `
+          module.exports = {
+            webpack: (config) => {
+              return config
+            },
+          }
+        `,
+      },
+    })
+
+    itif(isTurbopack)('warns', async () => {
+      if (next) await next.render('/')
+      expect(next.cliOutput).toContain(WARNING_MESSAGE)
+    })
+  })
+
+  describe('when webpack is configured and config.turbopack is set', () => {
+    const { next, isTurbopack } = nextTestSetup({
+      files: {
+        'app/page.js': `
+          export default function Page() {
+            return <p>hello world</p>
+          }
+        `,
+        'next.config.js': `
+          module.exports = {
+            turbopack: {
+              rules: {
+                '*.foo': {
+                  loaders: ['foo-loader']
+                }
+              }
+            },
+            webpack: (config) => {
+              return config
+            },
+          }
+        `,
+      },
+    })
+
+    itif(isTurbopack)('does not warn', async () => {
+      if (next) await next.render('/')
+      expect(next.cliOutput).not.toContain(WARNING_MESSAGE)
+    })
+  })
+
+  describe('when webpack is configured and config.experimental.turbo is set', () => {
+    const { next, isTurbopack } = nextTestSetup({
+      files: {
+        'app/page.js': `
+          export default function Page() {
+            return <p>hello world</p>
+          }
+        `,
+        'next.config.js': `
+          module.exports = {
+            experimental: {
+              turbo: {
+                rules: {
+                  '*.foo': {
+                    loaders: ['foo-loader']
+                  }
+                }
+              }
+            },
+            webpack: (config) => {
+              return config
+            },
+          }
+        `,
+      },
+    })
+
+    itif(isTurbopack)('does not warn', async () => {
+      if (next) await next.render('/')
+      expect(next.cliOutput).not.toContain(WARNING_MESSAGE)
+    })
+  })
+})