Consolidate experimental React opt-in & add ppr
flag (#55560)
This consolidates how we're evaluating when to opt into `react@experimental` since it's sprinkled in a lot of spots. Also adds a new flag to opt into the experimental channel Closes NEXT-1632
This commit is contained in:
parent
f630cb8e56
commit
33c561b21d
12 changed files with 99 additions and 36 deletions
|
@ -145,6 +145,7 @@ import { createClientRouterFilter } from '../lib/create-client-router-filter'
|
|||
import { createValidFileMatcher } from '../server/lib/find-page-file'
|
||||
import { startTypeChecking } from './type-check'
|
||||
import { generateInterceptionRoutesRewrites } from '../lib/generate-interception-routes-rewrites'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
|
||||
import { buildDataRoute } from '../server/lib/router-utils/build-data-route'
|
||||
import { defaultOverrides } from '../server/require-hook'
|
||||
|
@ -1255,11 +1256,9 @@ export default async function build(
|
|||
__NEXT_INCREMENTAL_CACHE_IPC_PORT: incrementalCacheIpcPort + '',
|
||||
__NEXT_INCREMENTAL_CACHE_IPC_KEY:
|
||||
incrementalCacheIpcValidationKey,
|
||||
__NEXT_PRIVATE_PREBUNDLED_REACT: hasAppDir
|
||||
? config.experimental.serverActions
|
||||
? 'experimental'
|
||||
: 'next'
|
||||
: '',
|
||||
__NEXT_PRIVATE_PREBUNDLED_REACT: needsExperimentalReact(config)
|
||||
? 'experimental'
|
||||
: 'next',
|
||||
},
|
||||
},
|
||||
enableWorkerThreads: config.experimental.workerThreads,
|
||||
|
@ -2438,8 +2437,7 @@ export default async function build(
|
|||
outputFileTracingRoot,
|
||||
requiredServerFiles.config,
|
||||
middlewareManifest,
|
||||
hasInstrumentationHook,
|
||||
hasAppDir
|
||||
hasInstrumentationHook
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { NextConfigComplete } from '../server/config-shared'
|
||||
import type { NextConfig, NextConfigComplete } from '../server/config-shared'
|
||||
import type { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
|
||||
import type { AssetBinding } from './webpack/loaders/get-module-build-info'
|
||||
import type { GetStaticPaths, PageConfig, ServerRuntime } from 'next/types'
|
||||
|
@ -67,7 +67,8 @@ import { nodeFs } from '../server/lib/node-fs-methods'
|
|||
import * as ciEnvironment from '../telemetry/ci-info'
|
||||
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
|
||||
import { denormalizeAppPagePath } from '../shared/lib/page-path/denormalize-app-path'
|
||||
// import { AppRouteRouteModule } from '../server/future/route-modules/app-route/module'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
|
||||
const { AppRouteRouteModule } =
|
||||
require('../server/future/route-modules/app-route/module.compiled') as typeof import('../server/future/route-modules/app-route/module')
|
||||
|
||||
|
@ -1826,13 +1827,17 @@ export async function copyTracedFiles(
|
|||
pageKeys: readonly string[],
|
||||
appPageKeys: readonly string[] | undefined,
|
||||
tracingRoot: string,
|
||||
serverConfig: { [key: string]: any },
|
||||
serverConfig: NextConfig,
|
||||
middlewareManifest: MiddlewareManifest,
|
||||
hasInstrumentationHook: boolean,
|
||||
hasAppDir: boolean
|
||||
hasInstrumentationHook: boolean
|
||||
) {
|
||||
const outputPath = path.join(distDir, 'standalone')
|
||||
let moduleType = false
|
||||
const nextConfig = {
|
||||
...serverConfig,
|
||||
distDir: `./${path.relative(dir, distDir)}`,
|
||||
}
|
||||
const hasExperimentalReact = needsExperimentalReact(nextConfig)
|
||||
try {
|
||||
const packageJsonPath = path.join(distDir, '../package.json')
|
||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'))
|
||||
|
@ -1956,6 +1961,7 @@ export async function copyTracedFiles(
|
|||
'server.js'
|
||||
)
|
||||
await fs.mkdir(path.dirname(serverOutputPath), { recursive: true })
|
||||
|
||||
await fs.writeFile(
|
||||
serverOutputPath,
|
||||
`${
|
||||
|
@ -1985,17 +1991,12 @@ const currentPort = parseInt(process.env.PORT, 10) || 3000
|
|||
const hostname = process.env.HOSTNAME || '0.0.0.0'
|
||||
|
||||
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
||||
const nextConfig = ${JSON.stringify({
|
||||
...serverConfig,
|
||||
distDir: `./${path.relative(dir, distDir)}`,
|
||||
})}
|
||||
const nextConfig = ${JSON.stringify(nextConfig)}
|
||||
|
||||
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
|
||||
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = ${hasAppDir}
|
||||
? nextConfig.experimental && nextConfig.experimental.serverActions
|
||||
? 'experimental'
|
||||
: 'next'
|
||||
: '';
|
||||
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = ${hasExperimentalReact}
|
||||
? 'experimental'
|
||||
: 'next'
|
||||
|
||||
require('next')
|
||||
const { startServer } = require('next/dist/server/lib/start-server')
|
||||
|
|
|
@ -71,6 +71,7 @@ import { getSupportedBrowsers } from './utils'
|
|||
import { MemoryWithGcCachePlugin } from './webpack/plugins/memory-with-gc-cache-plugin'
|
||||
import { getBabelConfigFile } from './get-babel-config-file'
|
||||
import { defaultOverrides } from '../server/require-hook'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
|
||||
type ExcludesFalse = <T>(x: T | false) => x is T
|
||||
type ClientEntries = {
|
||||
|
@ -191,7 +192,6 @@ export function getDefineEnv({
|
|||
isNodeServer,
|
||||
middlewareMatchers,
|
||||
previewModeId,
|
||||
useServerActions,
|
||||
}: {
|
||||
allowedRevalidateHeaderKeys: string[] | undefined
|
||||
clientRouterFilters: Parameters<
|
||||
|
@ -208,7 +208,6 @@ export function getDefineEnv({
|
|||
isNodeServer: boolean
|
||||
middlewareMatchers: MiddlewareMatcher[] | undefined
|
||||
previewModeId: string | undefined
|
||||
useServerActions: boolean
|
||||
}) {
|
||||
return {
|
||||
// internal field to identify the plugin config
|
||||
|
@ -373,8 +372,9 @@ export function getDefineEnv({
|
|||
'process.env.TURBOPACK': JSON.stringify(false),
|
||||
...(isNodeServer
|
||||
? {
|
||||
'process.env.__NEXT_EXPERIMENTAL_REACT':
|
||||
JSON.stringify(useServerActions),
|
||||
'process.env.__NEXT_EXPERIMENTAL_REACT': JSON.stringify(
|
||||
needsExperimentalReact(config)
|
||||
),
|
||||
}
|
||||
: undefined),
|
||||
}
|
||||
|
@ -808,7 +808,9 @@ export default async function getBaseWebpackConfig(
|
|||
const disableOptimizedLoading = true
|
||||
const enableTypedRoutes = !!config.experimental.typedRoutes && hasAppDir
|
||||
const useServerActions = !!config.experimental.serverActions && hasAppDir
|
||||
const bundledReactChannel = useServerActions ? '-experimental' : ''
|
||||
const bundledReactChannel = needsExperimentalReact(config)
|
||||
? '-experimental'
|
||||
: ''
|
||||
|
||||
if (isClient) {
|
||||
if (
|
||||
|
@ -2543,7 +2545,6 @@ export default async function getBaseWebpackConfig(
|
|||
isNodeServer,
|
||||
middlewareMatchers,
|
||||
previewModeId,
|
||||
useServerActions,
|
||||
})
|
||||
),
|
||||
isClient &&
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
getReservedPortExplanation,
|
||||
isPortIsReserved,
|
||||
} from '../lib/helpers/get-reserved-port'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
|
||||
let dir: string
|
||||
let child: undefined | ReturnType<typeof fork>
|
||||
|
@ -198,8 +199,7 @@ const nextDev: CliCommand = async (args) => {
|
|||
},
|
||||
})
|
||||
|
||||
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = config.experimental
|
||||
.serverActions
|
||||
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = needsExperimentalReact(config)
|
||||
? 'experimental'
|
||||
: 'next'
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ import { MiddlewareManifest } from '../build/webpack/plugins/middleware-plugin'
|
|||
import { isAppRouteRoute } from '../lib/is-app-route-route'
|
||||
import { isAppPageRoute } from '../lib/is-app-page-route'
|
||||
import isError from '../lib/is-error'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
|
||||
const exists = promisify(existsOrig)
|
||||
|
||||
|
@ -730,7 +731,7 @@ export default async function exportApp(
|
|||
fetchCacheKeyPrefix: nextConfig.experimental.fetchCacheKeyPrefix,
|
||||
incrementalCacheHandlerPath:
|
||||
nextConfig.experimental.incrementalCacheHandlerPath,
|
||||
serverActions: nextConfig.experimental.serverActions,
|
||||
enableExperimentalReact: needsExperimentalReact(nextConfig),
|
||||
})
|
||||
|
||||
for (const validation of result.ampValidations || []) {
|
||||
|
|
|
@ -97,7 +97,7 @@ interface ExportPageInput {
|
|||
incrementalCacheHandlerPath?: string
|
||||
fetchCacheKeyPrefix?: string
|
||||
nextConfigOutput?: NextConfigComplete['output']
|
||||
serverActions?: boolean
|
||||
enableExperimentalReact?: boolean
|
||||
}
|
||||
|
||||
interface ExportPageResults {
|
||||
|
@ -154,7 +154,7 @@ export default async function exportPage({
|
|||
fetchCache,
|
||||
fetchCacheKeyPrefix,
|
||||
incrementalCacheHandlerPath,
|
||||
serverActions,
|
||||
enableExperimentalReact,
|
||||
}: ExportPageInput): Promise<ExportPageResults> {
|
||||
setHttpClientAndAgentOptions({
|
||||
httpAgentOptions,
|
||||
|
@ -171,7 +171,7 @@ export default async function exportPage({
|
|||
if (renderOpts.deploymentId) {
|
||||
process.env.NEXT_DEPLOYMENT_ID = renderOpts.deploymentId
|
||||
}
|
||||
if (serverActions) {
|
||||
if (enableExperimentalReact) {
|
||||
process.env.__NEXT_EXPERIMENTAL_REACT = 'true'
|
||||
}
|
||||
const { query: originalQuery = {} } = pathMap
|
||||
|
|
5
packages/next/src/lib/needs-experimental-react.ts
Normal file
5
packages/next/src/lib/needs-experimental-react.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import type { NextConfig } from '../server/config-shared'
|
||||
|
||||
export function needsExperimentalReact(config: NextConfig) {
|
||||
return Boolean(config.experimental?.serverActions || config.experimental?.ppr)
|
||||
}
|
|
@ -384,6 +384,9 @@ const configSchema = {
|
|||
outputFileTracingIncludes: {
|
||||
type: 'object',
|
||||
},
|
||||
ppr: {
|
||||
type: 'boolean',
|
||||
},
|
||||
proxyTimeout: {
|
||||
minimum: 0,
|
||||
type: 'number',
|
||||
|
|
|
@ -302,10 +302,16 @@ export interface ExperimentalConfig {
|
|||
instrumentationHook?: boolean
|
||||
|
||||
/**
|
||||
* Enable `react@experimental` channel for the `app` directory.
|
||||
* Enables server actions. Using this feature will enable the `react@experimental` for the `app` directory.
|
||||
* @see https://nextjs.org/docs/app/api-reference/functions/server-actions
|
||||
*/
|
||||
serverActions?: boolean
|
||||
|
||||
/**
|
||||
* Using this feature will enable the `react@experimental` for the `app` directory.
|
||||
*/
|
||||
ppr?: boolean
|
||||
|
||||
/**
|
||||
* Allows adjusting body parser size limit for server actions.
|
||||
*/
|
||||
|
|
|
@ -1733,7 +1733,6 @@ async function startWatcher(opts: SetupOpts) {
|
|||
isNodeServer,
|
||||
middlewareMatchers: undefined,
|
||||
previewModeId: undefined,
|
||||
useServerActions: !!nextConfig.experimental.serverActions,
|
||||
})
|
||||
|
||||
Object.keys(plugin.definitions).forEach((key) => {
|
||||
|
|
|
@ -4,7 +4,7 @@ console.time('next-wall-time')
|
|||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
// Change this to 'experimental' for server actions
|
||||
// Change this to 'experimental' to opt into the React experimental channel (needed for server actions, ppr)
|
||||
process.env.__NEXT_PRIVATE_PREBUNDLED_REACT = 'next'
|
||||
|
||||
if (process.env.LOG_REQUIRE) {
|
||||
|
|
|
@ -592,5 +592,54 @@ createNextDescribe(
|
|||
await Promise.all(promises)
|
||||
})
|
||||
}
|
||||
|
||||
describe('react@experimental', () => {
|
||||
it.each([{ flag: 'ppr' }, { flag: 'serverActions' }])(
|
||||
'should opt into the react@experimental when enabling $flag',
|
||||
async ({ flag }) => {
|
||||
await next.stop()
|
||||
await next.patchFile(
|
||||
'next.config.js',
|
||||
`
|
||||
module.exports = {
|
||||
experimental: {
|
||||
${flag}: true
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await next.start()
|
||||
const resPages$ = await next.render$('/app-react')
|
||||
const ssrPagesReactVersions = [
|
||||
await resPages$('#react').text(),
|
||||
await resPages$('#react-dom').text(),
|
||||
await resPages$('#react-dom-server').text(),
|
||||
await resPages$('#client-react').text(),
|
||||
await resPages$('#client-react-dom').text(),
|
||||
await resPages$('#client-react-dom-server').text(),
|
||||
]
|
||||
|
||||
ssrPagesReactVersions.forEach((version) => {
|
||||
expect(version).toMatch('-experimental-')
|
||||
})
|
||||
|
||||
const browser = await next.browser('/app-react')
|
||||
const browserAppReactVersions = await browser.eval(`
|
||||
[
|
||||
document.querySelector('#react').innerText,
|
||||
document.querySelector('#react-dom').innerText,
|
||||
document.querySelector('#react-dom-server').innerText,
|
||||
document.querySelector('#client-react').innerText,
|
||||
document.querySelector('#client-react-dom').innerText,
|
||||
document.querySelector('#client-react-dom-server').innerText,
|
||||
]
|
||||
`)
|
||||
browserAppReactVersions.forEach((version) =>
|
||||
expect(version).toMatch('-experimental-')
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue