add experimental flag to allow forcing NODE_ENV=development in builds (#65463)

For debugging purposes, it can be useful to set `NODE_ENV=development`
during a `next build`. Currently this value is forced to be production
in Next.js. This PR adds an experimental flag to not force a mode of
`production` when the flag is set.

To use this flag, you'll still need to explicitly set
`NODE_ENV=development`, while also enabling
`nextConfig.experimental.allowDevelopmentBuild`

Closes NEXT-3277
This commit is contained in:
Zack Tanner 2024-05-07 11:35:13 -07:00 committed by GitHub
parent d0d22ac625
commit 8b9aa2dcc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 126 additions and 2 deletions

View file

@ -117,7 +117,9 @@ async function loaderTransform(
filename,
isServer,
isPageFile,
development: this.mode === 'development',
development:
this.mode === 'development' ||
!!nextConfig.experimental?.allowDevelopmentBuild,
hasReactRefresh,
modularizeImports: nextConfig?.modularizeImports,
optimizePackageImports: nextConfig?.experimental?.optimizePackageImports,

View file

@ -159,7 +159,10 @@ export function getDefineEnv({
'process.turbopack': isTurbopack,
'process.env.TURBOPACK': isTurbopack,
// TODO: enforce `NODE_ENV` on `process.env`, and add a test:
'process.env.NODE_ENV': dev ? 'development' : 'production',
'process.env.NODE_ENV':
dev || config.experimental.allowDevelopmentBuild
? 'development'
: 'production',
'process.env.NEXT_RUNTIME': isEdgeServer
? 'edge'
: isNodeServer

View file

@ -421,6 +421,7 @@ export const configSchema: zod.ZodType<NextConfig> = z.lazy(() =>
useEarlyImport: z.boolean().optional(),
testProxy: z.boolean().optional(),
defaultTestRunner: z.enum(SUPPORTED_TEST_RUNNERS_LIST).optional(),
allowDevelopmentBuild: z.literal(true).optional(),
})
.optional(),
exportPathMap: z

View file

@ -452,6 +452,10 @@ export interface ExperimentalConfig {
* Set a default test runner to be used by `next experimental-test`.
*/
defaultTestRunner?: SupportedTestRunners
/**
* Allow NODE_ENV=development even for `next build`.
*/
allowDevelopmentBuild?: true
}
export type ExportPathMap = {
@ -963,6 +967,7 @@ export const defaultConfig: NextConfig = {
dynamic: 30,
static: 300,
},
allowDevelopmentBuild: undefined,
},
bundlePagesRouterDependencies: false,
}

View file

@ -262,6 +262,15 @@ function assignDefaults(
const result = { ...defaultConfig, ...config }
if (
result.experimental?.allowDevelopmentBuild &&
process.env.NODE_ENV !== 'development'
) {
throw new Error(
`The experimental.allowDevelopmentBuild option requires NODE_ENV to be explicitly set to 'development'.`
)
}
if (
result.experimental?.ppr &&
!process.env.__NEXT_VERSION!.includes('canary') &&

View file

@ -0,0 +1,74 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
describe('allow-development-build', () => {
describe('with NODE_ENV set to development', () => {
const { next } = nextTestSetup({
files: __dirname,
env: {
NODE_ENV: 'development',
},
nextConfig: {
experimental: {
allowDevelopmentBuild: true,
},
},
})
it('should warn about a non-standard NODE_ENV', () => {
expect(next.cliOutput).toContain(
'You are using a non-standard "NODE_ENV" value in your environment'
)
})
it.each(['app-page', 'pages-page'])(
`should show React development errors in %s`,
async (page) => {
const browser = await next.browser(page, {
pushErrorAsConsoleLog: true,
})
await retry(async () => {
const logs = await browser.log()
const errorLogs = logs.filter((log) => log.source === 'error')
expect(errorLogs).toEqual(
expect.arrayContaining([
{
message: expect.toBeOneOf([
expect.stringContaining(
"Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client."
),
expect.stringContaining(
'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.'
),
]),
source: 'error',
},
])
)
})
}
)
})
describe('with NODE_ENV not set to development', () => {
const { next } = nextTestSetup({
files: __dirname,
skipStart: true,
nextConfig: {
experimental: {
allowDevelopmentBuild: true,
},
},
})
it('should fail the build with a message about not setting NODE_ENV', async () => {
await next.start().catch(() => {})
expect(next.cliOutput).toContain(
"The experimental.allowDevelopmentBuild option requires NODE_ENV to be explicitly set to 'development'"
)
})
})
})

View file

@ -0,0 +1,11 @@
'use client'
import React from 'react'
export default function Page() {
return (
<div>
Hello World{' '}
{typeof window !== 'undefined' && <span>Hydration Error!</span>}
</div>
)
}

View file

@ -0,0 +1,11 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

View file

@ -0,0 +1,8 @@
export default function Page() {
return (
<div>
Hello World{' '}
{typeof window !== 'undefined' && <span>Hydration Error!</span>}
</div>
)
}