Skip to content

Commit 745baf2

Browse files
authored
Provide new jsx transform target for reactjs/rfcs#107 (#15141)
* adding jsx function * add more feature flag defaults * flip ReactElement order back
1 parent 81a61b1 commit 745baf2

13 files changed

+659
-12
lines changed

packages/react/src/React.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
createFactory,
2323
cloneElement,
2424
isValidElement,
25+
jsx,
2526
} from './ReactElement';
2627
import {createContext} from './ReactContext';
2728
import {lazy} from './ReactLazy';
@@ -43,10 +44,16 @@ import {
4344
createElementWithValidation,
4445
createFactoryWithValidation,
4546
cloneElementWithValidation,
47+
jsxWithValidation,
48+
jsxWithValidationStatic,
49+
jsxWithValidationDynamic,
4650
} from './ReactElementValidator';
4751
import ReactSharedInternals from './ReactSharedInternals';
4852
import {error, warn} from './withComponentStack';
49-
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
53+
import {
54+
enableStableConcurrentModeAPIs,
55+
enableJSXTransformAPI,
56+
} from 'shared/ReactFeatureFlags';
5057

5158
const React = {
5259
Children: {
@@ -107,4 +114,17 @@ if (enableStableConcurrentModeAPIs) {
107114
React.unstable_ConcurrentMode = undefined;
108115
}
109116

117+
if (enableJSXTransformAPI) {
118+
if (__DEV__) {
119+
React.jsxDEV = jsxWithValidation;
120+
React.jsx = jsxWithValidationDynamic;
121+
React.jsxs = jsxWithValidationStatic;
122+
} else {
123+
React.jsx = jsx;
124+
// we may want to special case jsxs internally to take advantage of static children.
125+
// for now we can ship identical prod functions
126+
React.jsxs = jsx;
127+
}
128+
}
129+
110130
export default React;

packages/react/src/ReactElement.js

+135-2
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,17 @@ function defineRefPropWarningGetter(props, displayName) {
9595
* if something is a React Element.
9696
*
9797
* @param {*} type
98+
* @param {*} props
9899
* @param {*} key
99100
* @param {string|object} ref
101+
* @param {*} owner
100102
* @param {*} self A *temporary* helper to detect places where `this` is
101103
* different from the `owner` when React.createElement is called, so that we
102104
* can warn. We want to get rid of owner and replace string `ref`s with arrow
103105
* functions, and as long as `this` and owner are the same, there will be no
104106
* change in behavior.
105107
* @param {*} source An annotation object (added by a transpiler or otherwise)
106108
* indicating filename, line number, and/or other information.
107-
* @param {*} owner
108-
* @param {*} props
109109
* @internal
110110
*/
111111
const ReactElement = function(type, key, ref, self, source, owner, props) {
@@ -164,6 +164,139 @@ const ReactElement = function(type, key, ref, self, source, owner, props) {
164164
return element;
165165
};
166166

167+
/**
168+
* https://github.com/reactjs/rfcs/pull/107
169+
* @param {*} type
170+
* @param {object} props
171+
* @param {string} key
172+
*/
173+
export function jsx(type, config, maybeKey) {
174+
let propName;
175+
176+
// Reserved names are extracted
177+
const props = {};
178+
179+
let key = null;
180+
let ref = null;
181+
182+
if (hasValidRef(config)) {
183+
ref = config.ref;
184+
}
185+
186+
if (hasValidKey(config)) {
187+
key = '' + config.key;
188+
}
189+
190+
// Remaining properties are added to a new props object
191+
for (propName in config) {
192+
if (
193+
hasOwnProperty.call(config, propName) &&
194+
!RESERVED_PROPS.hasOwnProperty(propName)
195+
) {
196+
props[propName] = config[propName];
197+
}
198+
}
199+
200+
// intentionally not checking if key was set above
201+
// this key is higher priority as it's static
202+
if (maybeKey !== undefined) {
203+
key = '' + maybeKey;
204+
}
205+
206+
// Resolve default props
207+
if (type && type.defaultProps) {
208+
const defaultProps = type.defaultProps;
209+
for (propName in defaultProps) {
210+
if (props[propName] === undefined) {
211+
props[propName] = defaultProps[propName];
212+
}
213+
}
214+
}
215+
216+
return ReactElement(
217+
type,
218+
key,
219+
ref,
220+
undefined,
221+
undefined,
222+
ReactCurrentOwner.current,
223+
props,
224+
);
225+
}
226+
227+
/**
228+
* https://github.com/reactjs/rfcs/pull/107
229+
* @param {*} type
230+
* @param {object} props
231+
* @param {string} key
232+
*/
233+
export function jsxDEV(type, config, maybeKey, source, self) {
234+
let propName;
235+
236+
// Reserved names are extracted
237+
const props = {};
238+
239+
let key = null;
240+
let ref = null;
241+
242+
if (hasValidRef(config)) {
243+
ref = config.ref;
244+
}
245+
246+
if (hasValidKey(config)) {
247+
key = '' + config.key;
248+
}
249+
250+
// Remaining properties are added to a new props object
251+
for (propName in config) {
252+
if (
253+
hasOwnProperty.call(config, propName) &&
254+
!RESERVED_PROPS.hasOwnProperty(propName)
255+
) {
256+
props[propName] = config[propName];
257+
}
258+
}
259+
260+
// intentionally not checking if key was set above
261+
// this key is higher priority as it's static
262+
if (maybeKey !== undefined) {
263+
key = '' + maybeKey;
264+
}
265+
266+
// Resolve default props
267+
if (type && type.defaultProps) {
268+
const defaultProps = type.defaultProps;
269+
for (propName in defaultProps) {
270+
if (props[propName] === undefined) {
271+
props[propName] = defaultProps[propName];
272+
}
273+
}
274+
}
275+
276+
if (key || ref) {
277+
const displayName =
278+
typeof type === 'function'
279+
? type.displayName || type.name || 'Unknown'
280+
: type;
281+
if (key) {
282+
defineKeyPropWarningGetter(props, displayName);
283+
}
284+
if (ref) {
285+
defineRefPropWarningGetter(props, displayName);
286+
}
287+
}
288+
289+
return ReactElement(
290+
type,
291+
key,
292+
ref,
293+
self,
294+
source,
295+
ReactCurrentOwner.current,
296+
props,
297+
);
298+
}
299+
167300
/**
168301
* Create and return a new ReactElement of the given type.
169302
* See https://reactjs.org/docs/react-api.html#createelement

packages/react/src/ReactElementValidator.js

+127-9
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import warning from 'shared/warning';
2727
import warningWithoutStack from 'shared/warningWithoutStack';
2828

2929
import ReactCurrentOwner from './ReactCurrentOwner';
30-
import {isValidElement, createElement, cloneElement} from './ReactElement';
30+
import {
31+
isValidElement,
32+
createElement,
33+
cloneElement,
34+
jsxDEV,
35+
} from './ReactElement';
3136
import ReactDebugCurrentFrame, {
3237
setCurrentlyValidatingElement,
3338
} from './ReactDebugCurrentFrame';
@@ -48,20 +53,22 @@ function getDeclarationErrorAddendum() {
4853
return '';
4954
}
5055

51-
function getSourceInfoErrorAddendum(elementProps) {
52-
if (
53-
elementProps !== null &&
54-
elementProps !== undefined &&
55-
elementProps.__source !== undefined
56-
) {
57-
const source = elementProps.__source;
56+
function getSourceInfoErrorAddendum(source) {
57+
if (source !== undefined) {
5858
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
5959
const lineNumber = source.lineNumber;
6060
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
6161
}
6262
return '';
6363
}
6464

65+
function getSourceInfoErrorAddendumForProps(elementProps) {
66+
if (elementProps !== null && elementProps !== undefined) {
67+
return getSourceInfoErrorAddendum(elementProps.__source);
68+
}
69+
return '';
70+
}
71+
6572
/**
6673
* Warn if there's no key explicitly set on dynamic arrays of children or
6774
* object keys are not valid. This allows us to keep track of children between
@@ -259,6 +266,117 @@ function validateFragmentProps(fragment) {
259266
setCurrentlyValidatingElement(null);
260267
}
261268

269+
export function jsxWithValidation(
270+
type,
271+
props,
272+
key,
273+
isStaticChildren,
274+
source,
275+
self,
276+
) {
277+
const validType = isValidElementType(type);
278+
279+
// We warn in this case but don't throw. We expect the element creation to
280+
// succeed and there will likely be errors in render.
281+
if (!validType) {
282+
let info = '';
283+
if (
284+
type === undefined ||
285+
(typeof type === 'object' &&
286+
type !== null &&
287+
Object.keys(type).length === 0)
288+
) {
289+
info +=
290+
' You likely forgot to export your component from the file ' +
291+
"it's defined in, or you might have mixed up default and named imports.";
292+
}
293+
294+
const sourceInfo = getSourceInfoErrorAddendum(source);
295+
if (sourceInfo) {
296+
info += sourceInfo;
297+
} else {
298+
info += getDeclarationErrorAddendum();
299+
}
300+
301+
let typeString;
302+
if (type === null) {
303+
typeString = 'null';
304+
} else if (Array.isArray(type)) {
305+
typeString = 'array';
306+
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
307+
typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
308+
info =
309+
' Did you accidentally export a JSX literal instead of a component?';
310+
} else {
311+
typeString = typeof type;
312+
}
313+
314+
warning(
315+
false,
316+
'React.jsx: type is invalid -- expected a string (for ' +
317+
'built-in components) or a class/function (for composite ' +
318+
'components) but got: %s.%s',
319+
typeString,
320+
info,
321+
);
322+
}
323+
324+
const element = jsxDEV(type, props, key, source, self);
325+
326+
// The result can be nullish if a mock or a custom function is used.
327+
// TODO: Drop this when these are no longer allowed as the type argument.
328+
if (element == null) {
329+
return element;
330+
}
331+
332+
// Skip key warning if the type isn't valid since our key validation logic
333+
// doesn't expect a non-string/function type and can throw confusing errors.
334+
// We don't want exception behavior to differ between dev and prod.
335+
// (Rendering will throw with a helpful message and as soon as the type is
336+
// fixed, the key warnings will appear.)
337+
if (validType) {
338+
const children = props.children;
339+
if (children !== undefined) {
340+
if (isStaticChildren) {
341+
for (let i = 0; i < children.length; i++) {
342+
validateChildKeys(children[i], type);
343+
}
344+
} else {
345+
validateChildKeys(children, type);
346+
}
347+
}
348+
}
349+
350+
if (props.key !== undefined) {
351+
warning(
352+
false,
353+
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
354+
'Explicitly pass a key after spreading props in your JSX call. ' +
355+
'E.g. <ComponentName {...props} key={key} />',
356+
);
357+
}
358+
359+
if (type === REACT_FRAGMENT_TYPE) {
360+
validateFragmentProps(element);
361+
} else {
362+
validatePropTypes(element);
363+
}
364+
365+
return element;
366+
}
367+
368+
// These two functions exist to still get child warnings in dev
369+
// even with the prod transform. This means that jsxDEV is purely
370+
// opt-in behavior for better messages but that we won't stop
371+
// giving you warnings if you use production apis.
372+
export function jsxWithValidationStatic(type, props, key) {
373+
return jsxWithValidation(type, props, key, true);
374+
}
375+
376+
export function jsxWithValidationDynamic(type, props, key) {
377+
return jsxWithValidation(type, props, key, false);
378+
}
379+
262380
export function createElementWithValidation(type, props, children) {
263381
const validType = isValidElementType(type);
264382

@@ -277,7 +395,7 @@ export function createElementWithValidation(type, props, children) {
277395
"it's defined in, or you might have mixed up default and named imports.";
278396
}
279397

280-
const sourceInfo = getSourceInfoErrorAddendum(props);
398+
const sourceInfo = getSourceInfoErrorAddendumForProps(props);
281399
if (sourceInfo) {
282400
info += sourceInfo;
283401
} else {

0 commit comments

Comments
 (0)