Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop experimental.reactRoot in favor of auto detection #37956

Merged
merged 2 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/next/bin/next.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env node
import * as log from '../build/output/log'
import arg from 'next/dist/compiled/arg/index.js'
import React from 'react'
import { NON_STANDARD_NODE_ENV } from '../lib/constants'
import { shouldUseReactRoot } from '../server/utils'
;['react', 'react-dom'].forEach((dependency) => {
try {
// When 'npm link' is used it checks the clone location. Not the project.
Expand Down Expand Up @@ -107,7 +107,6 @@ if (process.env.NODE_ENV) {
;(process.env as any).NODE_ENV = process.env.NODE_ENV || defaultEnv
;(process.env as any).NEXT_RUNTIME = 'nodejs'

const shouldUseReactRoot = parseInt(React.version) >= 18
if (shouldUseReactRoot) {
;(process.env as any).__NEXT_REACT_ROOT = 'true'
}
Expand Down
1 change: 0 additions & 1 deletion packages/next/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ export function getServerlessEntry(opts: {
page: opts.page,
poweredByHeader: opts.config.poweredByHeader ? 'true' : '',
previewProps: JSON.stringify(opts.previewMode),
reactRoot: !!opts.config.experimental.reactRoot ? 'true' : '',
runtimeConfig:
Object.keys(opts.config.publicRuntimeConfig).length > 0 ||
Object.keys(opts.config.serverRuntimeConfig).length > 0
Expand Down
32 changes: 11 additions & 21 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,26 +364,17 @@ export default async function getBaseWebpackConfig(
rewrites.afterFiles.length > 0 ||
rewrites.fallback.length > 0

// Make sure `reactRoot` is enabled when React 18 or experimental is detected.
if (hasReactRoot) {
config.experimental.reactRoot = true
}

// Only inform during one of the builds
if (isClient && config.experimental.reactRoot && !hasReactRoot) {
// It's fine to only mention React 18 here as we don't recommend people to try experimental.
Log.warn('You have to use React 18 to use `experimental.reactRoot`.')
}

if (isClient && config.experimental.runtime && !hasReactRoot) {
throw new Error(
'`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.'
)
}
if (config.experimental.serverComponents && !hasReactRoot) {
throw new Error(
'`experimental.serverComponents` requires React 18 to be installed.'
)
if (isClient && !hasReactRoot) {
if (config.experimental.runtime) {
throw new Error(
'`experimental.runtime` requires React 18 to be installed.'
)
}
if (config.experimental.serverComponents) {
throw new Error(
'`experimental.serverComponents` requires React 18 to be installed.'
)
}
}

const hasConcurrentFeatures = hasReactRoot
Expand Down Expand Up @@ -1830,7 +1821,6 @@ export default async function getBaseWebpackConfig(
reactProductionProfiling,
webpack: !!config.webpack,
hasRewrites,
reactRoot: config.experimental.reactRoot,
runtime: config.experimental.runtime,
swcMinify: config.swcMinify,
swcLoader: useSWCLoader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ export function getRender({
page,
extendRenderOpts: {
buildId,
reactRoot: true,
runtime: 'edge',
supportsDynamicHTML: true,
disableOptimizedLoading: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export type ServerlessLoaderQuery = {
previewProps: string
loadedEnvFiles: string
i18n: string
reactRoot: string
}

const nextServerlessLoader: webpack.loader.Loader = function () {
Expand All @@ -53,7 +52,6 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
previewProps,
loadedEnvFiles,
i18n,
reactRoot,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.slice(1)) : this.query

Expand Down Expand Up @@ -187,7 +185,6 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
canonicalBase: "${canonicalBase}",
generateEtags: ${generateEtags || 'false'},
poweredByHeader: ${poweredByHeader || 'false'},
reactRoot: ${reactRoot || 'false'},

runtimeConfig,
buildManifest,
Expand Down
1 change: 0 additions & 1 deletion packages/next/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,6 @@ export default async function exportApp(
optimizeCss: nextConfig.experimental.optimizeCss,
nextScriptWorkers: nextConfig.experimental.nextScriptWorkers,
optimizeFonts: nextConfig.optimizeFonts,
reactRoot: nextConfig.experimental.reactRoot || false,
largePageDataBytes: nextConfig.experimental.largePageDataBytes,
}

Expand Down
1 change: 0 additions & 1 deletion packages/next/server/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export type RenderOptsPartial = {
supportsDynamicHTML?: boolean
runtime?: 'nodejs' | 'edge'
serverComponents?: boolean
reactRoot: boolean
}

export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
Expand Down
4 changes: 1 addition & 3 deletions packages/next/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ export default abstract class Server<ServerOptions extends Options = Options> {
serverComponentManifest?: any
renderServerComponentData?: boolean
serverComponentProps?: any
reactRoot: boolean
largePageDataBytes?: number
}
protected serverOptions: ServerOptions
Expand Down Expand Up @@ -328,7 +327,6 @@ export default abstract class Server<ServerOptions extends Options = Options> {
crossOrigin: this.nextConfig.crossOrigin
? this.nextConfig.crossOrigin
: undefined,
reactRoot: this.nextConfig.experimental.reactRoot === true,
largePageDataBytes: this.nextConfig.experimental.largePageDataBytes,
}

Expand Down Expand Up @@ -1292,7 +1290,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
typeof components.Document?.getInitialProps !== 'function' ||
// When concurrent features is enabled, the built-in `Document`
// component also supports dynamic HTML.
(this.renderOpts.reactRoot &&
(!!process.env.__NEXT_REACT_ROOT &&
NEXT_BUILTIN_DOCUMENT in components.Document)

// Disable dynamic HTML in cases that we know it won't be generated,
Expand Down
2 changes: 0 additions & 2 deletions packages/next/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ export interface ExperimentalConfig {
validator?: string
skipValidation?: boolean
}
reactRoot?: boolean
disableOptimizedLoading?: boolean
gzipSize?: boolean
craCompat?: boolean
Expand Down Expand Up @@ -511,7 +510,6 @@ export const defaultConfig: NextConfig = {
nextScriptWorkers: false,
scrollRestoration: false,
externalDir: false,
reactRoot: Number(process.env.NEXT_PRIVATE_REACT_ROOT) > 0,
disableOptimizedLoading: false,
gzipSize: true,
swcFileReading: true,
Expand Down
20 changes: 0 additions & 20 deletions packages/next/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,6 @@ function assignDefaults(userConfig: { [key: string]: any }) {
delete userConfig.exportTrailingSlash
}

if (typeof userConfig.experimental?.reactMode !== 'undefined') {
console.warn(
chalk.yellow.bold('Warning: ') +
`The experimental "reactMode" option has been replaced with "reactRoot". Please update your ${configFileName}.`
)
if (typeof userConfig.experimental?.reactRoot === 'undefined') {
userConfig.experimental.reactRoot = ['concurrent', 'blocking'].includes(
userConfig.experimental.reactMode
)
}
delete userConfig.experimental.reactMode
}

const config = Object.keys(userConfig).reduce<{ [key: string]: any }>(
(currentConfig, key) => {
const value = userConfig[key]
Expand Down Expand Up @@ -233,13 +220,6 @@ function assignDefaults(userConfig: { [key: string]: any }) {
}
}

const hasReactRoot = process.env.__NEXT_REACT_ROOT
if (hasReactRoot) {
// users might not have the `experimental` key in their config
result.experimental = result.experimental || {}
result.experimental.reactRoot = true
}

if (result?.images) {
const images: ImageConfig = result.images

Expand Down
3 changes: 1 addition & 2 deletions packages/next/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import type {
import fs from 'fs'
import { join, relative, resolve, sep } from 'path'
import { IncomingMessage, ServerResponse } from 'http'
import React from 'react'
import { addRequestMeta, getRequestMeta } from './request-meta'

import {
Expand Down Expand Up @@ -88,8 +87,8 @@ import {
} from './body-streams'
import { checkIsManualRevalidate } from './api-utils'
import { isDynamicRoute } from '../shared/lib/router/utils'
import { shouldUseReactRoot } from './utils'

const shouldUseReactRoot = parseInt(React.version) >= 18
if (shouldUseReactRoot) {
;(process.env as any).__NEXT_REACT_ROOT = 'true'
}
Expand Down
3 changes: 1 addition & 2 deletions packages/next/server/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type { NodeRequestHandler } from './next-server'
import type { UrlWithParsedQuery } from 'url'

import './node-polyfill-fetch'
import React from 'react'
import { default as Server } from './next-server'
import * as log from '../build/output/log'
import loadConfig from './config'
Expand All @@ -13,6 +12,7 @@ import { PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants'
import { PHASE_PRODUCTION_SERVER } from '../shared/lib/constants'
import { IncomingMessage, ServerResponse } from 'http'
import { NextUrlWithParsedQuery } from './request-meta'
import { shouldUseReactRoot } from './utils'

let ServerImpl: typeof Server

Expand Down Expand Up @@ -183,7 +183,6 @@ function createServer(options: NextServerOptions): NextServer {
)
}

const shouldUseReactRoot = parseInt(React.version) >= 18
if (shouldUseReactRoot) {
;(process.env as any).__NEXT_REACT_ROOT = 'true'
}
Expand Down
10 changes: 4 additions & 6 deletions packages/next/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,15 @@ import stripAnsi from 'next/dist/compiled/strip-ansi'
import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring'
import { postProcessHTML } from './post-process'
import { htmlEscapeJsonString } from './htmlescape'
import { stripInternalQueries } from './utils'
import { shouldUseReactRoot, stripInternalQueries } from './utils'

let tryGetPreviewData: typeof import('./api-utils/node').tryGetPreviewData
let warn: typeof import('../build/output/log').warn

const DOCTYPE = '<!DOCTYPE html>'
const ReactDOMServer =
parseInt(React.version) >= 18
? require('react-dom/server.browser')
: require('react-dom/server')
const ReactDOMServer = shouldUseReactRoot
? require('react-dom/server.browser')
: require('react-dom/server')

if (process.env.NEXT_RUNTIME !== 'edge') {
require('./node-polyfill-web-streams')
Expand Down Expand Up @@ -244,7 +243,6 @@ export type RenderOptsPartial = {
customServer?: boolean
crossOrigin?: string
images: ImageConfigComplete
reactRoot: boolean
largePageDataBytes?: number
}

Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextParsedUrlQuery } from './request-meta'
import React from 'react'
import { BLOCKED_PAGES } from '../shared/lib/constants'

export function isBlockedPage(pathname: string): boolean {
Expand Down Expand Up @@ -42,3 +43,6 @@ export function stripInternalQueries(query: NextParsedUrlQuery) {

return query
}

// When react version is >= 18 opt-in using reactRoot
export const shouldUseReactRoot = parseInt(React.version) >= 18
1 change: 0 additions & 1 deletion test/e2e/app-dir/app-rendering/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module.exports = {
experimental: {
appDir: true,
runtime: 'nodejs',
reactRoot: true,
serverComponents: true,
},
}
1 change: 0 additions & 1 deletion test/e2e/app-dir/app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module.exports = {
experimental: {
appDir: true,
runtime: 'nodejs',
reactRoot: true,
serverComponents: true,
},
}
13 changes: 3 additions & 10 deletions test/integration/react-18-invalid-config/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ function writeNextConfig(config, reactVersion = 17) {
}

describe('Invalid react 18 webpack config', () => {
it('should enable `experimental.reactRoot` when `experimental.runtime` is enabled', async () => {
it('should install react 18 when `experimental.runtime` is enabled', async () => {
writeNextConfig({
runtime: 'edge',
})
const { stderr } = await nextBuild(appDir, [], { stderr: true, nodeArgs })
nextConfig.restore()

expect(stderr).toContain(
'`experimental.runtime` requires `experimental.reactRoot` to be enabled along with React 18.'
'`experimental.runtime` requires React 18 to be installed.'
)
})
})
Expand All @@ -68,20 +68,13 @@ describe('React 17 with React 18 config', () => {
join(reactDomPackagePah, 'package.json'),
JSON.stringify({ name: 'react-dom', version: '17.0.0' })
)
writeNextConfig({ reactRoot: true })
writeNextConfig({})
})
afterAll(async () => {
await fs.remove(reactDomPackagePah)
nextConfig.restore()
})

it('should warn user when not using react 18 and `experimental.reactRoot` is enabled', async () => {
const { stderr } = await nextBuild(appDir, [], { stderr: true, nodeArgs })
expect(stderr).toContain(
'You have to use React 18 to use `experimental.reactRoot`.'
)
})

it('suspense is not allowed in blocking rendering mode', async () => {
const { stderr, code } = await nextBuild(appDir, [], {
stderr: true,
Expand Down
2 changes: 1 addition & 1 deletion test/integration/react-18-invalid-config/next.config.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = { experimental: { reactRoot: true } }
module.exports = {}
1 change: 0 additions & 1 deletion test/integration/react-18/app/next.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module.exports = {
// reactStrictMode: true,
experimental: {
reactRoot: true,
// runtime: 'edge',
},
images: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
module.exports = {
experimental: {
reactRoot: true,
serverComponents: true,
},
}
2 changes: 1 addition & 1 deletion test/unit/parse-page-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const fixtureDir = join(__dirname, 'fixtures')

function createNextConfig(runtime?: 'edge' | 'nodejs') {
return {
experimental: { reactRoot: true, runtime },
experimental: { runtime },
}
}

Expand Down