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.
This commit is contained in:
Luna Ruan
2020-03-17 13:22:19 -07:00
committed by GitHub
parent 26666427d6
commit 90f8fe6f55
18 changed files with 927 additions and 88 deletions

View File

@@ -50,8 +50,5 @@ export {
DEPRECATED_createResponder,
// enableScopeAPI
unstable_createScope,
// enableJSXTransformAPI
jsx,
jsxs,
jsxDEV,
} from './src/React';
export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';

View File

@@ -45,8 +45,4 @@ export {
unstable_withSuspenseConfig,
// enableBlocksAPI
block,
// enableJSXTransformAPI
jsx,
jsxs,
jsxDEV,
} from './src/React';

View File

@@ -76,7 +76,4 @@ export {
DEPRECATED_createResponder,
unstable_createFundamental,
unstable_createScope,
jsx,
jsxs,
jsxDEV,
} from './src/React';

View File

@@ -49,8 +49,5 @@ export {
DEPRECATED_createResponder,
// enableScopeAPI
unstable_createScope,
// enableJSXTransformAPI
jsx,
jsxs,
jsxDEV,
} from './src/React';
export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX';

View File

@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export {Fragment, jsxDEV} from './src/jsx/ReactJSX';

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export {Fragment, jsx, jsxs} from './src/jsx/ReactJSX';

View File

@@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-jsx-dev-runtime.production.min.js');
} else {
module.exports = require('./cjs/react-jsx-dev-runtime.development.js');
}

View File

@@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-jsx-runtime.production.min.js');
} else {
module.exports = require('./cjs/react-jsx-runtime.development.js');
}

View File

@@ -14,7 +14,9 @@
"build-info.json",
"index.js",
"cjs/",
"umd/"
"umd/",
"jsx-runtime.js",
"jsx-dev-runtime.js"
],
"main": "index.js",
"repository": {

View File

@@ -24,7 +24,6 @@ import {
createFactory as createFactoryProd,
cloneElement as cloneElementProd,
isValidElement,
jsx as jsxProd,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
@@ -52,9 +51,6 @@ import {
createElementWithValidation,
createFactoryWithValidation,
cloneElementWithValidation,
jsxWithValidation,
jsxWithValidationStatic,
jsxWithValidationDynamic,
} from './ReactElementValidator';
import createMutableSource from './createMutableSource';
import ReactSharedInternals from './ReactSharedInternals';
@@ -67,12 +63,6 @@ const createElement = __DEV__ ? createElementWithValidation : createElementProd;
const cloneElement = __DEV__ ? cloneElementWithValidation : cloneElementProd;
const createFactory = __DEV__ ? createFactoryWithValidation : createFactoryProd;
const jsxDEV = __DEV__ ? jsxWithValidation : undefined;
const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd;
// we may want to special case jsxs internally to take advantage of static children.
// for now we can ship identical prod functions
const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd;
const Children = {
map,
forEach,
@@ -127,9 +117,4 @@ export {
createFundamental as unstable_createFundamental,
// enableScopeAPI
createScope as unstable_createScope,
// enableJSXTransformAPI
jsx,
jsxs,
// TODO: jsxDEV should not be exposed as a name. We might want to move it to a different entry point.
jsxDEV,
};

View File

@@ -23,6 +23,13 @@ export function setCurrentlyValidatingElement(element: null | ReactElement) {
}
if (__DEV__) {
ReactDebugCurrentFrame.setCurrentlyValidatingElement = function(
element: null | ReactElement,
) {
if (__DEV__) {
currentlyValidatingElement = element;
}
};
// Stack implementation injected by the current renderer.
ReactDebugCurrentFrame.getCurrentStack = (null: null | (() => string));

View File

@@ -12,6 +12,8 @@
let React;
let ReactDOM;
let ReactTestUtils;
let JSXRuntime;
let JSXDEVRuntime;
// NOTE: We're explicitly not using JSX here. This is intended to test
// a new React.jsx api which does not have a JSX transformer yet.
@@ -29,6 +31,8 @@ describe('ReactElement.jsx', () => {
global.Symbol = undefined;
React = require('react');
JSXRuntime = require('react/jsx-runtime');
JSXDEVRuntime = require('react/jsx-dev-runtime');
ReactDOM = require('react-dom');
ReactTestUtils = require('react-dom/test-utils');
});
@@ -37,32 +41,35 @@ describe('ReactElement.jsx', () => {
global.Symbol = originalSymbol;
});
if (!__EXPERIMENTAL__) {
it("empty test so Jest doesn't complain", () => {});
return;
}
it('allows static methods to be called using the type property', () => {
class StaticMethodComponentClass extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
StaticMethodComponentClass.someStaticMethod = () => 'someReturnValue';
const element = React.jsx(StaticMethodComponentClass, {});
const element = JSXRuntime.jsx(StaticMethodComponentClass, {});
expect(element.type.someStaticMethod()).toBe('someReturnValue');
});
it('identifies valid elements', () => {
class Component extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
expect(
React.isValidElement(JSXRuntime.jsx(JSXRuntime.Fragment, {})),
).toEqual(true);
if (__DEV__) {
expect(React.isValidElement(JSXDEVRuntime.jsxDEV('div', {}))).toEqual(
true,
);
}
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
@@ -83,12 +90,12 @@ describe('ReactElement.jsx', () => {
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(React.jsx('div', {}));
const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(true);
});
it('is indistinguishable from a plain object', () => {
const element = React.jsx('div', {className: 'foo'});
const element = JSXRuntime.jsx('div', {className: 'foo'});
const object = {};
expect(element.constructor).toBe(object.constructor);
});
@@ -96,37 +103,37 @@ describe('ReactElement.jsx', () => {
it('should use default prop value when removing a prop', () => {
class Component extends React.Component {
render() {
return React.jsx('span', {});
return JSXRuntime.jsx('span', {});
}
}
Component.defaultProps = {fruit: 'persimmon'};
const container = document.createElement('div');
const instance = ReactDOM.render(
React.jsx(Component, {fruit: 'mango'}),
JSXRuntime.jsx(Component, {fruit: 'mango'}),
container,
);
expect(instance.props.fruit).toBe('mango');
ReactDOM.render(React.jsx(Component, {}), container);
ReactDOM.render(JSXRuntime.jsx(Component, {}), container);
expect(instance.props.fruit).toBe('persimmon');
});
it('should normalize props with default values', () => {
class Component extends React.Component {
render() {
return React.jsx('span', {children: this.props.prop});
return JSXRuntime.jsx('span', {children: this.props.prop});
}
}
Component.defaultProps = {prop: 'testKey'};
const instance = ReactTestUtils.renderIntoDocument(
React.jsx(Component, {}),
JSXRuntime.jsx(Component, {}),
);
expect(instance.props.prop).toBe('testKey');
const inst2 = ReactTestUtils.renderIntoDocument(
React.jsx(Component, {prop: null}),
JSXRuntime.jsx(Component, {prop: null}),
);
expect(inst2.props.prop).toBe(null);
});
@@ -134,7 +141,7 @@ describe('ReactElement.jsx', () => {
it('throws when changing a prop (in dev) after element creation', () => {
class Outer extends React.Component {
render() {
const el = React.jsx('div', {className: 'moo'});
const el = JSXRuntime.jsx('div', {className: 'moo'});
if (__DEV__) {
expect(function() {
@@ -150,7 +157,7 @@ describe('ReactElement.jsx', () => {
}
}
const outer = ReactTestUtils.renderIntoDocument(
React.jsx(Outer, {color: 'orange'}),
JSXRuntime.jsx(Outer, {color: 'orange'}),
);
if (__DEV__) {
expect(ReactDOM.findDOMNode(outer).className).toBe('moo');
@@ -163,7 +170,7 @@ describe('ReactElement.jsx', () => {
const container = document.createElement('div');
class Outer extends React.Component {
render() {
const el = React.jsx('div', {children: this.props.sound});
const el = JSXRuntime.jsx('div', {children: this.props.sound});
if (__DEV__) {
expect(function() {
@@ -179,7 +186,7 @@ describe('ReactElement.jsx', () => {
}
}
Outer.defaultProps = {sound: 'meow'};
const outer = ReactDOM.render(React.jsx(Outer, {}), container);
const outer = ReactDOM.render(JSXRuntime.jsx(Outer, {}), container);
expect(ReactDOM.findDOMNode(outer).textContent).toBe('meow');
if (__DEV__) {
expect(ReactDOM.findDOMNode(outer).className).toBe('');
@@ -191,11 +198,11 @@ describe('ReactElement.jsx', () => {
it('does not warn for NaN props', () => {
class Test extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
const test = ReactTestUtils.renderIntoDocument(
React.jsx(Test, {value: +undefined}),
JSXRuntime.jsx(Test, {value: +undefined}),
);
expect(test.props.value).toBeNaN();
});
@@ -204,21 +211,23 @@ describe('ReactElement.jsx', () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return React.jsx('div', {children: this.props.key});
return JSXRuntime.jsx('div', {children: this.props.key});
}
}
class Parent extends React.Component {
render() {
return React.jsxs('div', {
return JSXRuntime.jsxs('div', {
children: [
React.jsx(Child, {}, '0'),
React.jsx(Child, {}, '1'),
React.jsx(Child, {}, '2'),
JSXRuntime.jsx(Child, {}, '0'),
JSXRuntime.jsx(Child, {}, '1'),
JSXRuntime.jsx(Child, {}, '2'),
],
});
}
}
expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
expect(() =>
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
).toErrorDev(
'Child: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
@@ -229,7 +238,10 @@ describe('ReactElement.jsx', () => {
it('warns when a jsxs is passed something that is not an array', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(React.jsxs('div', {children: 'foo'}, null), container),
ReactDOM.render(
JSXRuntime.jsxs('div', {children: 'foo'}, null),
container,
),
).toErrorDev(
'React.jsx: Static children should always be an array. ' +
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
@@ -239,7 +251,7 @@ describe('ReactElement.jsx', () => {
});
it('should warn when `key` is being accessed on a host element', () => {
const element = React.jsxs('div', {}, '3');
const element = JSXRuntime.jsxs('div', {}, '3');
expect(
() => void element.props.key,
).toErrorDev(
@@ -255,17 +267,19 @@ describe('ReactElement.jsx', () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return React.jsx('div', {children: this.props.ref});
return JSXRuntime.jsx('div', {children: this.props.ref});
}
}
class Parent extends React.Component {
render() {
return React.jsx('div', {
children: React.jsx(Child, {ref: 'childElement'}),
return JSXRuntime.jsx('div', {
children: JSXRuntime.jsx(Child, {ref: 'childElement'}),
});
}
}
expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
expect(() =>
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
).toErrorDev(
'Child: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
@@ -292,15 +306,16 @@ describe('ReactElement.jsx', () => {
jest.resetModules();
React = require('react');
JSXRuntime = require('react/jsx-runtime');
class Component extends React.Component {
render() {
return React.jsx('div');
return JSXRuntime.jsx('div');
}
}
expect(React.isValidElement(React.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(React.jsx(Component, {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx('div', {}))).toEqual(true);
expect(React.isValidElement(JSXRuntime.jsx(Component, {}))).toEqual(true);
expect(React.isValidElement(null)).toEqual(false);
expect(React.isValidElement(true)).toEqual(false);
@@ -321,29 +336,32 @@ describe('ReactElement.jsx', () => {
expect(React.isValidElement(Component)).toEqual(false);
expect(React.isValidElement({type: 'div', props: {}})).toEqual(false);
const jsonElement = JSON.stringify(React.jsx('div', {}));
const jsonElement = JSON.stringify(JSXRuntime.jsx('div', {}));
expect(React.isValidElement(JSON.parse(jsonElement))).toBe(false);
});
it('should warn when unkeyed children are passed to jsx', () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
class Parent extends React.Component {
render() {
return React.jsx('div', {
return JSXRuntime.jsx('div', {
children: [
React.jsx(Child, {}),
React.jsx(Child, {}),
React.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
],
});
}
}
expect(() => ReactDOM.render(React.jsx(Parent, {}), container)).toErrorDev(
expect(() =>
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
).toErrorDev(
'Warning: Each child in a list should have a unique "key" prop.\n\n' +
'Check the render method of `Parent`. See https://fb.me/react-warning-keys for more information.\n' +
' in Child (created by Parent)\n' +
@@ -356,18 +374,18 @@ describe('ReactElement.jsx', () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
class Parent extends React.Component {
render() {
return React.jsx('div', {
children: [React.jsx(Child, {key: '0'})],
return JSXRuntime.jsx('div', {
children: [JSXRuntime.jsx(Child, {key: '0'})],
});
}
}
expect(() =>
ReactDOM.render(React.jsx(Parent, {}), container),
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container),
).toErrorDev(
'Warning: React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
@@ -380,21 +398,21 @@ describe('ReactElement.jsx', () => {
const container = document.createElement('div');
class Child extends React.Component {
render() {
return React.jsx('div', {});
return JSXRuntime.jsx('div', {});
}
}
class Parent extends React.Component {
render() {
return React.jsxs('div', {
return JSXRuntime.jsxs('div', {
children: [
React.jsx(Child, {}),
React.jsx(Child, {}),
React.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
JSXRuntime.jsx(Child, {}),
],
});
}
}
// TODO: an explicit expect for no warning?
ReactDOM.render(React.jsx(Parent, {}), container);
ReactDOM.render(JSXRuntime.jsx(Parent, {}), container);
});
});

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
import {
jsxWithValidationStatic,
jsxWithValidationDynamic,
jsxWithValidation,
} from './ReactJSXElementValidator';
import {jsx as jsxProd} from './ReactJSXElement';
const jsx = __DEV__ ? jsxWithValidationDynamic : jsxProd;
// we may want to special case jsxs internally to take advantage of static children.
// for now we can ship identical prod functions
const jsxs = __DEV__ ? jsxWithValidationStatic : jsxProd;
const jsxDEV = __DEV__ ? jsxWithValidation : undefined;
export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};

View File

@@ -0,0 +1,345 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import getComponentName from 'shared/getComponentName';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
const hasOwnProperty = Object.prototype.hasOwnProperty;
const RESERVED_PROPS = {
key: true,
ref: true,
__self: true,
__source: true,
};
let specialPropKeyWarningShown;
let specialPropRefWarningShown;
let didWarnAboutStringRefs;
if (__DEV__) {
didWarnAboutStringRefs = {};
}
function hasValidRef(config) {
if (__DEV__) {
if (hasOwnProperty.call(config, 'ref')) {
const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}
function hasValidKey(config) {
if (__DEV__) {
if (hasOwnProperty.call(config, 'key')) {
const getter = Object.getOwnPropertyDescriptor(config, 'key').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.key !== undefined;
}
function warnIfStringRefCannotBeAutoConverted(config) {
if (__DEV__) {
if (
typeof config.ref === 'string' &&
ReactCurrentOwner.current &&
config.__self &&
ReactCurrentOwner.current.stateNode !== config.__self
) {
const componentName = getComponentName(ReactCurrentOwner.current.type);
if (!didWarnAboutStringRefs[componentName]) {
console.error(
'Component "%s" contains the string ref "%s". ' +
'Support for string refs will be removed in a future major release. ' +
'This case cannot be automatically converted to an arrow function. ' +
'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref',
getComponentName(ReactCurrentOwner.current.type),
config.ref,
);
didWarnAboutStringRefs[componentName] = true;
}
}
}
}
function defineKeyPropWarningGetter(props, displayName) {
if (__DEV__) {
const warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) {
specialPropKeyWarningShown = true;
console.error(
'%s: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
displayName,
);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true,
});
}
}
function defineRefPropWarningGetter(props, displayName) {
if (__DEV__) {
const warnAboutAccessingRef = function() {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
console.error(
'%s: `ref` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
displayName,
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, 'ref', {
get: warnAboutAccessingRef,
configurable: true,
});
}
}
/**
* Factory method to create a new React element. This no longer adheres to
* the class pattern, so do not use new to call it. Also, instanceof check
* will not work. Instead test $$typeof field against Symbol.for('react.element') to check
* 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.
* @internal
*/
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// The validation flag is currently mutative. We put it on
// an external backing store so that we can freeze the whole object.
// This can be replaced with a WeakMap once they are implemented in
// commonly used development environments.
element._store = {};
// To make comparing ReactElements easier for testing purposes, we make
// the validation flag non-enumerable (where possible, which should
// include every environment we run tests in), so the test framework
// ignores it.
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
// Two elements created in two different places should be considered
// equal for testing purposes and therefore we hide it from enumeration.
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
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;
// Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
// or <div key="Hi" {...props} /> ). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except
// <div {...props} key="Hi" />, because we aren't currently able to tell if
// key is explicitly declared to be undefined or not.
if (maybeKey !== undefined) {
key = '' + maybeKey;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
}
// 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];
}
}
// 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) {
if (__DEV__) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
// Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie. <div {...props} key="Hi" />
// or <div key="Hi" {...props} /> ). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except
// <div {...props} key="Hi" />, because we aren't currently able to tell if
// key is explicitly declared to be undefined or not.
if (maybeKey !== undefined) {
key = '' + maybeKey;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
if (hasValidRef(config)) {
ref = config.ref;
warnIfStringRefCannotBeAutoConverted(config);
}
// 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];
}
}
// 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,
);
}
}

View File

@@ -0,0 +1,407 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* ReactElementValidator provides a wrapper around a element factory
* which validates the props passed to the element. This is intended to be
* used only in DEV and could be replaced by a static type checker for languages
* that support it.
*/
import isValidElementType from 'shared/isValidElementType';
import getComponentName from 'shared/getComponentName';
import checkPropTypes from 'shared/checkPropTypes';
import {
getIteratorFn,
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_FRAGMENT_TYPE,
REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';
import {warnAboutSpreadingKeyToJSX} from 'shared/ReactFeatureFlags';
import {jsxDEV} from './ReactJSXElement';
import ReactSharedInternals from 'shared/ReactSharedInternals';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
let propTypesMisspellWarningShown;
if (__DEV__) {
propTypesMisspellWarningShown = false;
}
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Verifies the object is a ReactElement.
* See https://reactjs.org/docs/react-api.html#isvalidelement
* @param {?object} object
* @return {boolean} True if `object` is a ReactElement.
* @final
*/
export function isValidElement(object) {
if (__DEV__) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}
}
function getDeclarationErrorAddendum() {
if (__DEV__) {
if (ReactCurrentOwner.current) {
const name = getComponentName(ReactCurrentOwner.current.type);
if (name) {
return '\n\nCheck the render method of `' + name + '`.';
}
}
return '';
}
}
function getSourceInfoErrorAddendum(source) {
if (__DEV__) {
if (source !== undefined) {
const fileName = source.fileName.replace(/^.*[\\\/]/, '');
const lineNumber = source.lineNumber;
return '\n\nCheck your code at ' + fileName + ':' + lineNumber + '.';
}
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
* updates.
*/
const ownerHasKeyUseWarning = {};
function getCurrentComponentErrorInfo(parentType) {
if (__DEV__) {
let info = getDeclarationErrorAddendum();
if (!info) {
const parentName =
typeof parentType === 'string'
? parentType
: parentType.displayName || parentType.name;
if (parentName) {
info = `\n\nCheck the top-level render call using <${parentName}>.`;
}
}
return info;
}
}
/**
* Warn if the element doesn't have an explicit key assigned to it.
* This element is in an array. The array could grow and shrink or be
* reordered. All children that haven't already been validated are required to
* have a "key" property assigned to it. Error statuses are cached so a warning
* will only be shown once.
*
* @internal
* @param {ReactElement} element Element that requires a key.
* @param {*} parentType element's parent's type.
*/
function validateExplicitKey(element, parentType) {
if (__DEV__) {
if (!element._store || element._store.validated || element.key != null) {
return;
}
element._store.validated = true;
const currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType);
if (ownerHasKeyUseWarning[currentComponentErrorInfo]) {
return;
}
ownerHasKeyUseWarning[currentComponentErrorInfo] = true;
// Usually the current owner is the offender, but if it accepts children as a
// property, it may be the creator of the child that's responsible for
// assigning it a key.
let childOwner = '';
if (
element &&
element._owner &&
element._owner !== ReactCurrentOwner.current
) {
// Give the component that originally created this child.
childOwner = ` It was passed a child from ${getComponentName(
element._owner.type,
)}.`;
}
ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
console.error(
'Each child in a list should have a unique "key" prop.' +
'%s%s See https://fb.me/react-warning-keys for more information.',
currentComponentErrorInfo,
childOwner,
);
ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
}
}
/**
* Ensure that every element either is passed in a static location, in an
* array with an explicit keys property defined, or in an object literal
* with valid key property.
*
* @internal
* @param {ReactNode} node Statically passed child of any type.
* @param {*} parentType node's parent's type.
*/
function validateChildKeys(node, parentType) {
if (__DEV__) {
if (typeof node !== 'object') {
return;
}
if (Array.isArray(node)) {
for (let i = 0; i < node.length; i++) {
const child = node[i];
if (isValidElement(child)) {
validateExplicitKey(child, parentType);
}
}
} else if (isValidElement(node)) {
// This element was passed in a valid location.
if (node._store) {
node._store.validated = true;
}
} else if (node) {
const iteratorFn = getIteratorFn(node);
if (typeof iteratorFn === 'function') {
// Entry iterators used to provide implicit keys,
// but now we print a separate warning for them later.
if (iteratorFn !== node.entries) {
const iterator = iteratorFn.call(node);
let step;
while (!(step = iterator.next()).done) {
if (isValidElement(step.value)) {
validateExplicitKey(step.value, parentType);
}
}
}
}
}
}
}
/**
* Given an element, validate that its props follow the propTypes definition,
* provided by the type.
*
* @param {ReactElement} element
*/
function validatePropTypes(element) {
if (__DEV__) {
const type = element.type;
if (type === null || type === undefined || typeof type === 'string') {
return;
}
const name = getComponentName(type);
let propTypes;
if (typeof type === 'function') {
propTypes = type.propTypes;
} else if (
typeof type === 'object' &&
(type.$$typeof === REACT_FORWARD_REF_TYPE ||
// Note: Memo only checks outer props here.
// Inner props are checked in the reconciler.
type.$$typeof === REACT_MEMO_TYPE)
) {
propTypes = type.propTypes;
} else {
return;
}
if (propTypes) {
ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
checkPropTypes(propTypes, element.props, 'prop', name);
ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
console.error(
'Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?',
name || 'Unknown',
);
}
if (
typeof type.getDefaultProps === 'function' &&
!type.getDefaultProps.isReactClassApproved
) {
console.error(
'getDefaultProps is only used on classic React.createClass ' +
'definitions. Use a static property named `defaultProps` instead.',
);
}
}
}
/**
* Given a fragment, validate that it can only be provided with fragment props
* @param {ReactElement} fragment
*/
function validateFragmentProps(fragment) {
if (__DEV__) {
ReactDebugCurrentFrame.setCurrentlyValidatingElement(fragment);
const keys = Object.keys(fragment.props);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key !== 'children' && key !== 'key') {
console.error(
'Invalid prop `%s` supplied to `React.Fragment`. ' +
'React.Fragment can only have `key` and `children` props.',
key,
);
break;
}
}
if (fragment.ref !== null) {
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
}
ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
}
}
export function jsxWithValidation(
type,
props,
key,
isStaticChildren,
source,
self,
) {
if (__DEV__) {
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;
}
console.error(
'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) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
validateChildKeys(children[i], type);
}
if (Object.freeze) {
Object.freeze(children);
}
} else {
console.error(
'React.jsx: Static children should always be an array. ' +
'You are likely explicitly calling React.jsxs or React.jsxDEV. ' +
'Use the Babel transform instead.',
);
}
} else {
validateChildKeys(children, type);
}
}
}
if (warnAboutSpreadingKeyToJSX) {
if (hasOwnProperty.call(props, 'key')) {
console.error(
'React.jsx: Spreading a key to JSX is a deprecated pattern. ' +
'Explicitly pass a key after spreading props in your JSX call. ' +
'E.g. <%s {...props} key={key} />',
getComponentName(type) || 'ComponentName',
);
}
}
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) {
if (__DEV__) {
return jsxWithValidation(type, props, key, true);
}
}
export function jsxWithValidationDynamic(type, props, key) {
if (__DEV__) {
return jsxWithValidation(type, props, key, false);
}
}

View File

@@ -76,6 +76,38 @@ const bundles = [
externals: [],
},
/******* React JSX Runtime *******/
{
bundleTypes: [
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
],
moduleType: ISOMORPHIC,
entry: 'react/jsx-runtime',
global: 'JSXRuntime',
externals: ['react'],
},
/******* React JSX DEV Runtime *******/
{
bundleTypes: [
NODE_DEV,
NODE_PROD,
NODE_PROFILING,
FB_WWW_DEV,
FB_WWW_PROD,
FB_WWW_PROFILING,
],
moduleType: ISOMORPHIC,
entry: 'react/jsx-dev-runtime',
global: 'JSXDEVRuntime',
externals: ['react'],
},
/******* React DOM *******/
{
bundleTypes: [

View File

@@ -58,7 +58,7 @@ const forks = Object.freeze({
if (entry === 'react') {
return 'react/src/ReactSharedInternals';
}
if (dependencies.indexOf('react') === -1) {
if (!entry.startsWith('react/') && dependencies.indexOf('react') === -1) {
// React internals are unavailable if we can't reference the package.
// We return an error because we only want to throw if this module gets used.
return new Error(

View File

@@ -14,6 +14,7 @@ const importSideEffects = Object.freeze({
scheduler: HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'scheduler/tracing': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'react-dom/server': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
'react/jsx-dev-runtime': HAS_NO_SIDE_EFFECTS_ON_IMPORT,
});
// Bundles exporting globals that other modules rely on.