react/packages/react-server/src/ReactFizzHooks.js

573 lines
17 KiB
JavaScript
Raw Normal View History

/**
* 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 type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
import type {
MutableSource,
MutableSourceGetSnapshotFn,
MutableSourceSubscribeFn,
ReactContext,
} from 'shared/ReactTypes';
import type {ResponseState} from './ReactServerFormatConfig';
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
import type {Task} from './ReactFizzServer';
import {readContext as readContextImpl} from './ReactFizzNewContext';
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
import {getTreeId} from './ReactFizzTreeContext';
import {makeId} from './ReactServerFormatConfig';
import {enableCache} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
type Update<A> = {|
action: A,
next: Update<A> | null,
|};
type UpdateQueue<A> = {|
last: Update<A> | null,
dispatch: any,
|};
type Hook = {|
memoizedState: any,
queue: UpdateQueue<any> | null,
next: Hook | null,
|};
let currentlyRenderingComponent: Object | null = null;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
let currentlyRenderingTask: Task | null = null;
let firstWorkInProgressHook: Hook | null = null;
let workInProgressHook: Hook | null = null;
// Whether the work-in-progress hook is a re-rendered hook
let isReRender: boolean = false;
// Whether an update was scheduled during the currently executing render pass.
let didScheduleRenderPhaseUpdate: boolean = false;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
// Counts the number of useId hooks in this component
let localIdCounter: number = 0;
// Lazily created map of render-phase updates
let renderPhaseUpdates: Map<UpdateQueue<any>, Update<any>> | null = null;
// Counter to prevent infinite loops.
let numberOfReRenders: number = 0;
const RE_RENDER_LIMIT = 25;
let isInHookUserCodeInDev = false;
// In DEV, this is the name of the currently executing primitive hook
let currentHookNameInDev: ?string;
function resolveCurrentlyRenderingComponent(): Object {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if (currentlyRenderingComponent === null) {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.',
);
}
if (__DEV__) {
if (isInHookUserCodeInDev) {
console.error(
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
'You can only call Hooks at the top level of your React function. ' +
'For more information, see ' +
'https://reactjs.org/link/rules-of-hooks',
);
}
}
return currentlyRenderingComponent;
}
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
if (prevDeps === null) {
if (__DEV__) {
console.error(
'%s received a final argument during this render, but not during ' +
'the previous render. Even though the final argument is optional, ' +
'its type cannot change between renders.',
currentHookNameInDev,
);
}
return false;
}
if (__DEV__) {
// Don't bother comparing lengths in prod because these arrays should be
// passed inline.
if (nextDeps.length !== prevDeps.length) {
console.error(
'The final argument passed to %s changed size between renders. The ' +
'order and size of this array must remain constant.\n\n' +
'Previous: %s\n' +
'Incoming: %s',
currentHookNameInDev,
`[${nextDeps.join(', ')}]`,
`[${prevDeps.join(', ')}]`,
);
}
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
function createHook(): Hook {
if (numberOfReRenders > 0) {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error('Rendered more hooks than during the previous render');
}
return {
memoizedState: null,
queue: null,
next: null,
};
}
function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
isReRender = false;
firstWorkInProgressHook = workInProgressHook = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
isReRender = false;
// Append to the end of the list
workInProgressHook = workInProgressHook.next = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = workInProgressHook.next;
}
}
return workInProgressHook;
}
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
export function prepareToUseHooks(task: Task, componentIdentity: Object): void {
currentlyRenderingComponent = componentIdentity;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
currentlyRenderingTask = task;
if (__DEV__) {
isInHookUserCodeInDev = false;
}
// The following should have already been reset
// didScheduleRenderPhaseUpdate = false;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
// localIdCounter = 0;
// firstWorkInProgressHook = null;
// numberOfReRenders = 0;
// renderPhaseUpdates = null;
// workInProgressHook = null;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
localIdCounter = 0;
}
export function finishHooks(
Component: any,
props: any,
children: any,
refOrContext: any,
): any {
// This must be called after every function component to prevent hooks from
// being used in classes.
while (didScheduleRenderPhaseUpdate) {
// Updates were scheduled during the render phase. They are stored in
// the `renderPhaseUpdates` map. Call the component again, reusing the
// work-in-progress hooks and applying the additional updates on top. Keep
// restarting until no more updates are scheduled.
didScheduleRenderPhaseUpdate = false;
localIdCounter = 0;
numberOfReRenders += 1;
// Start over from the beginning of the list
workInProgressHook = null;
children = Component(props, refOrContext);
}
resetHooksState();
return children;
}
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
export function checkDidRenderIdHook() {
// This should be called immediately after every finishHooks call.
// Conceptually, it's part of the return value of finishHooks; it's only a
// separate function to avoid using an array tuple.
const didRenderIdHook = localIdCounter !== 0;
return didRenderIdHook;
}
// Reset the internal hooks state if an error occurs while rendering a component
export function resetHooksState(): void {
if (__DEV__) {
isInHookUserCodeInDev = false;
}
currentlyRenderingComponent = null;
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
currentlyRenderingTask = null;
didScheduleRenderPhaseUpdate = false;
firstWorkInProgressHook = null;
numberOfReRenders = 0;
renderPhaseUpdates = null;
workInProgressHook = null;
}
function getCacheForType<T>(resourceType: () => T): T {
2021-10-23 00:44:31 +08:00
// TODO: This should silently mark this as client rendered since it's not necessarily
// considered an error. It needs to work for things like Flight though.
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error('Not implemented.');
}
function readContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
if (isInHookUserCodeInDev) {
console.error(
'Context can only be read while React is rendering. ' +
'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
'In function components, you can read it directly in the function body, but not ' +
'inside Hooks like useReducer() or useMemo().',
);
}
}
return readContextImpl(context);
}
function useContext<T>(context: ReactContext<T>): T {
if (__DEV__) {
currentHookNameInDev = 'useContext';
}
resolveCurrentlyRenderingComponent();
return readContextImpl(context);
}
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
if (__DEV__) {
currentHookNameInDev = 'useState';
}
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
if (__DEV__) {
if (reducer !== basicStateReducer) {
currentHookNameInDev = 'useReducer';
}
}
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
if (isReRender) {
// This is a re-render. Apply the new render phase updates to the previous
// current hook.
const queue: UpdateQueue<A> = (workInProgressHook.queue: any);
const dispatch: Dispatch<A> = (queue.dispatch: any);
if (renderPhaseUpdates !== null) {
// Render phase updates are stored in a map of queue -> linked list
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
let newState = workInProgressHook.memoizedState;
let update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
const action = update.action;
if (__DEV__) {
isInHookUserCodeInDev = true;
}
newState = reducer(newState, action);
if (__DEV__) {
isInHookUserCodeInDev = false;
}
update = update.next;
} while (update !== null);
workInProgressHook.memoizedState = newState;
return [newState, dispatch];
}
}
return [workInProgressHook.memoizedState, dispatch];
} else {
if (__DEV__) {
isInHookUserCodeInDev = true;
}
let initialState;
if (reducer === basicStateReducer) {
// Special case for `useState`.
initialState =
typeof initialArg === 'function'
? ((initialArg: any): () => S)()
: ((initialArg: any): S);
} else {
initialState =
init !== undefined ? init(initialArg) : ((initialArg: any): S);
}
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = initialState;
const queue: UpdateQueue<A> = (workInProgressHook.queue = {
last: null,
dispatch: null,
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingComponent,
queue,
): any));
return [workInProgressHook.memoizedState, dispatch];
}
}
function useMemo<T>(nextCreate: () => T, deps: Array<mixed> | void | null): T {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
if (workInProgressHook !== null) {
const prevState = workInProgressHook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
}
}
if (__DEV__) {
isInHookUserCodeInDev = true;
}
const nextValue = nextCreate();
if (__DEV__) {
isInHookUserCodeInDev = false;
}
workInProgressHook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
function useRef<T>(initialValue: T): {|current: T|} {
currentlyRenderingComponent = resolveCurrentlyRenderingComponent();
workInProgressHook = createWorkInProgressHook();
const previousRef = workInProgressHook.memoizedState;
if (previousRef === null) {
const ref = {current: initialValue};
if (__DEV__) {
Object.seal(ref);
}
workInProgressHook.memoizedState = ref;
return ref;
} else {
return previousRef;
}
}
export function useLayoutEffect(
create: () => (() => void) | void,
inputs: Array<mixed> | void | null,
) {
if (__DEV__) {
currentHookNameInDev = 'useLayoutEffect';
console.error(
'useLayoutEffect does nothing on the server, because its effect cannot ' +
"be encoded into the server renderer's output format. This will lead " +
'to a mismatch between the initial, non-hydrated UI and the intended ' +
'UI. To avoid this, useLayoutEffect should only be used in ' +
'components that render exclusively on the client. ' +
'See https://reactjs.org/link/uselayouteffect-ssr for common fixes.',
);
}
}
function dispatchAction<A>(
componentIdentity: Object,
queue: UpdateQueue<A>,
action: A,
) {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
if (numberOfReRenders >= RE_RENDER_LIMIT) {
throw new Error(
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);
}
if (componentIdentity === currentlyRenderingComponent) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
// This means an update has happened after the function component has
// returned. On the server this is a no-op. In React Fiber, the update
// would be scheduled for a future render.
}
}
export function useCallback<T>(
callback: T,
deps: Array<mixed> | void | null,
): T {
return useMemo(() => callback, deps);
}
// TODO Decide on how to implement this hook for server rendering.
// If a mutation occurs during render, consider triggering a Suspense boundary
// and falling back to client rendering.
function useMutableSource<Source, Snapshot>(
source: MutableSource<Source>,
getSnapshot: MutableSourceGetSnapshotFn<Source, Snapshot>,
subscribe: MutableSourceSubscribeFn<Source, Snapshot>,
): Snapshot {
resolveCurrentlyRenderingComponent();
return getSnapshot(source._source);
}
function useSyncExternalStore<T>(
subscribe: (() => void) => () => void,
getSnapshot: () => T,
getServerSnapshot?: () => T,
): T {
if (getServerSnapshot === undefined) {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error(
'Missing getServerSnapshot, which is required for ' +
'server-rendered content. Will revert to client rendering.',
);
}
return getServerSnapshot();
}
function useDeferredValue<T>(value: T): T {
resolveCurrentlyRenderingComponent();
return value;
}
function unsupportedStartTransition() {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error('startTransition cannot be called during server rendering.');
}
function useTransition(): [boolean, (callback: () => void) => void] {
resolveCurrentlyRenderingComponent();
return [false, unsupportedStartTransition];
}
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
function useId(): string {
const task: Task = (currentlyRenderingTask: any);
const treeId = getTreeId(task.treeContext);
const responseState = currentResponseState;
if (responseState === null) {
throw new Error(
'Invalid hook call. Hooks can only be called inside of the body of a function component.',
);
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
}
const localId = localIdCounter++;
return makeId(responseState, treeId, localId);
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
}
function unsupportedRefresh() {
[RFC] Codemod invariant -> throw new Error (#22435) * Hoist error codes import to module scope When this code was written, the error codes map (`codes.json`) was created on-the-fly, so we had to lazily require from inside the visitor. Because `codes.json` is now checked into source, we can import it a single time in module scope. * Minify error constructors in production We use a script to minify our error messages in production. Each message is assigned an error code, defined in `scripts/error-codes/codes.json`. Then our build script replaces the messages with a link to our error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92 This enables us to write helpful error messages without increasing the bundle size. Right now, the script only works for `invariant` calls. It does not work if you throw an Error object. This is an old Facebookism that we don't really need, other than the fact that our error minification script relies on it. So, I've updated the script to minify error constructors, too: Input: Error(`A ${adj} message that contains ${noun}`); Output: Error(formatProdErrorMessage(ERR_CODE, adj, noun)); It only works for constructors that are literally named Error, though we could add support for other names, too. As a next step, I will add a lint rule to enforce that errors written this way must have a corresponding error code. * Minify "no fallback UI specified" error in prod This error message wasn't being minified because it doesn't use invariant. The reason it didn't use invariant is because this particular error is created without begin thrown — it doesn't need to be thrown because it's located inside the error handling part of the runtime. Now that the error minification script supports Error constructors, we can minify it by assigning it a production error code in `scripts/error-codes/codes.json`. To support the use of Error constructors more generally, I will add a lint rule that enforces each message has a corresponding error code. * Lint rule to detect unminified errors Adds a lint rule that detects when an Error constructor is used without a corresponding production error code. We already have this for `invariant`, but not for regular errors, i.e. `throw new Error(msg)`. There's also nothing that enforces the use of `invariant` besides convention. There are some packages where we don't care to minify errors. These are packages that run in environments where bundle size is not a concern, like react-pg. I added an override in the ESLint config to ignore these. * Temporarily add invariant codemod script I'm adding this codemod to the repo temporarily, but I'll revert it in the same PR. That way we don't have to check it in but it's still accessible (via the PR) if we need it later. * [Automated] Codemod invariant -> Error This commit contains only automated changes: npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*" yarn linc --fix yarn prettier I will do any manual touch ups in separate commits so they're easier to review. * Remove temporary codemod script This reverts the codemod script and ESLint config I added temporarily in order to perform the invariant codemod. * Manual touch ups A few manual changes I made after the codemod ran. * Enable error code transform per package Currently we're not consistent about which packages should have their errors minified in production and which ones should. This adds a field to the bundle configuration to control whether to apply the transform. We should decide what the criteria is going forward. I think it's probably a good idea to minify any package that gets sent over the network. So yes to modules that run in the browser, and no to modules that run on the server and during development only.
2021-10-01 03:01:28 +08:00
throw new Error('Cache cannot be refreshed during server rendering.');
}
function useCacheRefresh(): <T>(?() => T, ?T) => void {
return unsupportedRefresh;
}
function noop(): void {}
export const Dispatcher: DispatcherType = {
readContext,
useContext,
useMemo,
useReducer,
useRef,
useState,
useInsertionEffect: noop,
useLayoutEffect,
useCallback,
// useImperativeHandle is not run in the server environment
useImperativeHandle: noop,
// Effects are not run in the server environment.
useEffect: noop,
// Debugging effect
useDebugValue: noop,
useDeferredValue,
useTransition,
useId (#22644) * Add useId to dispatcher * Initial useId implementation Ids are base 32 strings whose binary representation corresponds to the position of a node in a tree. Every time the tree forks into multiple children, we add additional bits to the left of the sequence that represent the position of the child within the current level of children. 00101 00010001011010101 ╰─┬─╯ ╰───────┬───────╯ Fork 5 of 20 Parent id The leading 0s are important. In the above example, you only need 3 bits to represent slot 5. However, you need 5 bits to represent all the forks at the current level, so we must account for the empty bits at the end. For this same reason, slots are 1-indexed instead of 0-indexed. Otherwise, the zeroth id at a level would be indistinguishable from its parent. If a node has only one child, and does not materialize an id (i.e. does not contain a useId hook), then we don't need to allocate any space in the sequence. It's treated as a transparent indirection. For example, these two trees produce the same ids: <> <> <Indirection> <A /> <A /> <B /> </Indirection> </> <B /> </> However, we cannot skip any materializes an id. Otherwise, a parent id that does not fork would be indistinguishable from its child id. For example, this tree does not fork, but the parent and child must have different ids. <Parent> <Child /> </Parent> To handle this scenario, every time we materialize an id, we allocate a new level with a single slot. You can think of this as a fork with only one prong, or an array of children with length 1. It's possible for the the size of the sequence to exceed 32 bits, the max size for bitwise operations. When this happens, we make more room by converting the right part of the id to a string and storing it in an overflow variable. We use a base 32 string representation, because 32 is the largest power of 2 that is supported by toString(). We want the base to be large so that the resulting ids are compact, and we want the base to be a power of 2 because every log2(base) bits corresponds to a single character, i.e. every log2(32) = 5 bits. That means we can lop bits off the end 5 at a time without affecting the final result. * Incremental hydration Stores the tree context on the dehydrated Suspense boundary's state object so it resume where it left off. * Add useId to react-debug-tools * Add selective hydration test Demonstrates that selective hydration works and ids are preserved even after subsequent client updates.
2021-11-02 04:30:44 +08:00
useId,
// Subscriptions are not setup in a server environment.
useMutableSource,
useSyncExternalStore,
};
if (enableCache) {
Dispatcher.getCacheForType = getCacheForType;
Dispatcher.useCacheRefresh = useCacheRefresh;
}
export let currentResponseState: null | ResponseState = (null: any);
export function setCurrentResponseState(
responseState: null | ResponseState,
): void {
currentResponseState = responseState;
}