Skip to content

Commit

Permalink
Consolidate experimental React opt-in & add ppr flag (#55560)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ztanner authored Sep 19, 2023
1 parent f630cb8 commit 33c561b
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 36 deletions.
12 changes: 5 additions & 7 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2438,8 +2437,7 @@ export default async function build(
outputFileTracingRoot,
requiredServerFiles.config,
middlewareManifest,
hasInstrumentationHook,
hasAppDir
hasInstrumentationHook
)
})
}
Expand Down
29 changes: 15 additions & 14 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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')

Expand Down Expand Up @@ -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'))
Expand Down Expand Up @@ -1956,6 +1961,7 @@ export async function copyTracedFiles(
'server.js'
)
await fs.mkdir(path.dirname(serverOutputPath), { recursive: true })

await fs.writeFile(
serverOutputPath,
`${
Expand Down Expand Up @@ -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')
Expand Down
13 changes: 7 additions & 6 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -191,7 +192,6 @@ export function getDefineEnv({
isNodeServer,
middlewareMatchers,
previewModeId,
useServerActions,
}: {
allowedRevalidateHeaderKeys: string[] | undefined
clientRouterFilters: Parameters<
Expand All @@ -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
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -2543,7 +2545,6 @@ export default async function getBaseWebpackConfig(
isNodeServer,
middlewareMatchers,
previewModeId,
useServerActions,
})
),
isClient &&
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/cli/next-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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'

Expand Down
3 changes: 2 additions & 1 deletion packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 || []) {
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ interface ExportPageInput {
incrementalCacheHandlerPath?: string
fetchCacheKeyPrefix?: string
nextConfigOutput?: NextConfigComplete['output']
serverActions?: boolean
enableExperimentalReact?: boolean
}

interface ExportPageResults {
Expand Down Expand Up @@ -154,7 +154,7 @@ export default async function exportPage({
fetchCache,
fetchCacheKeyPrefix,
incrementalCacheHandlerPath,
serverActions,
enableExperimentalReact,
}: ExportPageInput): Promise<ExportPageResults> {
setHttpClientAndAgentOptions({
httpAgentOptions,
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/lib/needs-experimental-react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { NextConfig } from '../server/config-shared'

export function needsExperimentalReact(config: NextConfig) {
return Boolean(config.experimental?.serverActions || config.experimental?.ppr)
}
3 changes: 3 additions & 0 deletions packages/next/src/server/config-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ const configSchema = {
outputFileTracingIncludes: {
type: 'object',
},
ppr: {
type: 'boolean',
},
proxyTimeout: {
minimum: 0,
type: 'number',
Expand Down
8 changes: 7 additions & 1 deletion packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/server/lib/router-utils/setup-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1733,7 +1733,6 @@ async function startWatcher(opts: SetupOpts) {
isNodeServer,
middlewareMatchers: undefined,
previewModeId: undefined,
useServerActions: !!nextConfig.experimental.serverActions,
})

Object.keys(plugin.definitions).forEach((key) => {
Expand Down
2 changes: 1 addition & 1 deletion scripts/minimal-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
49 changes: 49 additions & 0 deletions test/e2e/app-dir/rsc-basic/rsc-basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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-')
)
}
)
})
}
)

0 comments on commit 33c561b

Please sign in to comment.