diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts index e89c80b63344b..aeabcd36ab334 100644 --- a/packages/next/server/config.ts +++ b/packages/next/server/config.ts @@ -9,8 +9,9 @@ import { CONFIG_FILES, PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' import { execOnce } from '../shared/lib/utils' import { defaultConfig, - NextConfigComplete, normalizeConfig, + ExperimentalConfig, + NextConfigComplete, } from './config-shared' import { loadWebpackHook } from './config-utils' import { @@ -43,6 +44,23 @@ const experimentalWarning = execOnce( } ) +const missingExperimentalWarning = execOnce( + (configFileName: string, features: string[]) => { + const s = features.length > 1 ? 's' : '' + const dont = features.length > 1 ? 'do not' : 'does not' + const them = features.length > 1 ? 'them' : 'it' + Log.warn( + chalk.bold( + `You have defined experimental feature${s} (${features.join( + ', ' + )}) in ${configFileName} that ${dont} exist in this version of Next.js.` + ) + ) + Log.warn(`Please remove ${them} from your configuration.`) + console.warn() + } +) + function assignDefaults(userConfig: { [key: string]: any }) { const configFileName = userConfig.configFileName if (typeof userConfig.exportTrailingSlash !== 'undefined') { @@ -77,13 +95,32 @@ function assignDefaults(userConfig: { [key: string]: any }) { return currentConfig } - if ( - key === 'experimental' && - value !== defaultConfig[key] && - typeof value === 'object' && - Object.keys(value).length > 0 - ) { - experimentalWarning(configFileName, Object.keys(value)) + if (key === 'experimental' && typeof value === 'object') { + const enabledMissingExperiments: string[] = [] + const enabledExperiments: (keyof ExperimentalConfig)[] = [] + + // defaultConfig.experimental is predefined and will never be undefined + // This is only a type guard for the typescript + if (defaultConfig.experimental) { + for (const featureName of Object.keys( + value + ) as (keyof ExperimentalConfig)[]) { + if (!(featureName in defaultConfig.experimental)) { + enabledMissingExperiments.push(featureName) + } else if ( + value[featureName] !== defaultConfig.experimental[featureName] + ) { + enabledExperiments.push(featureName) + } + } + } + + if (enabledMissingExperiments.length > 0) { + missingExperimentalWarning(configFileName, enabledMissingExperiments) + } + if (enabledExperiments.length > 0) { + experimentalWarning(configFileName, enabledExperiments) + } } if (key === 'distDir') { diff --git a/test/integration/config-experimental-warning/test/index.test.js b/test/integration/config-experimental-warning/test/index.test.js index dc20cd6820471..151684dfb9ee1 100644 --- a/test/integration/config-experimental-warning/test/index.test.js +++ b/test/integration/config-experimental-warning/test/index.test.js @@ -44,13 +44,13 @@ describe('Config Experimental Warning', () => { module.exports = { target: 'server', experimental: { - something: true + newNextLinkBehavior: true } } `) const { stderr } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch( - 'You have enabled experimental feature (something) in next.config.js.' + 'You have enabled experimental feature (newNextLinkBehavior) in next.config.js.' ) }) @@ -59,13 +59,28 @@ describe('Config Experimental Warning', () => { module.exports = (phase) => ({ target: 'server', experimental: { - something: true + newNextLinkBehavior: true } }) `) const { stderr } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch( - 'You have enabled experimental feature (something) in next.config.js.' + 'You have enabled experimental feature (newNextLinkBehavior) in next.config.js.' + ) + }) + + it('should not show warning with default value', async () => { + configFile.write(` + module.exports = (phase) => ({ + target: 'server', + experimental: { + newNextLinkBehavior: false + } + }) + `) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + expect(stderr).not.toMatch( + 'You have enabled experimental feature (newNextLinkBehavior) in next.config.js.' ) }) @@ -73,14 +88,14 @@ describe('Config Experimental Warning', () => { configFile.write(` module.exports = { experimental: { - something: true, - another: 1, + newNextLinkBehavior: true, + legacyBrowsers: false, } } `) const { stderr } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch( - 'You have enabled experimental features (something, another) in next.config.js.' + 'You have enabled experimental features (newNextLinkBehavior, legacyBrowsers) in next.config.js.' ) }) @@ -88,14 +103,60 @@ describe('Config Experimental Warning', () => { configFileMjs.write(` const config = { experimental: { - something: true, + newNextLinkBehavior: true, } } export default config `) const { stderr } = await nextBuild(appDir, [], { stderr: true }) expect(stderr).toMatch( - 'You have enabled experimental feature (something) in next.config.mjs.' + 'You have enabled experimental feature (newNextLinkBehavior) in next.config.mjs.' + ) + }) + + it('should show warning with next.config.js from object with non-exist experimental', async () => { + configFile.write(` + const config = { + experimental: { + foo: true + } + } + module.exports = config + `) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + expect(stderr).toMatch( + 'You have defined experimental feature (foo) in next.config.js that does not exist in this version' + ) + }) + + it('should show warning with next.config.mjs from object with non-exist experimental', async () => { + configFileMjs.write(` + const config = { + experimental: { + foo: true + } + } + export default config + `) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + expect(stderr).toMatch( + 'You have defined experimental feature (foo) in next.config.mjs that does not exist in this version' + ) + }) + + it('should show warning with next.config.js from object with multiple non-exist experimental', async () => { + configFile.write(` + const config = { + experimental: { + foo: true, + bar: false + } + } + module.exports = config + `) + const { stderr } = await nextBuild(appDir, [], { stderr: true }) + expect(stderr).toMatch( + 'You have defined experimental features (foo, bar) in next.config.js that do not exist in this version' ) }) })