@samitouri / QOS-React-2 / commits / 90f8fe6f55

add jsx-runtime and jsx-dev-runtime (#18299)

This PR adds the jsx-runtime and jsx-dev-runtime modules for the JSX Babel Plugin. WWW still relies on jsx/jsxs/jsxDEV from the "react" module, so once we refactor the code to point to the runtime modules we will remove jsx/jsxs/jsxDEV from the "react" module.

Luna Ruan committed Mar 17, 2020 at 13:22 UTC 90f8fe6f5509cab7d6d280b4ed17181697f394e9
18 files changed +927 -88
packages/react/index.classic.fb.js
+1 -4
@@ -50,8 +50,5 @@ export {
50 DEPRECATED_createResponder,
51 // enableScopeAPI
52 unstable_createScope,
53 - // enableJSXTransformAPI
54 - jsx,
55 - jsxs,
56 - jsxDEV,
53 } from './src/React';
54 +export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';
packages/react/index.experimental.js
-4
@@ -45,8 +45,4 @@ export {
45 unstable_withSuspenseConfig,
46 // enableBlocksAPI
47 block,
48 - // enableJSXTransformAPI
49 - jsx,
50 - jsxs,
51 - jsxDEV,
48 } from './src/React';
packages/react/index.js
-3
@@ -76,7 +76,4 @@ export {
76 DEPRECATED_createResponder,
77 unstable_createFundamental,
78 unstable_createScope,
79 - jsx,
80 - jsxs,
81 - jsxDEV,
79 } from './src/React';
packages/react/index.modern.fb.js
+1 -4
@@ -49,8 +49,5 @@ export {
49 DEPRECATED_createResponder,
50 // enableScopeAPI
51 unstable_createScope,
52 - // enableJSXTransformAPI
53 - jsx,
54 - jsxs,
55 - jsxDEV,
52 } from './src/React';
53 +export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';
packages/react/jsx-dev-runtime.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export {Fragment, jsxDEV} from './src/jsx/ReactJSX';
packages/react/jsx-runtime.js new
+9
@@ -0,0 +1,9 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +export {Fragment, jsx, jsxs} from './src/jsx/ReactJSX';
packages/react/npm/jsx-dev-runtime.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-jsx-dev-runtime.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-jsx-dev-runtime.development.js');
7 +}
packages/react/npm/jsx-runtime.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/react-jsx-runtime.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/react-jsx-runtime.development.js');
7 +}
packages/react/package.json
+3 -1
@@ -14,7 +14,9 @@
14 "build-info.json",
15 "index.js",
16 "cjs/",
17 - "umd/"
17 + "umd/",
18 + "jsx-runtime.js",
19 + "jsx-dev-runtime.js"
20 ],
21 "main": "index.js",
22 "repository": {
packages/react/src/React.js
-15
@@ -24,7 +24,6 @@ import {
24 createFactory as createFactoryProd,
25 cloneElement as cloneElementProd,
26 isValidElement,
27 - jsx as jsxProd,
27 } from './ReactElement';
28 import {createContext} from './ReactContext';
29 import {lazy} from './ReactLazy';
@@ -52,9 +51,6 @@ import {
51 createElementWithValidation,
52 createFactoryWithValidation,
53 cloneElementWithValidation,
55 - jsxWithValidation,
56 - jsxWithValidationStatic,
57 - jsxWithValidationDynamic,
54 } from './ReactElementValidator';
55 import createMutableSource from './createMutableSource';
56 import ReactSharedInternals from './ReactSharedInternals';
@@ -67,12 +63,6 @@ const createElement = __DEV__ ? createElementWithValidation : createElementProd;
63 const cloneElement = __DEV__ ? cloneElementWithValidation : cloneElementProd;
64 const createFactory = __DEV__ ? createFactoryWithValidation : createFactoryProd;
65
70 -const jsxDEV = __DEV__ ? jsxWithValidation : undefined;
71 -const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd;
72 -// we may want to special case jsxs internally to take advantage of static children.
73 -// for now we can ship identical prod functions
74 -const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd;
75 -
66 const Children = {
67 map,
68 forEach,
@@ -127,9 +117,4 @@ export {
117 createFundamental as unstable_createFundamental,
118 // enableScopeAPI
119 createScope as unstable_createScope,
130 - // enableJSXTransformAPI
131 - jsx,
132 - jsxs,
133 - // TODO: jsxDEV should not be exposed as a name. We might want to move it to a different entry point.
134 - jsxDEV,
120 };
packages/react/src/ReactDebugCurrentFrame.js
+7
@@ -23,6 +23,13 @@ export function setCurrentlyValidatingElement(element: null | ReactElement) {
23 }
24
25 if (__DEV__) {
26 + ReactDebugCurrentFrame.setCurrentlyValidatingElement = function(
27 + element: null | ReactElement,
28 + ) {
29 + if (__DEV__) {
30 + currentlyValidatingElement = element;
31 + }
32 + };
33 // Stack implementation injected by the current renderer.
34 ReactDebugCurrentFrame.getCurrentStack = (null: null | (() => string));
35
packages/react/src/__tests__/ReactElementJSX-test.js
+74 -56
@@ -12,6 +12,8 @@
12 let React;
13 let ReactDOM;
14 let ReactTestUtils;
15 +let JSXRuntime;
16 +let JSXDEVRuntime;
17
18 // NOTE: We're explicitly not using JSX here. This is intended to test
19 // a new React.jsx api which does not have a JSX transformer yet.
@@ -29,6 +31,8 @@ describe('ReactElement.jsx', () => {
31 global.Symbol = undefined;
32
33 React = require('react');
34 + JSXRuntime = require('react/jsx-runtime');
35 + JSXDEVRuntime = require('react/jsx-dev-runtime');
36 ReactDOM = require('react-dom');
37 ReactTestUtils = require('react-dom/test-utils');
38 });
@@ -37,32 +41,35 @@ describe('ReactElement.jsx', () => {
41 global.Symbol = originalSymbol;
42 });
43
40 - if (!__EXPERIMENTAL__) {
41 - it("empty test so Jest doesn't complain", () => {});
42 - return;
43 - }
44 -
44 it('allows static methods to be called using the type property', () => {
45 class StaticMethodComponentClass extends React.Component {
46 render() {
48 - return React.jsx('div', {});
47 + return JSXRuntime.jsx('div', {});
48 }
49 }
50 StaticMethodComponentClass.someStaticMethod = () => 'someReturnValue';
51
53 - const element = React.jsx(StaticMethodComponentClass, {});
52 + const element = JSXRuntime.jsx(StaticMethodComponentClass, {});
53 expect(element.type.someStaticMethod()).toBe('someReturnValue');
54 });
55
56 it('identifies valid elements', () => {
57 class Component extends React.Component {
58 render() {
60 - return React.jsx('div', {});
59 + return JSXRuntime.jsx('div', {});
60 }
61 }
62
64 - expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
65 - expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
63 + expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
64 + expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
65 + expect(
66 + React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
67 + ).toEqual(true);
68 + if (__DEV__) {
69 + expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
70 + true,
71 + );
72 + }
73
74 expect(React.isValidElement(null)).toEqual(false);
75 expect(React.isValidElement(true)).toEqual(false);
@@ -83,12 +90,12 @@ describe('ReactElement.jsx', () => {
90 expect(React.isValidElement(Component)).toEqual(false);
91 expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
92
86 - const jsonElement = JSON.stringify(React.jsx('div', {}));
93 + const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
94 expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
95 });
96
97 it('is indistinguishable from a plain object', () => {
91 - const element = React.jsx('div', {className: 'foo'});
98 + const element = JSXRuntime.jsx('div', {className: 'foo'});
99 const object = {};
100 expect(element.constructor).toBe(object.constructor);
101 });
@@ -96,37 +103,37 @@ describe('ReactElement.jsx', () => {
103 it('should use default prop value when removing a prop', () => {
104 class Component extends React.Component {
105 render() {
99 - return React.jsx('span', {});
106 + return JSXRuntime.jsx('span', {});
107 }
108 }
109 Component.defaultProps = {fruit: 'persimmon'};
110
111 const container = document.createElement('div');
112 const instance = ReactDOM.render(
106 - React.jsx(Component, {fruit: 'mango'}),
113 + JSXRuntime.jsx(Component, {fruit: 'mango'}),
114 container,
115 );
116 expect(instance.props.fruit).toBe('mango');
117
111 - ReactDOM.render(React.jsx(Component, {}), container);
118 + ReactDOM.render(JSXRuntime.jsx(Component, {}), container);
119 expect(instance.props.fruit).toBe('persimmon');
120 });
121
122 it('should normalize props with default values', () => {
123 class Component extends React.Component {
124 render() {
118 - return React.jsx('span', {children: this.props.prop});
125 + return JSXRuntime.jsx('span', {children: this.props.prop});
126 }
127 }
128 Component.defaultProps = {prop: 'testKey'};
129
130 const instance = ReactTestUtils.renderIntoDocument(
124 - React.jsx(Component, {}),
131 + JSXRuntime.jsx(Component, {}),
132 );
133 expect(instance.props.prop).toBe('testKey');
134
135 const inst2 = ReactTestUtils.renderIntoDocument(
129 - React.jsx(Component, {prop: null}),
136 + JSXRuntime.jsx(Component, {prop: null}),
137 );
138 expect(inst2.props.prop).toBe(null);
139 });
@@ -134,7 +141,7 @@ describe('ReactElement.jsx', () => {
141 it('throws when changing a prop (in dev) after element creation', () => {
142 class Outer extends React.Component {
143 render() {
137 - const el = React.jsx('div', {className: 'moo'});
144 + const el = JSXRuntime.jsx('div', {className: 'moo'});
145
146 if (__DEV__) {
147 expect(function() {
@@ -150,7 +157,7 @@ describe('ReactElement.jsx', () => {
157 }
158 }
159 const outer = ReactTestUtils.renderIntoDocument(
153 - React.jsx(Outer, {color: 'orange'}),
160 + JSXRuntime.jsx(Outer, {color: 'orange'}),
161 );
162 if (__DEV__) {
163 expect(ReactDOM.findDOMNode(outer).className).toBe('moo');
@@ -163,7 +170,7 @@ describe('ReactElement.jsx', () => {
170 const container = document.createElement('div');
171 class Outer extends React.Component {
172 render() {
166 - const el = React.jsx('div', {children: this.props.sound});
173 + const el = JSXRuntime.jsx('div', {children: this.props.sound});
174
175 if (__DEV__) {
176 expect(function() {
@@ -179,7 +186,7 @@ describe('ReactElement.jsx', () => {
186 }
187 }
188 Outer.defaultProps = {sound: 'meow'};
182 - const outer = ReactDOM.render(React.jsx(Outer, {}), container);
189 + const outer = ReactDOM.render(JSXRuntime.jsx(Outer, {}), container);
190 expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow');
191 if (__DEV__) {
192 expect(ReactDOM.findDOMNode(outer).className).toBe('');
@@ -191,11 +198,11 @@ describe('ReactElement.jsx', () => {
198 it('does not warn for NaN props', () => {
199 class Test extends React.Component {
200 render() {
194 - return React.jsx('div', {});
201 + return JSXRuntime.jsx('div', {});
202 }
203 }
204 const test = ReactTestUtils.renderIntoDocument(
198 - React.jsx(Test, {value: +undefined}),
205 + JSXRuntime.jsx(Test, {value: +undefined}),
206 );
207 expect(test.props.value).toBeNaN();
208 });
@@ -204,21 +211,23 @@ describe('ReactElement.jsx', () => {
211 const container = document.createElement('div');
212 class Child extends React.Component {
213 render() {
207 - return React.jsx('div', {children: this.props.key});
214 + return JSXRuntime.jsx('div', {children: this.props.key});
215 }
216 }
217 class Parent extends React.Component {
218 render() {
212 - return React.jsxs('div', {
219 + return JSXRuntime.jsxs('div', {
220 children: [
214 - React.jsx(Child, {}, '0'),
215 - React.jsx(Child, {}, '1'),
216 - React.jsx(Child, {}, '2'),
221 + JSXRuntime.jsx(Child, {}, '0'),
222 + JSXRuntime.jsx(Child, {}, '1'),
223 + JSXRuntime.jsx(Child, {}, '2'),
224 ],
225 });
226 }
227 }
221 - expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
228 + expect(() =>
229 + ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
230 + ).toErrorDev(
231 'Child: `key` is not a prop. Trying to access it will result ' +
232 'in `undefined` being returned. If you need to access the same ' +
233 'value within the child component, you should pass it as a different ' +
@@ -229,7 +238,10 @@ describe('ReactElement.jsx', () => {
238 it('warns when a jsxs is passed something that is not an array', () => {
239 const container = document.createElement('div');
240 expect(() =>
232 - ReactDOM.render(React.jsxs('div', {children: 'foo'}, null), container),
241 + ReactDOM.render(
242 + JSXRuntime.jsxs('div', {children: 'foo'}, null),
243 + container,
244 + ),
245 ).toErrorDev(
246 'React.jsx: Static children should always be an array. ' +
247 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
@@ -239,7 +251,7 @@ describe('ReactElement.jsx', () => {
251 });
252
253 it('should warn when `key` is being accessed on a host element', () => {
242 - const element = React.jsxs('div', {}, '3');
254 + const element = JSXRuntime.jsxs('div', {}, '3');
255 expect(
256 () => void element.props.key,
257 ).toErrorDev(
@@ -255,17 +267,19 @@ describe('ReactElement.jsx', () => {
267 const container = document.createElement('div');
268 class Child extends React.Component {
269 render() {
258 - return React.jsx('div', {children: this.props.ref});
270 + return JSXRuntime.jsx('div', {children: this.props.ref});
271 }
272 }
273 class Parent extends React.Component {
274 render() {
263 - return React.jsx('div', {
264 - children: React.jsx(Child, {ref: 'childElement'}),
275 + return JSXRuntime.jsx('div', {
276 + children: JSXRuntime.jsx(Child, {ref: 'childElement'}),
277 });
278 }
279 }
268 - expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
280 + expect(() =>
281 + ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
282 + ).toErrorDev(
283 'Child: `ref` is not a prop. Trying to access it will result ' +
284 'in `undefined` being returned. If you need to access the same ' +
285 'value within the child component, you should pass it as a different ' +
@@ -292,15 +306,16 @@ describe('ReactElement.jsx', () => {
306 jest.resetModules();
307
308 React = require('react');
309 + JSXRuntime = require('react/jsx-runtime');
310
311 class Component extends React.Component {
312 render() {
298 - return React.jsx('div');
313 + return JSXRuntime.jsx('div');
314 }
315 }
316
302 - expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
303 - expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
317 + expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
318 + expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
319
320 expect(React.isValidElement(null)).toEqual(false);
321 expect(React.isValidElement(true)).toEqual(false);
@@ -321,29 +336,32 @@ describe('ReactElement.jsx', () => {
336 expect(React.isValidElement(Component)).toEqual(false);
337 expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
338
324 - const jsonElement = JSON.stringify(React.jsx('div', {}));
339 + const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
340 expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
341 });
342
343 it('should warn when unkeyed children are passed to jsx', () => {
344 const container = document.createElement('div');
345 +
346 class Child extends React.Component {
347 render() {
332 - return React.jsx('div', {});
348 + return JSXRuntime.jsx('div', {});
349 }
350 }
351 class Parent extends React.Component {
352 render() {
337 - return React.jsx('div', {
353 + return JSXRuntime.jsx('div', {
354 children: [
339 - React.jsx(Child, {}),
340 - React.jsx(Child, {}),
341 - React.jsx(Child, {}),
355 + JSXRuntime.jsx(Child, {}),
356 + JSXRuntime.jsx(Child, {}),
357 + JSXRuntime.jsx(Child, {}),
358 ],
359 });
360 }
361 }
346 - expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
362 + expect(() =>
363 + ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
364 + ).toErrorDev(
365 'Warning: Each child in a list should have a unique "key" prop.\n\n' +
366 'Check the render method of `Parent`. See https://fb.me/react-warning-keys for more information.\n' +
367 ' in Child (created by Parent)\n' +
@@ -356,18 +374,18 @@ describe('ReactElement.jsx', () => {
374 const container = document.createElement('div');
375 class Child extends React.Component {
376 render() {
359 - return React.jsx('div', {});
377 + return JSXRuntime.jsx('div', {});
378 }
379 }
380 class Parent extends React.Component {
381 render() {
364 - return React.jsx('div', {
365 - children: [React.jsx(Child, {key: '0'})],
382 + return JSXRuntime.jsx('div', {
383 + children: [JSXRuntime.jsx(Child, {key: '0'})],
384 });
385 }
386 }
387 expect(() =>
370 - ReactDOM.render(React.jsx(Parent, {}), container),
388 + ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
389 ).toErrorDev(
390 'Warning: React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
391 'Explicitly pass a key after spreading props in your JSX call. ' +
@@ -380,21 +398,21 @@ describe('ReactElement.jsx', () => {
398 const container = document.createElement('div');
399 class Child extends React.Component {
400 render() {
383 - return React.jsx('div', {});
401 + return JSXRuntime.jsx('div', {});
402 }
403 }
404 class Parent extends React.Component {
405 render() {
388 - return React.jsxs('div', {
406 + return JSXRuntime.jsxs('div', {
407 children: [
390 - React.jsx(Child, {}),
391 - React.jsx(Child, {}),
392 - React.jsx(Child, {}),
408 + JSXRuntime.jsx(Child, {}),
409 + JSXRuntime.jsx(Child, {}),
410 + JSXRuntime.jsx(Child, {}),
411 ],
412 });
413 }
414 }
415 // TODO: an explicit expect for no warning?
398 - ReactDOM.render(React.jsx(Parent, {}), container);
416 + ReactDOM.render(JSXRuntime.jsx(Parent, {}), container);
417 });
418 });
packages/react/src/jsx/ReactJSX.js new
+22
@@ -0,0 +1,22 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
10 +import {
11 + jsxWithValidationStatic,
12 + jsxWithValidationDynamic,
13 + jsxWithValidation,
14 +} from './ReactJSXElementValidator';
15 +import {jsx as jsxProd} from './ReactJSXElement';
16 +const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd;
17 +// we may want to special case jsxs internally to take advantage of static children.
18 +// for now we can ship identical prod functions
19 +const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd;
20 +const jsxDEV = __DEV__ ? jsxWithValidation : undefined;
21 +
22 +export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};
packages/react/src/jsx/ReactJSXElement.js new
+345
@@ -0,0 +1,345 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +import getComponentName from 'shared/getComponentName';
9 +import ReactSharedInternals from 'shared/ReactSharedInternals';
10 +
11 +import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
12 +
13 +const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
14 +
15 +const hasOwnProperty = Object.prototype.hasOwnProperty;
16 +
17 +const RESERVED_PROPS = {
18 + key: true,
19 + ref: true,
20 + __self: true,
21 + __source: true,
22 +};
23 +
24 +let specialPropKeyWarningShown;
25 +let specialPropRefWarningShown;
26 +let didWarnAboutStringRefs;
27 +
28 +if (__DEV__) {
29 + didWarnAboutStringRefs = {};
30 +}
31 +
32 +function hasValidRef(config) {
33 + if (__DEV__) {
34 + if (hasOwnProperty.call(config, 'ref')) {
35 + const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
36 + if (getter && getter.isReactWarning) {
37 + return false;
38 + }
39 + }
40 + }
41 + return config.ref !== undefined;
42 +}
43 +
44 +function hasValidKey(config) {
45 + if (__DEV__) {
46 + if (hasOwnProperty.call(config, 'key')) {
47 + const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
48 + if (getter && getter.isReactWarning) {
49 + return false;
50 + }
51 + }
52 + }
53 + return config.key !== undefined;
54 +}
55 +
56 +function warnIfStringRefCannotBeAutoConverted(config) {
57 + if (__DEV__) {
58 + if (
59 + typeof config.ref === 'string' &&
60 + ReactCurrentOwner.current &&
61 + config.__self &&
62 + ReactCurrentOwner.current.stateNode !== config.__self
63 + ) {
64 + const componentName = getComponentName(ReactCurrentOwner.current.type);
65 +
66 + if (!didWarnAboutStringRefs[componentName]) {
67 + console.error(
68 + 'Component "%s" contains the string ref "%s". ' +
69 + 'Support for string refs will be removed in a future major release. ' +
70 + 'This case cannot be automatically converted to an arrow function. ' +
71 + 'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
72 + 'Learn more about using refs safely here: ' +
73 + 'https://fb.me/react-strict-mode-string-ref',
74 + getComponentName(ReactCurrentOwner.current.type),
75 + config.ref,
76 + );
77 + didWarnAboutStringRefs[componentName] = true;
78 + }
79 + }
80 + }
81 +}
82 +
83 +function defineKeyPropWarningGetter(props, displayName) {
84 + if (__DEV__) {
85 + const warnAboutAccessingKey = function() {
86 + if (!specialPropKeyWarningShown) {
87 + specialPropKeyWarningShown = true;
88 + console.error(
89 + '%s: `key` is not a prop. Trying to access it will result ' +
90 + 'in `undefined` being returned. If you need to access the same ' +
91 + 'value within the child component, you should pass it as a different ' +
92 + 'prop. (https://fb.me/react-special-props)',
93 + displayName,
94 + );
95 + }
96 + };
97 + warnAboutAccessingKey.isReactWarning = true;
98 + Object.defineProperty(props, 'key', {
99 + get: warnAboutAccessingKey,
100 + configurable: true,
101 + });
102 + }
103 +}
104 +
105 +function defineRefPropWarningGetter(props, displayName) {
106 + if (__DEV__) {
107 + const warnAboutAccessingRef = function() {
108 + if (!specialPropRefWarningShown) {
109 + specialPropRefWarningShown = true;
110 + console.error(
111 + '%s: `ref` is not a prop. Trying to access it will result ' +
112 + 'in `undefined` being returned. If you need to access the same ' +
113 + 'value within the child component, you should pass it as a different ' +
114 + 'prop. (https://fb.me/react-special-props)',
115 + displayName,
116 + );
117 + }
118 + };
119 + warnAboutAccessingRef.isReactWarning = true;
120 + Object.defineProperty(props, 'ref', {
121 + get: warnAboutAccessingRef,
122 + configurable: true,
123 + });
124 + }
125 +}
126 +
127 +/**
128 + * Factory method to create a new React element. This no longer adheres to
129 + * the class pattern, so do not use new to call it. Also, instanceof check
130 + * will not work. Instead test $$typeof field against Symbol.for('react.element') to check
131 + * if something is a React Element.
132 + *
133 + * @param {*} type
134 + * @param {*} props
135 + * @param {*} key
136 + * @param {string|object} ref
137 + * @param {*} owner
138 + * @param {*} self A *temporary* helper to detect places where `this` is
139 + * different from the `owner` when React.createElement is called, so that we
140 + * can warn. We want to get rid of owner and replace string `ref`s with arrow
141 + * functions, and as long as `this` and owner are the same, there will be no
142 + * change in behavior.
143 + * @param {*} source An annotation object (added by a transpiler or otherwise)
144 + * indicating filename, line number, and/or other information.
145 + * @internal
146 + */
147 +const ReactElement = function(type, key, ref, self, source, owner, props) {
148 + const element = {
149 + // This tag allows us to uniquely identify this as a React Element
150 + $$typeof: REACT_ELEMENT_TYPE,
151 +
152 + // Built-in properties that belong on the element
153 + type: type,
154 + key: key,
155 + ref: ref,
156 + props: props,
157 +
158 + // Record the component responsible for creating this element.
159 + _owner: owner,
160 + };
161 +
162 + if (__DEV__) {
163 + // The validation flag is currently mutative. We put it on
164 + // an external backing store so that we can freeze the whole object.
165 + // This can be replaced with a WeakMap once they are implemented in
166 + // commonly used development environments.
167 + element._store = {};
168 +
169 + // To make comparing ReactElements easier for testing purposes, we make
170 + // the validation flag non-enumerable (where possible, which should
171 + // include every environment we run tests in), so the test framework
172 + // ignores it.
173 + Object.defineProperty(element._store, 'validated', {
174 + configurable: false,
175 + enumerable: false,
176 + writable: true,
177 + value: false,
178 + });
179 + // self and source are DEV only properties.
180 + Object.defineProperty(element, '_self', {
181 + configurable: false,
182 + enumerable: false,
183 + writable: false,
184 + value: self,
185 + });
186 + // Two elements created in two different places should be considered
187 + // equal for testing purposes and therefore we hide it from enumeration.
188 + Object.defineProperty(element, '_source', {
189 + configurable: false,
190 + enumerable: false,
191 + writable: false,
192 + value: source,
193 + });
194 + if (Object.freeze) {
195 + Object.freeze(element.props);
196 + Object.freeze(element);
197 + }
198 + }
199 +
200 + return element;
201 +};
202 +
203 +/**
204 + * https://github.com/reactjs/rfcs/pull/107
205 + * @param {*} type
206 + * @param {object} props
207 + * @param {string} key
208 + */
209 +export function jsx(type, config, maybeKey) {
210 + let propName;
211 +
212 + // Reserved names are extracted
213 + const props = {};
214 +
215 + let key = null;
216 + let ref = null;
217 +
218 + // Currently, key can be spread in as a prop. This causes a potential
219 + // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
220 + // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
221 + // but as an intermediary step, we will use jsxDEV for everything except
222 + // <div {...props} key="Hi" />, because we aren't currently able to tell if
223 + // key is explicitly declared to be undefined or not.
224 + if (maybeKey !== undefined) {
225 + key = '' + maybeKey;
226 + }
227 +
228 + if (hasValidKey(config)) {
229 + key = '' + config.key;
230 + }
231 +
232 + if (hasValidRef(config)) {
233 + ref = config.ref;
234 + }
235 +
236 + // Remaining properties are added to a new props object
237 + for (propName in config) {
238 + if (
239 + hasOwnProperty.call(config, propName) &&
240 + !RESERVED_PROPS.hasOwnProperty(propName)
241 + ) {
242 + props[propName] = config[propName];
243 + }
244 + }
245 +
246 + // Resolve default props
247 + if (type && type.defaultProps) {
248 + const defaultProps = type.defaultProps;
249 + for (propName in defaultProps) {
250 + if (props[propName] === undefined) {
251 + props[propName] = defaultProps[propName];
252 + }
253 + }
254 + }
255 +
256 + return ReactElement(
257 + type,
258 + key,
259 + ref,
260 + undefined,
261 + undefined,
262 + ReactCurrentOwner.current,
263 + props,
264 + );
265 +}
266 +
267 +/**
268 + * https://github.com/reactjs/rfcs/pull/107
269 + * @param {*} type
270 + * @param {object} props
271 + * @param {string} key
272 + */
273 +export function jsxDEV(type, config, maybeKey, source, self) {
274 + if (__DEV__) {
275 + let propName;
276 +
277 + // Reserved names are extracted
278 + const props = {};
279 +
280 + let key = null;
281 + let ref = null;
282 +
283 + // Currently, key can be spread in as a prop. This causes a potential
284 + // issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
285 + // or <div key="Hi" {...props} /> ). We want to deprecate key spread,
286 + // but as an intermediary step, we will use jsxDEV for everything except
287 + // <div {...props} key="Hi" />, because we aren't currently able to tell if
288 + // key is explicitly declared to be undefined or not.
289 + if (maybeKey !== undefined) {
290 + key = '' + maybeKey;
291 + }
292 +
293 + if (hasValidKey(config)) {
294 + key = '' + config.key;
295 + }
296 +
297 + if (hasValidRef(config)) {
298 + ref = config.ref;
299 + warnIfStringRefCannotBeAutoConverted(config);
300 + }
301 +
302 + // Remaining properties are added to a new props object
303 + for (propName in config) {
304 + if (
305 + hasOwnProperty.call(config, propName) &&
306 + !RESERVED_PROPS.hasOwnProperty(propName)
307 + ) {
308 + props[propName] = config[propName];
309 + }
310 + }
311 +
312 + // Resolve default props
313 + if (type && type.defaultProps) {
314 + const defaultProps = type.defaultProps;
315 + for (propName in defaultProps) {
316 + if (props[propName] === undefined) {
317 + props[propName] = defaultProps[propName];
318 + }
319 + }
320 + }
321 +
322 + if (key || ref) {
323 + const displayName =
324 + typeof type === 'function'
325 + ? type.displayName || type.name || 'Unknown'
326 + : type;
327 + if (key) {
328 + defineKeyPropWarningGetter(props, displayName);
329 + }
330 + if (ref) {
331 + defineRefPropWarningGetter(props, displayName);
332 + }
333 + }
334 +
335 + return ReactElement(
336 + type,
337 + key,
338 + ref,
339 + self,
340 + source,
341 + ReactCurrentOwner.current,
342 + props,
343 + );
344 + }
345 +}
packages/react/src/jsx/ReactJSXElementValidator.js new
+407
@@ -0,0 +1,407 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + */
7 +
8 +/**
9 + * ReactElementValidator provides a wrapper around a element factory
10 + * which validates the props passed to the element. This is intended to be
11 + * used only in DEV and could be replaced by a static type checker for languages
12 + * that support it.
13 + */
14 +import isValidElementType from 'shared/isValidElementType';
15 +import getComponentName from 'shared/getComponentName';
16 +import checkPropTypes from 'shared/checkPropTypes';
17 +import {
18 + getIteratorFn,
19 + REACT_FORWARD_REF_TYPE,
20 + REACT_MEMO_TYPE,
21 + REACT_FRAGMENT_TYPE,
22 + REACT_ELEMENT_TYPE,
23 +} from 'shared/ReactSymbols';
24 +import {warnAboutSpreadingKeyToJSX} from 'shared/ReactFeatureFlags';
25 +
26 +import {jsxDEV} from './ReactJSXElement';
27 +import ReactSharedInternals from 'shared/ReactSharedInternals';
28 +
29 +const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
30 +const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
31 +
32 +let propTypesMisspellWarningShown;
33 +
34 +if (__DEV__) {
35 + propTypesMisspellWarningShown = false;
36 +}
37 +
38 +const hasOwnProperty = Object.prototype.hasOwnProperty;
39 +
40 +/**
41 + * Verifies the object is a ReactElement.
42 + * See https://reactjs.org/docs/react-api.html#isvalidelement
43 + * @param {?object} object
44 + * @return {boolean} True if `object` is a ReactElement.
45 + * @final
46 + */
47 +export function isValidElement(object) {
48 + if (__DEV__) {
49 + return (
50 + typeof object === 'object' &&
51 + object !== null &&
52 + object.$$typeof === REACT_ELEMENT_TYPE
53 + );
54 + }
55 +}
56 +
57 +function getDeclarationErrorAddendum() {
58 + if (__DEV__) {
59 + if (ReactCurrentOwner.current) {
60 + const name = getComponentName(ReactCurrentOwner.current.type);
61 + if (name) {
62 + return '\n\nCheck the render method of `' + name + '`.';
63 + }
64 + }
65 + return '';
66 + }
67 +}
68 +
69 +function getSourceInfoErrorAddendum(source) {
70 + if (__DEV__) {
71 + if (source !== undefined) {
72 + const fileName = source.fileName.replace(/^.*[\\\/]/, '');
73 + const lineNumber = source.lineNumber;
74 + return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
75 + }
76 + return '';
77 + }
78 +}
79 +
80 +/**
81 + * Warn if there's no key explicitly set on dynamic arrays of children or
82 + * object keys are not valid. This allows us to keep track of children between
83 + * updates.
84 + */
85 +const ownerHasKeyUseWarning = {};
86 +
87 +function getCurrentComponentErrorInfo(parentType) {
88 + if (__DEV__) {
89 + let info = getDeclarationErrorAddendum();
90 +
91 + if (!info) {
92 + const parentName =
93 + typeof parentType === 'string'
94 + ? parentType
95 + : parentType.displayName || parentType.name;
96 + if (parentName) {
97 + info = `\n\nCheck the top-level render call using <${parentName}>.`;
98 + }
99 + }
100 + return info;
101 + }
102 +}
103 +
104 +/**
105 + * Warn if the element doesn't have an explicit key assigned to it.
106 + * This element is in an array. The array could grow and shrink or be
107 + * reordered. All children that haven't already been validated are required to
108 + * have a "key" property assigned to it. Error statuses are cached so a warning
109 + * will only be shown once.
110 + *
111 + * @internal
112 + * @param {ReactElement} element Element that requires a key.
113 + * @param {*} parentType element's parent's type.
114 + */
115 +function validateExplicitKey(element, parentType) {
116 + if (__DEV__) {
117 + if (!element._store || element._store.validated || element.key != null) {
118 + return;
119 + }
120 + element._store.validated = true;
121 +
122 + const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
123 + if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
124 + return;
125 + }
126 + ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
127 +
128 + // Usually the current owner is the offender, but if it accepts children as a
129 + // property, it may be the creator of the child that's responsible for
130 + // assigning it a key.
131 + let childOwner = '';
132 + if (
133 + element &&
134 + element._owner &&
135 + element._owner !== ReactCurrentOwner.current
136 + ) {
137 + // Give the component that originally created this child.
138 + childOwner = ` It was passed a child from ${getComponentName(
139 + element._owner.type,
140 + )}.`;
141 + }
142 +
143 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
144 + console.error(
145 + 'Each child in a list should have a unique "key" prop.' +
146 + '%s%s See https://fb.me/react-warning-keys for more information.',
147 + currentComponentErrorInfo,
148 + childOwner,
149 + );
150 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
151 + }
152 +}
153 +
154 +/**
155 + * Ensure that every element either is passed in a static location, in an
156 + * array with an explicit keys property defined, or in an object literal
157 + * with valid key property.
158 + *
159 + * @internal
160 + * @param {ReactNode} node Statically passed child of any type.
161 + * @param {*} parentType node's parent's type.
162 + */
163 +function validateChildKeys(node, parentType) {
164 + if (__DEV__) {
165 + if (typeof node !== 'object') {
166 + return;
167 + }
168 + if (Array.isArray(node)) {
169 + for (let i = 0; i < node.length; i++) {
170 + const child = node[i];
171 + if (isValidElement(child)) {
172 + validateExplicitKey(child, parentType);
173 + }
174 + }
175 + } else if (isValidElement(node)) {
176 + // This element was passed in a valid location.
177 + if (node._store) {
178 + node._store.validated = true;
179 + }
180 + } else if (node) {
181 + const iteratorFn = getIteratorFn(node);
182 + if (typeof iteratorFn === 'function') {
183 + // Entry iterators used to provide implicit keys,
184 + // but now we print a separate warning for them later.
185 + if (iteratorFn !== node.entries) {
186 + const iterator = iteratorFn.call(node);
187 + let step;
188 + while (!(step = iterator.next()).done) {
189 + if (isValidElement(step.value)) {
190 + validateExplicitKey(step.value, parentType);
191 + }
192 + }
193 + }
194 + }
195 + }
196 + }
197 +}
198 +
199 +/**
200 + * Given an element, validate that its props follow the propTypes definition,
201 + * provided by the type.
202 + *
203 + * @param {ReactElement} element
204 + */
205 +function validatePropTypes(element) {
206 + if (__DEV__) {
207 + const type = element.type;
208 + if (type === null || type === undefined || typeof type === 'string') {
209 + return;
210 + }
211 + const name = getComponentName(type);
212 + let propTypes;
213 + if (typeof type === 'function') {
214 + propTypes = type.propTypes;
215 + } else if (
216 + typeof type === 'object' &&
217 + (type.$$typeof === REACT_FORWARD_REF_TYPE ||
218 + // Note: Memo only checks outer props here.
219 + // Inner props are checked in the reconciler.
220 + type.$$typeof === REACT_MEMO_TYPE)
221 + ) {
222 + propTypes = type.propTypes;
223 + } else {
224 + return;
225 + }
226 + if (propTypes) {
227 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
228 + checkPropTypes(propTypes, element.props, 'prop', name);
229 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
230 + } else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
231 + propTypesMisspellWarningShown = true;
232 + console.error(
233 + 'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
234 + name || 'Unknown',
235 + );
236 + }
237 + if (
238 + typeof type.getDefaultProps === 'function' &&
239 + !type.getDefaultProps.isReactClassApproved
240 + ) {
241 + console.error(
242 + 'getDefaultProps is only used on classic React.createClass ' +
243 + 'definitions. Use a static property named `defaultProps` instead.',
244 + );
245 + }
246 + }
247 +}
248 +
249 +/**
250 + * Given a fragment, validate that it can only be provided with fragment props
251 + * @param {ReactElement} fragment
252 + */
253 +function validateFragmentProps(fragment) {
254 + if (__DEV__) {
255 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(fragment);
256 +
257 + const keys = Object.keys(fragment.props);
258 + for (let i = 0; i < keys.length; i++) {
259 + const key = keys[i];
260 + if (key !== 'children' && key !== 'key') {
261 + console.error(
262 + 'Invalid prop `%s` supplied to `React.Fragment`. ' +
263 + 'React.Fragment can only have `key` and `children` props.',
264 + key,
265 + );
266 + break;
267 + }
268 + }
269 +
270 + if (fragment.ref !== null) {
271 + console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
272 + }
273 +
274 + ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
275 + }
276 +}
277 +
278 +export function jsxWithValidation(
279 + type,
280 + props,
281 + key,
282 + isStaticChildren,
283 + source,
284 + self,
285 +) {
286 + if (__DEV__) {
287 + const validType = isValidElementType(type);
288 +
289 + // We warn in this case but don't throw. We expect the element creation to
290 + // succeed and there will likely be errors in render.
291 + if (!validType) {
292 + let info = '';
293 + if (
294 + type === undefined ||
295 + (typeof type === 'object' &&
296 + type !== null &&
297 + Object.keys(type).length === 0)
298 + ) {
299 + info +=
300 + ' You likely forgot to export your component from the file ' +
301 + "it's defined in, or you might have mixed up default and named imports.";
302 + }
303 +
304 + const sourceInfo = getSourceInfoErrorAddendum(source);
305 + if (sourceInfo) {
306 + info += sourceInfo;
307 + } else {
308 + info += getDeclarationErrorAddendum();
309 + }
310 +
311 + let typeString;
312 + if (type === null) {
313 + typeString = 'null';
314 + } else if (Array.isArray(type)) {
315 + typeString = 'array';
316 + } else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
317 + typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
318 + info =
319 + ' Did you accidentally export a JSX literal instead of a component?';
320 + } else {
321 + typeString = typeof type;
322 + }
323 +
324 + console.error(
325 + 'React.jsx: type is invalid -- expected a string (for ' +
326 + 'built-in components) or a class/function (for composite ' +
327 + 'components) but got: %s.%s',
328 + typeString,
329 + info,
330 + );
331 + }
332 +
333 + const element = jsxDEV(type, props, key, source, self);
334 +
335 + // The result can be nullish if a mock or a custom function is used.
336 + // TODO: Drop this when these are no longer allowed as the type argument.
337 + if (element == null) {
338 + return element;
339 + }
340 +
341 + // Skip key warning if the type isn't valid since our key validation logic
342 + // doesn't expect a non-string/function type and can throw confusing errors.
343 + // We don't want exception behavior to differ between dev and prod.
344 + // (Rendering will throw with a helpful message and as soon as the type is
345 + // fixed, the key warnings will appear.)
346 +
347 + if (validType) {
348 + const children = props.children;
349 + if (children !== undefined) {
350 + if (isStaticChildren) {
351 + if (Array.isArray(children)) {
352 + for (let i = 0; i < children.length; i++) {
353 + validateChildKeys(children[i], type);
354 + }
355 +
356 + if (Object.freeze) {
357 + Object.freeze(children);
358 + }
359 + } else {
360 + console.error(
361 + 'React.jsx: Static children should always be an array. ' +
362 + 'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
363 + 'Use the Babel transform instead.',
364 + );
365 + }
366 + } else {
367 + validateChildKeys(children, type);
368 + }
369 + }
370 + }
371 +
372 + if (warnAboutSpreadingKeyToJSX) {
373 + if (hasOwnProperty.call(props, 'key')) {
374 + console.error(
375 + 'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
376 + 'Explicitly pass a key after spreading props in your JSX call. ' +
377 + 'E.g. <%s {...props} key={key} />',
378 + getComponentName(type) || 'ComponentName',
379 + );
380 + }
381 + }
382 +
383 + if (type === REACT_FRAGMENT_TYPE) {
384 + validateFragmentProps(element);
385 + } else {
386 + validatePropTypes(element);
387 + }
388 +
389 + return element;
390 + }
391 +}
392 +
393 +// These two functions exist to still get child warnings in dev
394 +// even with the prod transform. This means that jsxDEV is purely
395 +// opt-in behavior for better messages but that we won't stop
396 +// giving you warnings if you use production apis.
397 +export function jsxWithValidationStatic(type, props, key) {
398 + if (__DEV__) {
399 + return jsxWithValidation(type, props, key, true);
400 + }
401 +}
402 +
403 +export function jsxWithValidationDynamic(type, props, key) {
404 + if (__DEV__) {
405 + return jsxWithValidation(type, props, key, false);
406 + }
407 +}
scripts/rollup/bundles.js
+32
@@ -76,6 +76,38 @@ const bundles = [
76 externals: [],
77 },
78
79 + /******* React JSX Runtime *******/
80 + {
81 + bundleTypes: [
82 + NODE_DEV,
83 + NODE_PROD,
84 + NODE_PROFILING,
85 + FB_WWW_DEV,
86 + FB_WWW_PROD,
87 + FB_WWW_PROFILING,
88 + ],
89 + moduleType: ISOMORPHIC,
90 + entry: 'react/jsx-runtime',
91 + global: 'JSXRuntime',
92 + externals: ['react'],
93 + },
94 +
95 + /******* React JSX DEV Runtime *******/
96 + {
97 + bundleTypes: [
98 + NODE_DEV,
99 + NODE_PROD,
100 + NODE_PROFILING,
101 + FB_WWW_DEV,
102 + FB_WWW_PROD,
103 + FB_WWW_PROFILING,
104 + ],
105 + moduleType: ISOMORPHIC,
106 + entry: 'react/jsx-dev-runtime',
107 + global: 'JSXDEVRuntime',
108 + externals: ['react'],
109 + },
110 +
111 /******* React DOM *******/
112 {
113 bundleTypes: [
scripts/rollup/forks.js
+1 -1
@@ -58,7 +58,7 @@ const forks = Object.freeze({
58 if (entry === 'react') {
59 return 'react/src/ReactSharedInternals';
60 }
61 - if (dependencies.indexOf('react') === -1) {
61 + if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {
62 // React internals are unavailable if we can't reference the package.
63 // We return an error because we only want to throw if this module gets used.
64 return new Error(
scripts/rollup/modules.js
+1
@@ -14,6 +14,7 @@ const importSideEffects = Object.freeze({
14 scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
15 'scheduler/tracing': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
16 'react-dom/server': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
17 + 'react/jsx-dev-runtime': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
18 });
19
20 // Bundles exporting globals that other modules rely on.