Update to handle correct react version with app (#41658)

Add auto install handling for the correct react experimental version for
app
This commit is contained in:
JJ Kasper 2022-10-22 13:29:00 -07:00 committed by GitHub
parent 78138af505
commit 4ce259fa3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 151 additions and 7 deletions

View file

@ -120,6 +120,7 @@ import { RemotePattern } from '../shared/lib/image-config'
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
import { verifyAppReactVersion } from '../lib/verifyAppReactVersion'
export type SsgRoute = {
initialRevalidateSeconds: number | false
@ -307,6 +308,9 @@ export default async function build(
}
const { pagesDir, appDir } = findPagesDir(dir, isAppDirEnabled)
if (isAppDirEnabled) {
await verifyAppReactVersion({ dir })
}
const hasPublicDir = await fileExists(publicDir)
telemetry.record(

View file

@ -0,0 +1,122 @@
import chalk from 'next/dist/compiled/chalk'
import {
hasNecessaryDependencies,
MissingDependency,
NecessaryDependencies,
} from './has-necessary-dependencies'
import { installDependencies } from './install-dependencies'
import { isCI } from '../telemetry/ci-info'
import { FatalError } from './fatal-error'
import { getPkgManager } from './helpers/get-pkg-manager'
import { getOxfordCommaList } from './oxford-comma-list'
const requiredReactVersion = process.env.REQUIRED_APP_REACT_VERSION || ''
const removalMsg =
'\n\n' +
chalk.bold(
'If you are not trying to use the `app` directory, please disable the ' +
chalk.cyan('experimental.appDir') +
' config in your `next.config.js`.'
)
const requiredPackages = [
{
file: 'react/index.js',
pkg: 'react',
exportsRestrict: true,
},
{
file: 'react-dom/index.js',
pkg: 'react-dom',
exportsRestrict: true,
},
]
async function missingDepsError(
dir: string,
missingPackages: MissingDependency[]
) {
const packagesHuman = getOxfordCommaList(missingPackages.map((p) => p.pkg))
const packagesCli = missingPackages
.map((p) => `${p.pkg}@${requiredReactVersion}`)
.join(' ')
const packageManager = getPkgManager(dir)
throw new FatalError(
chalk.bold.red(
`It looks like you're trying to use the \`app\` directory but do not have the required react version installed.`
) +
'\n\n' +
chalk.bold(`Please install ${chalk.bold(packagesHuman)} by running:`) +
'\n\n' +
`\t${chalk.bold.cyan(
(packageManager === 'yarn'
? 'yarn add --dev'
: packageManager === 'pnpm'
? 'pnpm install --save-dev'
: 'npm install --save-dev') +
' ' +
packagesCli
)}` +
removalMsg +
'\n'
)
}
export async function verifyAppReactVersion({
dir,
}: {
dir: string
}): Promise<void> {
if (process.env.NEXT_SKIP_APP_REACT_INSTALL) {
return
}
// Ensure TypeScript and necessary `@types/*` are installed:
let deps: NecessaryDependencies = await hasNecessaryDependencies(
dir,
requiredPackages
)
const resolvedReact = deps.resolved.get('react')
const installedVersion =
resolvedReact &&
require(deps.resolved.get('react') || '')
.version?.split('-experimental')
.pop()
if (
deps.missing?.length ||
installedVersion !== requiredReactVersion.split('-experimental').pop()
) {
const neededDeps = requiredPackages.map((dep) => {
dep.pkg = `${dep.pkg}@${requiredReactVersion}`
return dep
})
if (isCI) {
// we don't attempt auto install in CI to avoid side-effects
// and instead log the error for installing needed packages
await missingDepsError(dir, neededDeps)
}
console.log(
chalk.bold.yellow(
`It looks like you're trying to use \`app\` directory but do not have the required react version installed.`
) +
'\n' +
removalMsg +
'\n'
)
await installDependencies(dir, neededDeps, true).catch((err) => {
if (err && typeof err === 'object' && 'command' in err) {
console.error(
`\nFailed to install required react versions, please install them manually to continue:\n` +
(err as any).command +
'\n'
)
}
process.exit(1)
})
}
}

View file

@ -76,6 +76,7 @@ import {
} from '../../build/utils'
import { getDefineEnv } from '../../build/webpack-config'
import loadJsConfig from '../../build/load-jsconfig'
import { verifyAppReactVersion } from '../../lib/verifyAppReactVersion'
// Load ReactDevOverlay only when needed
let ReactDevOverlayImpl: FunctionComponent
@ -658,6 +659,10 @@ export default class DevServer extends Server {
setGlobal('distDir', this.distDir)
setGlobal('phase', PHASE_DEVELOPMENT_SERVER)
if (this.hasAppDir) {
await verifyAppReactVersion({ dir: this.dir })
}
await this.verifyTypeScript()
this.customRoutes = await loadCustomRoutes(this.nextConfig)

View file

@ -156,8 +156,17 @@ if ((typeof exports.default === 'function' || (typeof exports.default === 'objec
}
function setNextVersion(code) {
return code.replace(
/process\.env\.__NEXT_VERSION/g,
`"${require('./package.json').version}"`
)
return code
.replace(
/process\.env\.__NEXT_VERSION/g,
`"${require('./package.json').version}"`
)
.replace(
/process\.env\.REQUIRED_APP_REACT_VERSION/,
`"${
require('../../package.json').devDependencies[
'react-server-dom-webpack'
]
}"`
)
}

View file

@ -4,7 +4,7 @@
"license": "MIT",
"dependencies": {
"next": "latest",
"react": "0.0.0-experimental-cb5084d1c-20220924",
"react-dom": "0.0.0-experimental-cb5084d1c-20220924"
"react": "0.0.0-experimental-9cdf8a99e-20221018",
"react-dom": "0.0.0-experimental-9cdf8a99e-20221018"
}
}

View file

@ -8,7 +8,11 @@ const appDir = path.join(__dirname, '..')
describe('conflict between app file and page file', () => {
it('errors during build', async () => {
const conflicts = ['/hello', '/another']
const results = await nextBuild(appDir, [], { stdout: true, stderr: true })
const results = await nextBuild(appDir, [], {
stdout: true,
stderr: true,
env: { NEXT_SKIP_APP_REACT_INSTALL: '1' },
})
const output = results.stdout + results.stderr
expect(output).toMatch(/Conflicting app and page files were found/)