Skip to content

Commit

Permalink
Ensure config warnings only show once (#45142)
Browse files Browse the repository at this point in the history
Since we now call `loadConfig()` in various processes our `execOnce`
handling isn't tracking when we have already shown warnings/logs in
another process so this adds a `silent` flag that we can leverage when
calling `loadConfig()`.

## Bug

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/contributing.md)

Fixes: [slack
thread](https://vercel.slack.com/archives/C03KAR5DCKC/p1674351677627259)
  • Loading branch information
ijjk committed Jan 22, 2023
1 parent 70282b7 commit 440560f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 62 deletions.
16 changes: 14 additions & 2 deletions packages/next/src/cli/next-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ const handleSessionStop = async () => {
const { eventCliSession } =
require('../telemetry/events/session-stopped') as typeof import('../telemetry/events/session-stopped')

const config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir)
const config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
dir,
undefined,
undefined,
true
)

let telemetry =
(traceGlobals.get('telemetry') as InstanceType<
Expand Down Expand Up @@ -493,7 +499,13 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit
cluster.settings.stdio = ['ipc', 'pipe', 'pipe']

setupFork()
config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir)
config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
dir,
undefined,
undefined,
true
)

const handleProjectDirRename = (newDir: string) => {
clusterExitUnsub()
Expand Down
160 changes: 102 additions & 58 deletions packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,22 @@ const experimentalWarning = execOnce(
}
)

export function setHttpClientAndAgentOptions(config: {
httpAgentOptions?: NextConfig['httpAgentOptions']
experimental?: {
enableUndici?: boolean
}
}) {
export function setHttpClientAndAgentOptions(
config: {
httpAgentOptions?: NextConfig['httpAgentOptions']
experimental?: {
enableUndici?: boolean
}
},
silent = false
) {
if (isAboveNodejs16) {
// Node.js 18 has undici built-in.
if (config.experimental?.enableUndici && !isAboveNodejs18) {
// When appDir is enabled undici is the default because of Response.clone() issues in node-fetch
;(globalThis as any).__NEXT_USE_UNDICI = config.experimental?.enableUndici
}
} else if (config.experimental?.enableUndici) {
} else if (config.experimental?.enableUndici && !silent) {
Log.warn(
`\`enableUndici\` option requires Node.js v${NODE_16_VERSION} or greater. Falling back to \`node-fetch\``
)
Expand Down Expand Up @@ -130,14 +133,17 @@ export function warnOptionHasBeenMovedOutOfExperimental(
config: NextConfig,
oldKey: string,
newKey: string,
configFileName: string
configFileName: string,
silent = false
) {
if (config.experimental && oldKey in config.experimental) {
Log.warn(
`\`${oldKey}\` has been moved out of \`experimental\`` +
(newKey.includes('.') ? ` and into \`${newKey}\`` : '') +
`. Please update your ${configFileName} file accordingly.`
)
if (!silent) {
Log.warn(
`\`${oldKey}\` has been moved out of \`experimental\`` +
(newKey.includes('.') ? ` and into \`${newKey}\`` : '') +
`. Please update your ${configFileName} file accordingly.`
)
}

let current = config
const newKeys = newKey.split('.')
Expand All @@ -152,9 +158,13 @@ export function warnOptionHasBeenMovedOutOfExperimental(
return config
}

function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
function assignDefaults(
dir: string,
userConfig: { [key: string]: any },
silent = false
) {
const configFileName = userConfig.configFileName
if (typeof userConfig.exportTrailingSlash !== 'undefined') {
if (!silent && typeof userConfig.exportTrailingSlash !== 'undefined') {
console.warn(
chalk.yellow.bold('Warning: ') +
`The "exportTrailingSlash" option has been renamed to "trailingSlash". Please update your ${configFileName}.`
Expand Down Expand Up @@ -200,7 +210,7 @@ function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
}
}

if (enabledExperiments.length > 0) {
if (!silent && enabledExperiments.length > 0) {
experimentalWarning(configFileName, enabledExperiments)
}
}
Expand Down Expand Up @@ -572,63 +582,75 @@ function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
result,
'relay',
'compiler.relay',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'styledComponents',
'compiler.styledComponents',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'emotion',
'compiler.emotion',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'reactRemoveProperties',
'compiler.reactRemoveProperties',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'removeConsole',
'compiler.removeConsole',
configFileName
configFileName,
silent
)

if (result.experimental?.swcMinifyDebugOptions) {
Log.warn(
'SWC minify debug option specified. This option is for debugging minifier issues and will be removed once SWC minifier is stable.'
)
if (!silent) {
Log.warn(
'SWC minify debug option specified. This option is for debugging minifier issues and will be removed once SWC minifier is stable.'
)
}
}

if ((result.experimental as any).outputStandalone) {
Log.warn(
`experimental.outputStandalone has been renamed to "output: 'standalone'", please move the config.`
)
if (!silent) {
Log.warn(
`experimental.outputStandalone has been renamed to "output: 'standalone'", please move the config.`
)
}
result.output = 'standalone'
}

warnOptionHasBeenMovedOutOfExperimental(
result,
'transpilePackages',
'transpilePackages',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'skipMiddlewareUrlNormalize',
'skipMiddlewareUrlNormalize',
configFileName
configFileName,
silent
)
warnOptionHasBeenMovedOutOfExperimental(
result,
'skipTrailingSlashRedirect',
'skipTrailingSlashRedirect',
configFileName
configFileName,
silent
)

if (
Expand All @@ -638,19 +660,23 @@ function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
result.experimental.outputFileTracingRoot = resolve(
result.experimental.outputFileTracingRoot
)
Log.warn(
`experimental.outputFileTracingRoot should be absolute, using: ${result.experimental.outputFileTracingRoot}`
)
if (!silent) {
Log.warn(
`experimental.outputFileTracingRoot should be absolute, using: ${result.experimental.outputFileTracingRoot}`
)
}
}

if (result.output === 'standalone' && !result.outputFileTracing) {
Log.warn(
`"output: 'standalone'" requires outputFileTracing not be disabled please enable it to leverage the standalone build`
)
if (!silent) {
Log.warn(
`"output: 'standalone'" requires outputFileTracing not be disabled please enable it to leverage the standalone build`
)
}
result.output = undefined
}

setHttpClientAndAgentOptions(result || defaultConfig)
setHttpClientAndAgentOptions(result || defaultConfig, silent)

if (result.i18n) {
const { i18n } = result
Expand All @@ -668,7 +694,7 @@ function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
)
}

if (i18n.locales.length > 100) {
if (i18n.locales.length > 100 && !silent) {
Log.warn(
`Received ${i18n.locales.length} i18n.locales items which exceeds the recommended max of 100.\nSee more info here: https://nextjs.org/docs/advanced-features/i18n-routing#how-does-this-work-with-static-generation`
)
Expand Down Expand Up @@ -700,7 +726,7 @@ function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
altItem.domain !== item.domain
)

if (defaultLocaleDuplicate) {
if (!silent && defaultLocaleDuplicate) {
console.warn(
`Both ${item.domain} and ${defaultLocaleDuplicate.domain} configured the defaultLocale ${item.defaultLocale} but only one can. Change one item's default locale to continue`
)
Expand Down Expand Up @@ -830,9 +856,18 @@ export default async function loadConfig(
phase: string,
dir: string,
customConfig?: object | null,
rawConfig?: boolean
rawConfig?: boolean,
silent?: boolean
): Promise<NextConfigComplete> {
await loadEnvConfig(dir, phase === PHASE_DEVELOPMENT_SERVER, Log)
const curLog = silent
? {
warn: () => {},
info: () => {},
error: () => {},
}
: Log

await loadEnvConfig(dir, phase === PHASE_DEVELOPMENT_SERVER, curLog)

if (!customConfig) {
loadWebpackHook()
Expand All @@ -841,11 +876,15 @@ export default async function loadConfig(
let configFileName = 'next.config.js'

if (customConfig) {
return assignDefaults(dir, {
configOrigin: 'server',
configFileName,
...customConfig,
}) as NextConfigComplete
return assignDefaults(
dir,
{
configOrigin: 'server',
configFileName,
...customConfig,
},
silent
) as NextConfigComplete
}

const path = await findUp(CONFIG_FILES, { cwd: dir })
Expand All @@ -872,7 +911,7 @@ export default async function loadConfig(
return userConfigModule
}
} catch (err) {
Log.error(
curLog.error(
`Failed to load ${configFileName}, see more info here https://nextjs.org/docs/messages/next-config-error`
)
throw err
Expand All @@ -884,8 +923,8 @@ export default async function loadConfig(

const validateResult = validateConfig(userConfig)

if (validateResult.errors) {
Log.warn(`Invalid next.config.js options detected: `)
if (!silent && validateResult.errors) {
curLog.warn(`Invalid next.config.js options detected: `)

// Only load @segment/ajv-human-errors when invalid config is detected
const { AggregateAjvError } =
Expand All @@ -903,7 +942,7 @@ export default async function loadConfig(
}

if (Object.keys(userConfig).length === 0) {
Log.warn(
curLog.warn(
`Detected ${configFileName}, no exported configuration found. https://nextjs.org/docs/messages/empty-configuration`
)
}
Expand All @@ -924,12 +963,16 @@ export default async function loadConfig(
: canonicalBase) || ''
}

const completeConfig = assignDefaults(dir, {
configOrigin: relative(dir, path),
configFile: path,
configFileName,
...userConfig,
}) as NextConfigComplete
const completeConfig = assignDefaults(
dir,
{
configOrigin: relative(dir, path),
configFile: path,
configFileName,
...userConfig,
},
silent
) as NextConfigComplete
setFontLoaderDefaults(completeConfig)
return completeConfig
} else {
Expand All @@ -956,10 +999,11 @@ export default async function loadConfig(
// reactRoot can be updated correctly even with no next.config.js
const completeConfig = assignDefaults(
dir,
defaultConfig
defaultConfig,
silent
) as NextConfigComplete
completeConfig.configFileName = configFileName
setHttpClientAndAgentOptions(completeConfig)
setHttpClientAndAgentOptions(completeConfig, silent)
setFontLoaderDefaults(completeConfig)
return completeConfig
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants'
}
dir = getProjectDir(dir)

const config = await loadConfig(PHASE_DEVELOPMENT_SERVER, dir)
const config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
dir,
undefined,
undefined,
true
)
const distDir = path.join(dir, config.distDir || '.next')
const eventsPath = path.join(distDir, '_events.json')

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/telemetry/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export class Telemetry {
JSON.stringify(allEvents)
)

spawn(process.execPath, [require.resolve('./deteched-flush'), mode, dir], {
spawn(process.execPath, [require.resolve('./detached-flush'), mode, dir], {
detached: !this.NEXT_TELEMETRY_DEBUG,
windowsHide: true,
shell: false,
Expand Down

0 comments on commit 440560f

Please sign in to comment.