-
Notifications
You must be signed in to change notification settings - Fork 47.3k
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
Throw if React and React DOM versions don't match #29236
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
*/ | ||
|
||
import reactDOMPackageVersion from 'shared/ReactVersion'; | ||
import * as IsomorphicReactPackage from 'react'; | ||
|
||
export function ensureCorrectIsomorphicReactVersion() { | ||
const isomorphicReactPackageVersion = IsomorphicReactPackage.version; | ||
if (isomorphicReactPackageVersion !== reactDOMPackageVersion) { | ||
throw new Error( | ||
'Incompatible React versions: The "react" and "react-dom" packages must ' + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a perfect world we'd also detect environment mismatches e.g. importing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add another internal field (or check for the presence of a server-only API, and vice versa), but I'm less concerned about that one since it's only the tip of the iceberg of what you have to do to configure a Server Components set-up correctly. For a similar reason I didn't add a check to React Native because nobody really imports the React Native renderer directly; it's configured by some sort of framework. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we actually add it for React Native too? Too often the RN sync are using the wrong versions together, which caused issues when landing breaking changes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like, RN installs the renderer, but the template still allows using your own version of |
||
'have the exact same version. Instead got:\n' + | ||
` - react: ${isomorphicReactPackageVersion}\n` + | ||
` - react-dom: ${reactDOMPackageVersion}\n` + | ||
'Learn more: https://react.dev/warnings/version-mismatch', | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @emails react-core | ||
*/ | ||
|
||
'use strict'; | ||
|
||
describe('ReactMismatchedVersions-test', () => { | ||
// Polyfills for test environment | ||
global.ReadableStream = | ||
require('web-streams-polyfill/ponyfill/es6').ReadableStream; | ||
global.TextEncoder = require('util').TextEncoder; | ||
|
||
let React; | ||
let actualReactVersion; | ||
|
||
beforeEach(() => { | ||
jest.resetModules(); | ||
jest.mock('react', () => { | ||
const actualReact = jest.requireActual('react'); | ||
return { | ||
...actualReact, | ||
version: '18.0.0-whoa-this-aint-the-right-react', | ||
__actualVersion: actualReact.version, | ||
}; | ||
}); | ||
React = require('react'); | ||
actualReactVersion = React.__actualVersion; | ||
}); | ||
|
||
test('importing "react-dom/client" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/client')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// When running in source mode, we lazily require the implementation to | ||
// simulate the static config dependency injection we do at build time. So it | ||
// only errors once you call something and trigger the require. Running the | ||
// test in build mode is sufficient. | ||
// @gate !source | ||
test('importing "react-dom/server" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/server')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// @gate !source | ||
test('importing "react-dom/server.node" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/server.node')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// @gate !source | ||
test('importing "react-dom/server.browser" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/server.browser')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// @gate !source | ||
test('importing "react-dom/server.bun" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/server.bun')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// @gate !source | ||
test('importing "react-dom/server.edge" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/server.edge')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
test('importing "react-dom/static" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/static')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
test('importing "react-dom/static.node" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/static.node')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
test('importing "react-dom/static.browser" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/static.browser')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
test('importing "react-dom/static.edge" throws if version does not match React version', async () => { | ||
expect(() => require('react-dom/static.edge')).toThrow( | ||
'Incompatible React versions: The "react" and "react-dom" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-dom: ${actualReactVersion}`, | ||
); | ||
}); | ||
|
||
// @gate source | ||
test('importing "react-native-renderer" throws if version does not match React version', async () => { | ||
expect(() => require('react-native-renderer')).toThrow( | ||
'Incompatible React versions: The "react" and "react-native-renderer" packages ' + | ||
'must have the exact same version. Instead got:\n' + | ||
' - react: 18.0.0-whoa-this-aint-the-right-react\n' + | ||
` - react-native-renderer: ${actualReactVersion}`, | ||
); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we compile to CJS this doesn't matter much but it might be nice to
import {version} from "react"
so that it doesn't pull in every export and disables dead export elimination.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been doing it this way based on this comment. Not sure if it still applies:
react/packages/react-reconciler/src/Scheduler.js
Lines 10 to 15 in e8b016c