From ed3c65caf042f75fe2fdc2a5e568a9624c6175fb Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 9 Apr 2024 17:13:19 -0400 Subject: [PATCH] Warn if outdated JSX transform is detected (#28781) We want to warn if we detect that an app is using an outdated JSX transform. We can't just warn if `createElement` is called because we still support `createElement` when it's called manually. We only want to warn if `createElement` is output by the compiler. The heuristic is to check for a `__self` prop, which is an optional, internal prop that older transforms used to pass to `createElement` for better debugging in development mode. If `__self` is present, we `console.warn` once with advice to upgrade to the modern JSX transform. Subsequent elements will not warn. There's a special case we have to account for: when a static "key" prop is defined _after_ a spread, the modern JSX transform outputs `createElement` instead of `jsx`. (This is because with `jsx`, a spread key always takes precedence over a static key, regardless of the order, whereas `createElement` respects the order.) To avoid a false positive warning, we skip the warning whenever a `key` prop is present. --- .../ReactDeprecationWarnings-test.js | 10 +++++-- .../src/__tests__/ReactCreateElement-test.js | 27 +++++++++++++++++++ packages/react/src/jsx/ReactJSXElement.js | 23 ++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js index 009ae14fccaa5..702b3cf5ef5fe 100644 --- a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js +++ b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js @@ -91,6 +91,9 @@ describe('ReactDeprecationWarnings', () => { ); }); + // Disabling this until #28732 lands so we can assert on the warning message. + // (It's already disabled in all but the Meta builds, anyway. Nbd.) + // @gate TODO || !__DEV__ // @gate !disableStringRefs it('should warn when owner and self are the same for string refs', async () => { class RefComponent extends React.Component { @@ -114,8 +117,11 @@ describe('ReactDeprecationWarnings', () => { await waitForAll([]); }); + // Disabling this until #28732 lands so we can assert on the warning message. + // (It's already disabled in all but the Meta builds, anyway. Nbd.) + // @gate TODO || !__DEV__ // @gate !disableStringRefs - it('should warn when owner and self are different for string refs', async () => { + it('should warn when owner and self are different for string refs (createElement)', async () => { class RefComponent extends React.Component { render() { return null; @@ -143,7 +149,7 @@ describe('ReactDeprecationWarnings', () => { // @gate __DEV__ // @gate !disableStringRefs - it('should warn when owner and self are different for string refs', async () => { + it('should warn when owner and self are different for string refs (jsx)', async () => { class RefComponent extends React.Component { render() { return null; diff --git a/packages/react/src/__tests__/ReactCreateElement-test.js b/packages/react/src/__tests__/ReactCreateElement-test.js index 380f2cd4cf45a..51012166af260 100644 --- a/packages/react/src/__tests__/ReactCreateElement-test.js +++ b/packages/react/src/__tests__/ReactCreateElement-test.js @@ -466,4 +466,31 @@ describe('ReactCreateElement', () => { }); expect(test.props.value).toBeNaN(); }); + + it('warns if outdated JSX transform is detected', async () => { + // Warns if __self is detected, because that's only passed by a compiler + expect(() => { + React.createElement('div', {className: 'foo', __self: this}); + }).toWarnDev( + 'Your app (or one of its dependencies) is using an outdated ' + + 'JSX transform.', + { + withoutStack: true, + }, + ); + + // Only warns the first time. Subsequent elements don't warn. + React.createElement('div', {className: 'foo', __self: this}); + }); + + it('do not warn about outdated JSX transform if `key` is present', () => { + // When a static "key" prop is defined _after_ a spread, the modern JSX + // transform outputs `createElement` instead of `jsx`. (This is because with + // `jsx`, a spread key always takes precedence over a static key, regardless + // of the order, whereas `createElement` respects the order.) + // + // To avoid a false positive warning, we skip the warning whenever a `key` + // prop is present. + React.createElement('div', {key: 'foo', __self: this}); + }); }); diff --git a/packages/react/src/jsx/ReactJSXElement.js b/packages/react/src/jsx/ReactJSXElement.js index d7b70e0f39784..2b9955fcc536a 100644 --- a/packages/react/src/jsx/ReactJSXElement.js +++ b/packages/react/src/jsx/ReactJSXElement.js @@ -33,6 +33,7 @@ let specialPropKeyWarningShown; let specialPropRefWarningShown; let didWarnAboutStringRefs; let didWarnAboutElementRef; +let didWarnAboutOldJSXRuntime; if (__DEV__) { didWarnAboutStringRefs = {}; @@ -722,6 +723,28 @@ export function createElement(type, config, children) { let ref = null; if (config != null) { + if (__DEV__) { + if ( + !didWarnAboutOldJSXRuntime && + '__self' in config && + // Do not assume this is the result of an oudated JSX transform if key + // is present, because the modern JSX transform sometimes outputs + // createElement to preserve precedence between a static key and a + // spread key. To avoid false positive warnings, we never warn if + // there's a key. + !('key' in config) + ) { + didWarnAboutOldJSXRuntime = true; + console.warn( + 'Your app (or one of its dependencies) is using an outdated JSX ' + + 'transform. Update to the modern JSX transform for ' + + 'faster performance: ' + + // TODO: Create a short link for this + 'https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html', + ); + } + } + if (hasValidRef(config)) { if (!enableRefAsProp) { ref = config.ref;