@samitouri / QOS-React / commits / ed3c65caf0

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.

Andrew Clark committed Apr 9, 2024 at 17:13 UTC ed3c65caf042f75fe2fdc2a5e568a9624c6175fb
3 files changed +58 -2
packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+8 -2
@@ -91,6 +91,9 @@ describe('ReactDeprecationWarnings', () => {
91 );
92 });
93
94 + // Disabling this until #28732 lands so we can assert on the warning message.
95 + // (It's already disabled in all but the Meta builds, anyway. Nbd.)
96 + // @gate TODO || !__DEV__
97 // @gate !disableStringRefs
98 it('should warn when owner and self are the same for string refs', async () => {
99 class RefComponent extends React.Component {
@@ -114,8 +117,11 @@ describe('ReactDeprecationWarnings', () => {
117 await waitForAll([]);
118 });
119
120 + // Disabling this until #28732 lands so we can assert on the warning message.
121 + // (It's already disabled in all but the Meta builds, anyway. Nbd.)
122 + // @gate TODO || !__DEV__
123 // @gate !disableStringRefs
118 - it('should warn when owner and self are different for string refs', async () => {
124 + it('should warn when owner and self are different for string refs (createElement)', async () => {
125 class RefComponent extends React.Component {
126 render() {
127 return null;
@@ -143,7 +149,7 @@ describe('ReactDeprecationWarnings', () => {
149
150 // @gate __DEV__
151 // @gate !disableStringRefs
146 - it('should warn when owner and self are different for string refs', async () => {
152 + it('should warn when owner and self are different for string refs (jsx)', async () => {
153 class RefComponent extends React.Component {
154 render() {
155 return null;
packages/react/src/__tests__/ReactCreateElement-test.js
+27
@@ -466,4 +466,31 @@ describe('ReactCreateElement', () => {
466 });
467 expect(test.props.value).toBeNaN();
468 });
469 +
470 + it('warns if outdated JSX transform is detected', async () => {
471 + // Warns if __self is detected, because that's only passed by a compiler
472 + expect(() => {
473 + React.createElement('div', {className: 'foo', __self: this});
474 + }).toWarnDev(
475 + 'Your app (or one of its dependencies) is using an outdated ' +
476 + 'JSX transform.',
477 + {
478 + withoutStack: true,
479 + },
480 + );
481 +
482 + // Only warns the first time. Subsequent elements don't warn.
483 + React.createElement('div', {className: 'foo', __self: this});
484 + });
485 +
486 + it('do not warn about outdated JSX transform if `key` is present', () => {
487 + // When a static "key" prop is defined _after_ a spread, the modern JSX
488 + // transform outputs `createElement` instead of `jsx`. (This is because with
489 + // `jsx`, a spread key always takes precedence over a static key, regardless
490 + // of the order, whereas `createElement` respects the order.)
491 + //
492 + // To avoid a false positive warning, we skip the warning whenever a `key`
493 + // prop is present.
494 + React.createElement('div', {key: 'foo', __self: this});
495 + });
496 });
packages/react/src/jsx/ReactJSXElement.js
+23
@@ -33,6 +33,7 @@ let specialPropKeyWarningShown;
33 let specialPropRefWarningShown;
34 let didWarnAboutStringRefs;
35 let didWarnAboutElementRef;
36 +let didWarnAboutOldJSXRuntime;
37
38 if (__DEV__) {
39 didWarnAboutStringRefs = {};
@@ -722,6 +723,28 @@ export function createElement(type, config, children) {
723 let ref = null;
724
725 if (config != null) {
726 + if (__DEV__) {
727 + if (
728 + !didWarnAboutOldJSXRuntime &&
729 + '__self' in config &&
730 + // Do not assume this is the result of an oudated JSX transform if key
731 + // is present, because the modern JSX transform sometimes outputs
732 + // createElement to preserve precedence between a static key and a
733 + // spread key. To avoid false positive warnings, we never warn if
734 + // there's a key.
735 + !('key' in config)
736 + ) {
737 + didWarnAboutOldJSXRuntime = true;
738 + console.warn(
739 + 'Your app (or one of its dependencies) is using an outdated JSX ' +
740 + 'transform. Update to the modern JSX transform for ' +
741 + 'faster performance: ' +
742 + // TODO: Create a short link for this
743 + 'https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html',
744 + );
745 + }
746 + }
747 +
748 if (hasValidRef(config)) {
749 if (!enableRefAsProp) {
750 ref = config.ref;