From ec0e8b01702a58bcad8cab683dacac7c6f9f2c73 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 15 Jan 2024 12:11:37 -0500 Subject: [PATCH 1/3] Move createElement resolution out of entry module The implementation of createElement is different in production and development. This moves the DEV check out of the entry module and into the ReactElement module. There was already a TODO comment for this; I'm doing it now because I'm about to refactor the server entry module (ReactServer.js) to import createElement directly instead of going through the client entry module (React.js, which I'll also probably rename to ReactClient.js). --- packages/react/src/React.js | 23 +- packages/react/src/ReactElement.js | 593 +------------------- packages/react/src/ReactElementProd.js | 573 +++++++++++++++++++ packages/react/src/ReactElementValidator.js | 2 +- 4 files changed, 604 insertions(+), 587 deletions(-) create mode 100644 packages/react/src/ReactElementProd.js diff --git a/packages/react/src/React.js b/packages/react/src/React.js index 5728a2ba50d78..5aaae5aaf8e37 100644 --- a/packages/react/src/React.js +++ b/packages/react/src/React.js @@ -26,9 +26,9 @@ import {Component, PureComponent} from './ReactBaseClasses'; import {createRef} from './ReactCreateRef'; import {forEach, map, count, toArray, only} from './ReactChildren'; import { - createElement as createElementProd, - createFactory as createFactoryProd, - cloneElement as cloneElementProd, + createElement, + createFactory, + cloneElement, isValidElement, } from './ReactElement'; import {createContext} from './ReactContext'; @@ -61,27 +61,12 @@ import { useMemoCache, useOptimistic, } from './ReactHooks'; -import { - createElementWithValidation, - createFactoryWithValidation, - cloneElementWithValidation, -} from './ReactElementValidator'; + import {createServerContext} from './ReactServerContext'; import ReactSharedInternals from './ReactSharedInternalsClient'; import {startTransition} from './ReactStartTransition'; import {act} from './ReactAct'; -// TODO: Move this branching into the other module instead and just re-export. -const createElement: any = __DEV__ - ? createElementWithValidation - : createElementProd; -const cloneElement: any = __DEV__ - ? cloneElementWithValidation - : cloneElementProd; -const createFactory: any = __DEV__ - ? createFactoryWithValidation - : createFactoryProd; - const Children = { map, forEach, diff --git a/packages/react/src/ReactElement.js b/packages/react/src/ReactElement.js index e9d721b92693a..ab3df9600ab86 100644 --- a/packages/react/src/ReactElement.js +++ b/packages/react/src/ReactElement.js @@ -3,571 +3,30 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */ - -import getComponentNameFromType from 'shared/getComponentNameFromType'; -import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; -import assign from 'shared/assign'; -import hasOwnProperty from 'shared/hasOwnProperty'; -import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; - -import ReactCurrentOwner from './ReactCurrentOwner'; - -const RESERVED_PROPS = { - key: true, - ref: true, - __self: true, - __source: true, -}; - -let specialPropKeyWarningShown, - specialPropRefWarningShown, - 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 defineKeyPropWarningGetter(props, displayName) { - const warnAboutAccessingKey = function () { - if (__DEV__) { - 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://reactjs.org/link/special-props)', - displayName, - ); - } - } - }; - warnAboutAccessingKey.isReactWarning = true; - Object.defineProperty(props, 'key', { - get: warnAboutAccessingKey, - configurable: true, - }); -} - -function defineRefPropWarningGetter(props, displayName) { - const warnAboutAccessingRef = function () { - if (__DEV__) { - 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://reactjs.org/link/special-props)', - displayName, - ); - } - } - }; - warnAboutAccessingRef.isReactWarning = true; - Object.defineProperty(props, 'ref', { - get: warnAboutAccessingRef, - configurable: true, - }); -} - -function warnIfStringRefCannotBeAutoConverted(config) { - if (__DEV__) { - if ( - typeof config.ref === 'string' && - ReactCurrentOwner.current && - config.__self && - ReactCurrentOwner.current.stateNode !== config.__self - ) { - const componentName = getComponentNameFromType( - 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://reactjs.org/link/strict-mode-string-ref', - componentName, - config.ref, - ); - didWarnAboutStringRefs[componentName] = 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 - */ -function ReactElement(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.
- // or
). We want to deprecate key spread, - // but as an intermediary step, we will use jsxDEV for everything except - //
, because we aren't currently able to tell if - // key is explicitly declared to be undefined or not. - if (maybeKey !== undefined) { - if (__DEV__) { - checkKeyStringCoercion(maybeKey); - } - key = '' + maybeKey; - } - - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - 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) { - 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.
- // or
). We want to deprecate key spread, - // but as an intermediary step, we will use jsxDEV for everything except - //
, because we aren't currently able to tell if - // key is explicitly declared to be undefined or not. - if (maybeKey !== undefined) { - if (__DEV__) { - checkKeyStringCoercion(maybeKey); - } - key = '' + maybeKey; - } - - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - 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, - ); -} - -/** - * Create and return a new ReactElement of the given type. - * See https://reactjs.org/docs/react-api.html#createelement - */ -export function createElement(type, config, children) { - let propName; - - // Reserved names are extracted - const props = {}; - - let key = null; - let ref = null; - let self = null; - let source = null; - - if (config != null) { - if (hasValidRef(config)) { - ref = config.ref; - - if (__DEV__) { - warnIfStringRefCannotBeAutoConverted(config); - } - } - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - key = '' + config.key; - } - - self = config.__self === undefined ? null : config.__self; - source = config.__source === undefined ? null : config.__source; - // 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]; - } - } - } - - // Children can be more than one argument, and those are transferred onto - // the newly allocated props object. - const childrenLength = arguments.length - 2; - if (childrenLength === 1) { - props.children = children; - } else if (childrenLength > 1) { - const childArray = Array(childrenLength); - for (let i = 0; i < childrenLength; i++) { - childArray[i] = arguments[i + 2]; - } - if (__DEV__) { - if (Object.freeze) { - Object.freeze(childArray); - } - } - props.children = childArray; - } - - // Resolve default props - if (type && type.defaultProps) { - const defaultProps = type.defaultProps; - for (propName in defaultProps) { - if (props[propName] === undefined) { - props[propName] = defaultProps[propName]; - } - } - } - if (__DEV__) { - 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, - ); -} - -/** - * Return a function that produces ReactElements of a given type. - * See https://reactjs.org/docs/react-api.html#createfactory - */ -export function createFactory(type) { - const factory = createElement.bind(null, type); - // Expose the type on the factory and the prototype so that it can be - // easily accessed on elements. E.g. `.type === Foo`. - // This should not be named `constructor` since this may not be the function - // that created the element, and it may not even be a constructor. - // Legacy hook: remove it - factory.type = type; - return factory; -} - -export function cloneAndReplaceKey(oldElement, newKey) { - const newElement = ReactElement( - oldElement.type, - newKey, - oldElement.ref, - oldElement._self, - oldElement._source, - oldElement._owner, - oldElement.props, - ); - - return newElement; -} - -/** - * Clone and return a new ReactElement using element as the starting point. - * See https://reactjs.org/docs/react-api.html#cloneelement - */ -export function cloneElement(element, config, children) { - if (element === null || element === undefined) { - throw new Error( - `React.cloneElement(...): The argument must be a React element, but you passed ${element}.`, - ); - } - - let propName; - - // Original props are copied - const props = assign({}, element.props); - - // Reserved names are extracted - let key = element.key; - let ref = element.ref; - // Self is preserved since the owner is preserved. - const self = element._self; - // Source is preserved since cloneElement is unlikely to be targeted by a - // transpiler, and the original source is probably a better indicator of the - // true owner. - const source = element._source; - - // Owner will be preserved, unless ref is overridden - let owner = element._owner; - - if (config != null) { - if (hasValidRef(config)) { - // Silently steal the ref from the parent. - ref = config.ref; - owner = ReactCurrentOwner.current; - } - if (hasValidKey(config)) { - if (__DEV__) { - checkKeyStringCoercion(config.key); - } - key = '' + config.key; - } - - // Remaining properties override existing props - let defaultProps; - if (element.type && element.type.defaultProps) { - defaultProps = element.type.defaultProps; - } - for (propName in config) { - if ( - hasOwnProperty.call(config, propName) && - !RESERVED_PROPS.hasOwnProperty(propName) - ) { - if (config[propName] === undefined && defaultProps !== undefined) { - // Resolve default props - props[propName] = defaultProps[propName]; - } else { - props[propName] = config[propName]; - } - } - } - } - - // Children can be more than one argument, and those are transferred onto - // the newly allocated props object. - const childrenLength = arguments.length - 2; - if (childrenLength === 1) { - props.children = children; - } else if (childrenLength > 1) { - const childArray = Array(childrenLength); - for (let i = 0; i < childrenLength; i++) { - childArray[i] = arguments[i + 2]; - } - props.children = childArray; - } - - return ReactElement(element.type, key, ref, self, source, owner, props); -} - -/** - * 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) { - return ( - typeof object === 'object' && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE - ); -} + * @flow + */ + +import { + createElement as createElementProd, + createFactory as createFactoryProd, + cloneElement as cloneElementProd, +} from './ReactElementProd'; + +import { + createElementWithValidation, + createFactoryWithValidation, + cloneElementWithValidation, +} from './ReactElementValidator'; + +export {isValidElement, cloneAndReplaceKey} from './ReactElementProd'; + +export const createElement: any = __DEV__ + ? createElementWithValidation + : createElementProd; +export const cloneElement: any = __DEV__ + ? cloneElementWithValidation + : cloneElementProd; +export const createFactory: any = __DEV__ + ? createFactoryWithValidation + : createFactoryProd; diff --git a/packages/react/src/ReactElementProd.js b/packages/react/src/ReactElementProd.js new file mode 100644 index 0000000000000..e9d721b92693a --- /dev/null +++ b/packages/react/src/ReactElementProd.js @@ -0,0 +1,573 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import getComponentNameFromType from 'shared/getComponentNameFromType'; +import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; +import assign from 'shared/assign'; +import hasOwnProperty from 'shared/hasOwnProperty'; +import {checkKeyStringCoercion} from 'shared/CheckStringCoercion'; + +import ReactCurrentOwner from './ReactCurrentOwner'; + +const RESERVED_PROPS = { + key: true, + ref: true, + __self: true, + __source: true, +}; + +let specialPropKeyWarningShown, + specialPropRefWarningShown, + 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 defineKeyPropWarningGetter(props, displayName) { + const warnAboutAccessingKey = function () { + if (__DEV__) { + 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://reactjs.org/link/special-props)', + displayName, + ); + } + } + }; + warnAboutAccessingKey.isReactWarning = true; + Object.defineProperty(props, 'key', { + get: warnAboutAccessingKey, + configurable: true, + }); +} + +function defineRefPropWarningGetter(props, displayName) { + const warnAboutAccessingRef = function () { + if (__DEV__) { + 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://reactjs.org/link/special-props)', + displayName, + ); + } + } + }; + warnAboutAccessingRef.isReactWarning = true; + Object.defineProperty(props, 'ref', { + get: warnAboutAccessingRef, + configurable: true, + }); +} + +function warnIfStringRefCannotBeAutoConverted(config) { + if (__DEV__) { + if ( + typeof config.ref === 'string' && + ReactCurrentOwner.current && + config.__self && + ReactCurrentOwner.current.stateNode !== config.__self + ) { + const componentName = getComponentNameFromType( + 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://reactjs.org/link/strict-mode-string-ref', + componentName, + config.ref, + ); + didWarnAboutStringRefs[componentName] = 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 + */ +function ReactElement(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.
+ // or
). We want to deprecate key spread, + // but as an intermediary step, we will use jsxDEV for everything except + //
, because we aren't currently able to tell if + // key is explicitly declared to be undefined or not. + if (maybeKey !== undefined) { + if (__DEV__) { + checkKeyStringCoercion(maybeKey); + } + key = '' + maybeKey; + } + + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + 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) { + 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.
+ // or
). We want to deprecate key spread, + // but as an intermediary step, we will use jsxDEV for everything except + //
, because we aren't currently able to tell if + // key is explicitly declared to be undefined or not. + if (maybeKey !== undefined) { + if (__DEV__) { + checkKeyStringCoercion(maybeKey); + } + key = '' + maybeKey; + } + + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + 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, + ); +} + +/** + * Create and return a new ReactElement of the given type. + * See https://reactjs.org/docs/react-api.html#createelement + */ +export function createElement(type, config, children) { + let propName; + + // Reserved names are extracted + const props = {}; + + let key = null; + let ref = null; + let self = null; + let source = null; + + if (config != null) { + if (hasValidRef(config)) { + ref = config.ref; + + if (__DEV__) { + warnIfStringRefCannotBeAutoConverted(config); + } + } + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + key = '' + config.key; + } + + self = config.__self === undefined ? null : config.__self; + source = config.__source === undefined ? null : config.__source; + // 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]; + } + } + } + + // Children can be more than one argument, and those are transferred onto + // the newly allocated props object. + const childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + const childArray = Array(childrenLength); + for (let i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + if (__DEV__) { + if (Object.freeze) { + Object.freeze(childArray); + } + } + props.children = childArray; + } + + // Resolve default props + if (type && type.defaultProps) { + const defaultProps = type.defaultProps; + for (propName in defaultProps) { + if (props[propName] === undefined) { + props[propName] = defaultProps[propName]; + } + } + } + if (__DEV__) { + 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, + ); +} + +/** + * Return a function that produces ReactElements of a given type. + * See https://reactjs.org/docs/react-api.html#createfactory + */ +export function createFactory(type) { + const factory = createElement.bind(null, type); + // Expose the type on the factory and the prototype so that it can be + // easily accessed on elements. E.g. `.type === Foo`. + // This should not be named `constructor` since this may not be the function + // that created the element, and it may not even be a constructor. + // Legacy hook: remove it + factory.type = type; + return factory; +} + +export function cloneAndReplaceKey(oldElement, newKey) { + const newElement = ReactElement( + oldElement.type, + newKey, + oldElement.ref, + oldElement._self, + oldElement._source, + oldElement._owner, + oldElement.props, + ); + + return newElement; +} + +/** + * Clone and return a new ReactElement using element as the starting point. + * See https://reactjs.org/docs/react-api.html#cloneelement + */ +export function cloneElement(element, config, children) { + if (element === null || element === undefined) { + throw new Error( + `React.cloneElement(...): The argument must be a React element, but you passed ${element}.`, + ); + } + + let propName; + + // Original props are copied + const props = assign({}, element.props); + + // Reserved names are extracted + let key = element.key; + let ref = element.ref; + // Self is preserved since the owner is preserved. + const self = element._self; + // Source is preserved since cloneElement is unlikely to be targeted by a + // transpiler, and the original source is probably a better indicator of the + // true owner. + const source = element._source; + + // Owner will be preserved, unless ref is overridden + let owner = element._owner; + + if (config != null) { + if (hasValidRef(config)) { + // Silently steal the ref from the parent. + ref = config.ref; + owner = ReactCurrentOwner.current; + } + if (hasValidKey(config)) { + if (__DEV__) { + checkKeyStringCoercion(config.key); + } + key = '' + config.key; + } + + // Remaining properties override existing props + let defaultProps; + if (element.type && element.type.defaultProps) { + defaultProps = element.type.defaultProps; + } + for (propName in config) { + if ( + hasOwnProperty.call(config, propName) && + !RESERVED_PROPS.hasOwnProperty(propName) + ) { + if (config[propName] === undefined && defaultProps !== undefined) { + // Resolve default props + props[propName] = defaultProps[propName]; + } else { + props[propName] = config[propName]; + } + } + } + } + + // Children can be more than one argument, and those are transferred onto + // the newly allocated props object. + const childrenLength = arguments.length - 2; + if (childrenLength === 1) { + props.children = children; + } else if (childrenLength > 1) { + const childArray = Array(childrenLength); + for (let i = 0; i < childrenLength; i++) { + childArray[i] = arguments[i + 2]; + } + props.children = childArray; + } + + return ReactElement(element.type, key, ref, self, source, owner, props); +} + +/** + * 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) { + return ( + typeof object === 'object' && + object !== null && + object.$$typeof === REACT_ELEMENT_TYPE + ); +} diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js index 44e9033fd3578..78f8e498d816f 100644 --- a/packages/react/src/ReactElementValidator.js +++ b/packages/react/src/ReactElementValidator.js @@ -30,7 +30,7 @@ import { createElement, cloneElement, jsxDEV, -} from './ReactElement'; +} from './ReactElementProd'; import {setExtraStackFrame} from './ReactDebugCurrentFrame'; import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame'; import hasOwnProperty from 'shared/hasOwnProperty'; From 4a072ac2aef64244727c8fd739bef76e13d5c01c Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 15 Jan 2024 12:32:41 -0500 Subject: [PATCH 2/3] Refactor Server entrypoint to not import Client This refactors the Server Components entrypoint for the `react` package (ReactServer.js) so that it doesn't depend on the client entrypoint (React.js). In the next step, I'll also rename React.js to ReactClient.js to make the separation clearer. This structure will make it easier to add client-only and server- only features. --- .../react/src/ReactServer.experimental.js | 57 +++++++++++++++---- packages/react/src/ReactServer.js | 43 ++++++++++++-- 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/packages/react/src/ReactServer.experimental.js b/packages/react/src/ReactServer.experimental.js index 45baaadc89f7b..330707b7b5992 100644 --- a/packages/react/src/ReactServer.experimental.js +++ b/packages/react/src/ReactServer.experimental.js @@ -14,6 +14,43 @@ export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './R export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals'; +import {forEach, map, count, toArray, only} from './ReactChildren'; +import { + REACT_FRAGMENT_TYPE, + REACT_PROFILER_TYPE, + REACT_STRICT_MODE_TYPE, + REACT_SUSPENSE_TYPE, + REACT_DEBUG_TRACING_MODE_TYPE, +} from 'shared/ReactSymbols'; +import {cloneElement, createElement, isValidElement} from './ReactElement'; +import {createRef} from './ReactCreateRef'; +import {createServerContext} from './ReactServerContext'; +import { + use, + useId, + useCallback, + useContext, + useDebugValue, + useMemo, + getCacheSignal, + getCacheForType, +} from './ReactHooks'; +import {forwardRef} from './ReactForwardRef'; +import {lazy} from './ReactLazy'; +import {memo} from './ReactMemo'; +import {cache} from './ReactCache'; +import {startTransition} from './ReactStartTransition'; +import {postpone} from './ReactPostpone'; +import version from 'shared/ReactVersion'; + +const Children = { + map, + forEach, + count, + toArray, + only, +}; + // These are server-only export { taintUniqueValue as experimental_taintUniqueValue, @@ -22,10 +59,10 @@ export { export { Children, - Fragment, - Profiler, - StrictMode, - Suspense, + REACT_FRAGMENT_TYPE as Fragment, + REACT_PROFILER_TYPE as Profiler, + REACT_STRICT_MODE_TYPE as StrictMode, + REACT_SUSPENSE_TYPE as Suspense, cloneElement, createElement, createRef, @@ -37,15 +74,15 @@ export { memo, cache, startTransition, - unstable_DebugTracingMode, - unstable_SuspenseList, - unstable_getCacheSignal, - unstable_getCacheForType, - unstable_postpone, + REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode, + REACT_SUSPENSE_TYPE as unstable_SuspenseList, + getCacheSignal as unstable_getCacheSignal, + getCacheForType as unstable_getCacheForType, + postpone as unstable_postpone, useId, useCallback, useContext, useDebugValue, useMemo, version, -} from './React'; +}; diff --git a/packages/react/src/ReactServer.js b/packages/react/src/ReactServer.js index 1bc2b3036f455..5631319132fda 100644 --- a/packages/react/src/ReactServer.js +++ b/packages/react/src/ReactServer.js @@ -14,12 +14,45 @@ export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './R export {default as __SECRET_SERVER_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactServerSharedInternals'; +import {forEach, map, count, toArray, only} from './ReactChildren'; +import { + REACT_FRAGMENT_TYPE, + REACT_PROFILER_TYPE, + REACT_STRICT_MODE_TYPE, + REACT_SUSPENSE_TYPE, +} from 'shared/ReactSymbols'; +import {cloneElement, createElement, isValidElement} from './ReactElement'; +import {createRef} from './ReactCreateRef'; +import {createServerContext} from './ReactServerContext'; +import { + use, + useId, + useCallback, + useContext, + useDebugValue, + useMemo, +} from './ReactHooks'; +import {forwardRef} from './ReactForwardRef'; +import {lazy} from './ReactLazy'; +import {memo} from './ReactMemo'; +import {cache} from './ReactCache'; +import {startTransition} from './ReactStartTransition'; +import version from 'shared/ReactVersion'; + +const Children = { + map, + forEach, + count, + toArray, + only, +}; + export { Children, - Fragment, - Profiler, - StrictMode, - Suspense, + REACT_FRAGMENT_TYPE as Fragment, + REACT_PROFILER_TYPE as Profiler, + REACT_STRICT_MODE_TYPE as StrictMode, + REACT_SUSPENSE_TYPE as Suspense, cloneElement, createElement, createRef, @@ -37,4 +70,4 @@ export { useDebugValue, useMemo, version, -} from './React'; +}; From 75703485cb0c58ae487cb50091439fcffc739a89 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Mon, 15 Jan 2024 12:37:53 -0500 Subject: [PATCH 3/3] Rename React.js entrypoint to ReactClient.js The server entrypoint no longer depends on this module. It's now only imported by the client. I've updated the name to reflect that it's client-only, and can include client-only features. --- packages/react/index.classic.fb.js | 2 +- packages/react/index.experimental.js | 4 ++-- packages/react/index.js | 2 +- packages/react/index.modern.fb.js | 2 +- packages/react/index.stable.js | 2 +- packages/react/src/{React.js => ReactClient.js} | 0 6 files changed, 6 insertions(+), 6 deletions(-) rename packages/react/src/{React.js => ReactClient.js} (100%) diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js index 035e5c0aec2d9..a417f3e755cab 100644 --- a/packages/react/index.classic.fb.js +++ b/packages/react/index.classic.fb.js @@ -58,5 +58,5 @@ export { useSyncExternalStore, useTransition, version, -} from './src/React'; +} from './src/ReactClient'; export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX'; diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index bdb26a4c42076..dd86090f093e1 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -57,9 +57,9 @@ export { useSyncExternalStore, useTransition, version, -} from './src/React'; +} from './src/ReactClient'; -import {useOptimistic} from './src/React'; +import {useOptimistic} from './src/ReactClient'; export function experimental_useOptimistic( passthrough: S, diff --git a/packages/react/index.js b/packages/react/index.js index b60e8957c5b0a..abce6537b5675 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -80,4 +80,4 @@ export { useState, useTransition, version, -} from './src/React'; +} from './src/ReactClient'; diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js index 1ef63b605af3a..10ae150f64eef 100644 --- a/packages/react/index.modern.fb.js +++ b/packages/react/index.modern.fb.js @@ -56,5 +56,5 @@ export { useSyncExternalStore, useTransition, version, -} from './src/React'; +} from './src/ReactClient'; export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX'; diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js index b88a83d78b9b2..9f8e46063782a 100644 --- a/packages/react/index.stable.js +++ b/packages/react/index.stable.js @@ -47,4 +47,4 @@ export { useSyncExternalStore, useTransition, version, -} from './src/React'; +} from './src/ReactClient'; diff --git a/packages/react/src/React.js b/packages/react/src/ReactClient.js similarity index 100% rename from packages/react/src/React.js rename to packages/react/src/ReactClient.js