Skip to content

Commit

Permalink
Provide new jsx transform target for reactjs/rfcs#107 (#15141)
Browse files Browse the repository at this point in the history
* adding jsx function

* add more feature flag defaults

* flip ReactElement order back
  • Loading branch information
rickyvetter authored Apr 7, 2019
1 parent 81a61b1 commit 745baf2
Show file tree
Hide file tree
Showing 13 changed files with 659 additions and 12 deletions.
22 changes: 21 additions & 1 deletion packages/react/src/React.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createFactory,
cloneElement,
isValidElement,
jsx,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
Expand All @@ -43,10 +44,16 @@ import {
createElementWithValidation,
createFactoryWithValidation,
cloneElementWithValidation,
jsxWithValidation,
jsxWithValidationStatic,
jsxWithValidationDynamic,
} from './ReactElementValidator';
import ReactSharedInternals from './ReactSharedInternals';
import {error, warn} from './withComponentStack';
import {enableStableConcurrentModeAPIs} from 'shared/ReactFeatureFlags';
import {
enableStableConcurrentModeAPIs,
enableJSXTransformAPI,
} from 'shared/ReactFeatureFlags';

const React = {
Children: {
Expand Down Expand Up @@ -107,4 +114,17 @@ if (enableStableConcurrentModeAPIs) {
React.unstable_ConcurrentMode = undefined;
}

if (enableJSXTransformAPI) {
if (__DEV__) {
React.jsxDEV = jsxWithValidation;
React.jsx = jsxWithValidationDynamic;
React.jsxs = jsxWithValidationStatic;
} else {
React.jsx = jsx;
// we may want to special case jsxs internally to take advantage of static children.
// for now we can ship identical prod functions
React.jsxs = jsx;
}
}

export default React;
137 changes: 135 additions & 2 deletions packages/react/src/ReactElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,17 @@ function defineRefPropWarningGetter(props, displayName) {
* if something is a React Element.
*
* @param {*} type
* @param {*} props
* @param {*} key
* @param {string|object} ref
* @param {*} owner
* @param {*} self A *temporary* helper to detect places where `this` is
* different from the `owner` when React.createElement is called, so that we
* can warn. We want to get rid of owner and replace string `ref`s with arrow
* functions, and as long as `this` and owner are the same, there will be no
* change in behavior.
* @param {*} source An annotation object (added by a transpiler or otherwise)
* indicating filename, line number, and/or other information.
* @param {*} owner
* @param {*} props
* @internal
*/
const ReactElement = function(type, key, ref, self, source, owner, props) {
Expand Down Expand Up @@ -164,6 +164,139 @@ const ReactElement = function(type, key, ref, self, source, owner, props) {
return element;
};

/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
export function jsx(type, config, maybeKey) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;

if (hasValidRef(config)) {
ref = config.ref;
}

if (hasValidKey(config)) {
key = '' + config.key;
}

// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}

// intentionally not checking if key was set above
// this key is higher priority as it's static
if (maybeKey !== undefined) {
key = '' + maybeKey;
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

return ReactElement(
type,
key,
ref,
undefined,
undefined,
ReactCurrentOwner.current,
props,
);
}

/**
* https://github.com/reactjs/rfcs/pull/107
* @param {*} type
* @param {object} props
* @param {string} key
*/
export function jsxDEV(type, config, maybeKey, source, self) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;

if (hasValidRef(config)) {
ref = config.ref;
}

if (hasValidKey(config)) {
key = '' + config.key;
}

// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}

// intentionally not checking if key was set above
// this key is higher priority as it's static
if (maybeKey !== undefined) {
key = '' + maybeKey;
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}

return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}

/**
* Create and return a new ReactElement of the given type.
* See https://reactjs.org/docs/react-api.html#createelement
Expand Down
136 changes: 127 additions & 9 deletions packages/react/src/ReactElementValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ import warning from 'shared/warning';
import warningWithoutStack from 'shared/warningWithoutStack';

import ReactCurrentOwner from './ReactCurrentOwner';
import {isValidElement, createElement, cloneElement} from './ReactElement';
import {
isValidElement,
createElement,
cloneElement,
jsxDEV,
} from './ReactElement';
import ReactDebugCurrentFrame, {
setCurrentlyValidatingElement,
} from './ReactDebugCurrentFrame';
Expand All @@ -48,20 +53,22 @@ function getDeclarationErrorAddendum() {
return '';
}

function getSourceInfoErrorAddendum(elementProps) {
if (
elementProps !== null &&
elementProps !== undefined &&
elementProps.__source !== undefined
) {
const source = elementProps.__source;
function getSourceInfoErrorAddendum(source) {
if (source !== undefined) {
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
const lineNumber = source.lineNumber;
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
}
return '';
}

function getSourceInfoErrorAddendumForProps(elementProps) {
if (elementProps !== null && elementProps !== undefined) {
return getSourceInfoErrorAddendum(elementProps.__source);
}
return '';
}

/**
* Warn if there's no key explicitly set on dynamic arrays of children or
* object keys are not valid. This allows us to keep track of children between
Expand Down Expand Up @@ -259,6 +266,117 @@ function validateFragmentProps(fragment) {
setCurrentlyValidatingElement(null);
}

export function jsxWithValidation(
type,
props,
key,
isStaticChildren,
source,
self,
) {
const validType = isValidElementType(type);

// We warn in this case but don't throw. We expect the element creation to
// succeed and there will likely be errors in render.
if (!validType) {
let info = '';
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and named imports.";
}

const sourceInfo = getSourceInfoErrorAddendum(source);
if (sourceInfo) {
info += sourceInfo;
} else {
info += getDeclarationErrorAddendum();
}

let typeString;
if (type === null) {
typeString = 'null';
} else if (Array.isArray(type)) {
typeString = 'array';
} else if (type !== undefined && type.$$typeof === REACT_ELEMENT_TYPE) {
typeString = `<${getComponentName(type.type) || 'Unknown'} />`;
info =
' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}

warning(
false,
'React.jsx: type is invalid -- expected a string (for ' +
'built-in components) or a class/function (for composite ' +
'components) but got: %s.%s',
typeString,
info,
);
}

const element = jsxDEV(type, props, key, source, self);

// The result can be nullish if a mock or a custom function is used.
// TODO: Drop this when these are no longer allowed as the type argument.
if (element == null) {
return element;
}

// Skip key warning if the type isn't valid since our key validation logic
// doesn't expect a non-string/function type and can throw confusing errors.
// We don't want exception behavior to differ between dev and prod.
// (Rendering will throw with a helpful message and as soon as the type is
// fixed, the key warnings will appear.)
if (validType) {
const children = props.children;
if (children !== undefined) {
if (isStaticChildren) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
} else {
validateChildKeys(children, type);
}
}
}

if (props.key !== undefined) {
warning(
false,
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <ComponentName {...props} key={key} />',
);
}

if (type === REACT_FRAGMENT_TYPE) {
validateFragmentProps(element);
} else {
validatePropTypes(element);
}

return element;
}

// These two functions exist to still get child warnings in dev
// even with the prod transform. This means that jsxDEV is purely
// opt-in behavior for better messages but that we won't stop
// giving you warnings if you use production apis.
export function jsxWithValidationStatic(type, props, key) {
return jsxWithValidation(type, props, key, true);
}

export function jsxWithValidationDynamic(type, props, key) {
return jsxWithValidation(type, props, key, false);
}

export function createElementWithValidation(type, props, children) {
const validType = isValidElementType(type);

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

const sourceInfo = getSourceInfoErrorAddendum(props);
const sourceInfo = getSourceInfoErrorAddendumForProps(props);
if (sourceInfo) {
info += sourceInfo;
} else {
Expand Down
Loading

0 comments on commit 745baf2

Please sign in to comment.