未验证 提交 882288b5 编写于 作者: J Joe Haddad 提交者: GitHub

Warn when Fast Refresh is disabled (React <16.10) (#15931)

上级 2d42b8e0
# Minimum React Version
#### Why This Error Occurred
Your project is using an old version of `react` or `react-dom` that does not
meet the suggested minimum version requirement.
Next.js suggests using, at a minimum, `react@16.10.0` and `react-dom@16.10.0`.
Older versions of `react` and `react-dom` do work with Next.js, however, they do
not enable all of Next.js' features.
For example, the following features are not enabled with old React versions:
- [Fast Refresh](https://nextjs.org/docs/basic-features/fast-refresh): instantly
view edits to your app without losing component state
- Component stack trace in development: see the component tree that lead up to
an error
- Hydration mismatch warnings: trace down discrepancies in your React tree that
cause performance problems
This list is not exhaustive, but illustrative in the value of upgrading React!
#### Possible Ways to Fix It
**Via npm**
```bash
npm upgrade react@latest react-dom@latest
```
**Via Yarn**
```bash
yarn add react@latest react-dom@latest
```
**Manually** Open your `package.json` and upgrade `react` and `react-dom`:
```json
{
"dependencies": {
"react": "^16.10.0",
"react-dom": "^16.10.0"
}
}
```
### Useful Links
- [Fast Refresh blog post](https://nextjs.org/blog/next-9-4#fast-refresh)
- [Fast Refresh docs](https://nextjs.org/docs/basic-features/fast-refresh)
......@@ -290,7 +290,7 @@ export default async function getBaseWebpackConfig(
} as ClientEntries)
: undefined
let typeScriptPath
let typeScriptPath: string | undefined
try {
typeScriptPath = resolveRequest('typescript', `${dir}/`)
} catch (_) {}
......@@ -302,7 +302,7 @@ export default async function getBaseWebpackConfig(
let jsConfig
// jsconfig is a subset of tsconfig
if (useTypeScript) {
const ts = (await import(typeScriptPath)) as typeof import('typescript')
const ts = (await import(typeScriptPath!)) as typeof import('typescript')
const tsConfig = await getTypeScriptConfiguration(ts, tsConfigPath)
jsConfig = { compilerOptions: tsConfig.options }
}
......
......@@ -4,6 +4,7 @@ import arg from 'next/dist/compiled/arg/index.js'
import { existsSync } from 'fs'
import startServer from '../server/lib/start-server'
import { printAndExit } from '../server/lib/utils'
import * as Log from '../build/output/log'
import { startedDevelopmentServer } from '../build/output'
import { cliCommand } from '../bin/next'
......@@ -56,6 +57,35 @@ const nextDev: cliCommand = (argv) => {
printAndExit(`> No such directory exists as the project root: ${dir}`)
}
async function preflight() {
const { getPackageVersion } = await import('../lib/get-package-version')
const semver = await import('next/dist/compiled/semver').then(
(res) => res.default
)
const reactVersion: string | null = await getPackageVersion({
cwd: dir,
name: 'react',
})
if (reactVersion && semver.lt(reactVersion, '16.10.0')) {
Log.warn(
'Fast Refresh is disabled in your application due to an outdated `react` version. Please upgrade 16.10 or newer!' +
' Read more: https://err.sh/next.js/react-version'
)
} else {
const reactDomVersion: string | null = await getPackageVersion({
cwd: dir,
name: 'react-dom',
})
if (reactDomVersion && semver.lt(reactDomVersion, '16.10.0')) {
Log.warn(
'Fast Refresh is disabled in your application due to an outdated `react-dom` version. Please upgrade 16.10 or newer!' +
' Read more: https://err.sh/next.js/react-version'
)
}
}
}
const port = args['--port'] || 3000
const appUrl = `http://${args['--hostname'] || 'localhost'}:${port}`
......@@ -66,6 +96,9 @@ const nextDev: cliCommand = (argv) => {
)
.then(async (app) => {
startedDevelopmentServer(appUrl)
// Start preflight after server is listening and ignore errors:
preflight().catch(() => {})
// Finalize server bootup:
await app.prepare()
})
.catch((err) => {
......
因为 它太大了无法显示 source diff 。你可以改为 查看blob
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
此差异已折叠。
{"name":"semver","main":"index.js","license":"ISC"}
// issuer.endsWith(path.posix.sep) || issuer.endsWith(path.win32.sep)
import findUp from 'next/dist/compiled/find-up'
import * as path from 'path'
import { promises as fs } from 'fs'
import JSON5 from 'next/dist/compiled/json5'
import { resolveRequest } from './resolve-request'
export async function getPackageVersion({
cwd,
name,
}: {
cwd: string
name: string
}): Promise<string | null> {
const configurationPath: string | undefined = await findUp('package.json', {
cwd,
})
if (!configurationPath) {
return null
}
const content = await fs.readFile(configurationPath, 'utf-8')
const packageJson: any = JSON5.parse(content)
const { dependencies = {}, devDependencies = {} } = packageJson || {}
if (!(dependencies[name] || devDependencies[name])) {
return null
}
const cwd2 =
cwd.endsWith(path.posix.sep) || cwd.endsWith(path.win32.sep)
? cwd
: `${cwd}/`
try {
const targetPath = resolveRequest(`${name}/package.json`, cwd2)
const targetContent = await fs.readFile(targetPath, 'utf-8')
return JSON5.parse(targetContent).version ?? null
} catch {
return null
}
}
import resolve from 'next/dist/compiled/resolve/index.js'
import path from 'path'
export function resolveRequest(req: string, issuer: string) {
export function resolveRequest(req: string, issuer: string): string {
// The `resolve` package is prebuilt through ncc, which prevents
// PnP from being able to inject itself into it. To circumvent
// this, we simply use PnP directly when available.
......
......@@ -151,6 +151,7 @@
"@types/react-dom": "16.9.4",
"@types/react-is": "16.7.1",
"@types/resolve": "0.0.8",
"@types/semver": "7.3.1",
"@types/send": "0.14.4",
"@types/styled-jsx": "2.2.8",
"@types/text-table": "0.2.1",
......@@ -200,6 +201,7 @@
"raw-body": "2.4.1",
"recast": "0.18.5",
"resolve": "1.11.0",
"semver": "7.3.2",
"send": "0.17.1",
"source-map": "0.6.1",
"string-hash": "1.1.3",
......
......@@ -488,6 +488,14 @@ export async function ncc_comment_json(task, opts) {
.target('compiled/comment-json')
}
externals['semver'] = 'next/dist/compiled/semver'
export async function ncc_semver(task, opts) {
await task
.source(opts.src || relative(__dirname, require.resolve('semver')))
.ncc({ packageName: 'semver', externals })
.target('compiled/semver')
}
externals['path-to-regexp'] = 'next/dist/compiled/path-to-regexp'
export async function path_to_regexp(task, opts) {
await task
......@@ -560,6 +568,7 @@ export async function ncc(task) {
'ncc_unistore',
'ncc_terser_webpack_plugin',
'ncc_comment_json',
'ncc_semver',
])
}
......
......@@ -190,6 +190,10 @@ declare module 'next/dist/compiled/terser' {
import m from 'terser'
export = m
}
declare module 'next/dist/compiled/semver' {
import m from 'semver'
export = m
}
declare module 'next/dist/compiled/text-table' {
function textTable(
rows: Array<Array<{}>>,
......
{
"dependencies": {
"react": "*",
"react-dom": "*"
}
}
export default function Home() {
return <p>Hello</p>
}
{
"dependencies": {
"react": "*",
"react-dom": "*"
}
}
export default function Home() {
return <p>Hello</p>
}
/* eslint-env jest */
import {
runNextCommand,
runNextCommandDev,
findPort,
killApp,
launchApp,
runNextCommand,
runNextCommandDev,
} from 'next-test-utils'
import { join } from 'path'
import pkg from 'next/package'
......@@ -13,6 +14,8 @@ import http from 'http'
jest.setTimeout(1000 * 60 * 5)
const dir = join(__dirname, '..')
const dirOldReact = join(__dirname, '../old-react')
const dirOldReactDom = join(__dirname, '../old-react-dom')
describe('CLI Usage', () => {
describe('no command', () => {
......@@ -216,6 +219,44 @@ describe('CLI Usage', () => {
})
expect(stderr).not.toContain('UnhandledPromiseRejectionWarning')
})
test('too old of react version', async () => {
const port = await findPort()
let stderr = ''
let instance = await launchApp(dirOldReact, port, {
stderr: true,
onStderr(msg) {
stderr += msg
},
})
expect(stderr).toMatch(
'Fast Refresh is disabled in your application due to an outdated `react` version'
)
expect(stderr).not.toMatch(`react-dom`)
await killApp(instance)
})
test('too old of react-dom version', async () => {
const port = await findPort()
let stderr = ''
let instance = await launchApp(dirOldReactDom, port, {
stderr: true,
onStderr(msg) {
stderr += msg
},
})
expect(stderr).toMatch(
'Fast Refresh is disabled in your application due to an outdated `react-dom` version'
)
expect(stderr).not.toMatch('`react`')
await killApp(instance)
})
})
describe('export', () => {
......
......@@ -2903,6 +2903,13 @@
"@types/glob" "*"
"@types/node" "*"
"@types/semver@7.3.1":
version "7.3.1"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.1.tgz#7a9a5d595b6d873f338c867dcef64df289468cfa"
integrity sha512-ooD/FJ8EuwlDKOI6D9HWxgIgJjMg2cuziXm/42npDC8y4NjxplBUn9loewZiBNCt44450lHAU0OSb51/UqXeag==
dependencies:
"@types/node" "*"
"@types/send@0.14.4":
version "0.14.4"
resolved "https://registry.yarnpkg.com/@types/send/-/send-0.14.4.tgz#d70458b030305999db619a7b057f7105058bd0ff"
......@@ -14237,15 +14244,15 @@ semver@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
semver@7.3.2, semver@^7.3.2:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
semver@^6.0.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.2:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
semver@~5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册