@samitouri / QOS-React-1 / commits / c897260cff

refactor[react-devtools-shared]: minor parsing improvements and modifications (#27661)

Had these stashed for some time, it includes: - Some refactoring to remove unnecessary `FlowFixMe`s and type castings via `any`. - Optimized version of parsing component names. We encode string names to utf8 and then pass it serialized from backend to frontend in a single array of numbers. Previously we would call `slice` to get the corresponding encoded string as a subarray and then parse each character. New implementation skips `slice` step and just receives `left` and `right` ranges for the string to parse. - Early `break` instead of `continue` when Store receives unexpected operation, like removing an element from the Store, which is not registered yet.

Ruslan Lesiutin committed Nov 7, 2023 at 16:39 UTC c897260cffb6a237d5ad707a6043f68ddf9ab014
6 files changed +106 -65
packages/react-devtools-shared/src/backend/renderer.js
-1
@@ -439,7 +439,6 @@ export function getInternalReactConstants(version: string): {
439 return 'Cache';
440 case ClassComponent:
441 case IncompleteClassComponent:
442 - return getDisplayName(resolvedType);
442 case FunctionComponent:
443 case IndeterminateComponent:
444 return getDisplayName(resolvedType);
packages/react-devtools-shared/src/devtools/store.js
+80 -39
@@ -27,7 +27,7 @@ import {
27 setSavedComponentFilters,
28 separateDisplayNameAndHOCs,
29 shallowDiffers,
30 - utfDecodeString,
30 + utfDecodeStringWithRanges,
31 } from '../utils';
32 import {localStorageGetItem, localStorageSetItem} from '../storage';
33 import {__DEBUG__} from '../constants';
@@ -450,7 +450,7 @@ export default class Store extends EventEmitter<{
450 }
451
452 // This build of DevTools supports the legacy profiler.
453 - // This is a static flag, controled by the Store config.
453 + // This is a static flag, controlled by the Store config.
454 get supportsProfiling(): boolean {
455 return this._supportsProfiling;
456 }
@@ -467,7 +467,7 @@ export default class Store extends EventEmitter<{
467 }
468
469 // This build of DevTools supports the Timeline profiler.
470 - // This is a static flag, controled by the Store config.
470 + // This is a static flag, controlled by the Store config.
471 get supportsTimeline(): boolean {
472 return this._supportsTimeline;
473 }
@@ -502,30 +502,58 @@ export default class Store extends EventEmitter<{
502 }
503
504 // Find which root this element is in...
505 - let rootID;
505 let root;
506 let rootWeight = 0;
507 for (let i = 0; i < this._roots.length; i++) {
509 - rootID = this._roots[i];
510 - root = ((this._idToElement.get(rootID): any): Element);
508 + const rootID = this._roots[i];
509 + root = this._idToElement.get(rootID);
510 +
511 + if (root === undefined) {
512 + this._throwAndEmitError(
513 + Error(
514 + `Couldn't find root with id "${rootID}": no matching node was found in the Store.`,
515 + ),
516 + );
517 +
518 + return null;
519 + }
520 +
521 if (root.children.length === 0) {
522 continue;
513 - } else if (rootWeight + root.weight > index) {
523 + }
524 +
525 + if (rootWeight + root.weight > index) {
526 break;
527 } else {
528 rootWeight += root.weight;
529 }
530 }
531
532 + if (root === undefined) {
533 + return null;
534 + }
535 +
536 // Find the element in the tree using the weight of each node...
537 // Skip over the root itself, because roots aren't visible in the Elements tree.
522 - let currentElement = ((root: any): Element);
538 + let currentElement: Element = root;
539 let currentWeight = rootWeight - 1;
540 +
541 while (index !== currentWeight) {
542 const numChildren = currentElement.children.length;
543 for (let i = 0; i < numChildren; i++) {
544 const childID = currentElement.children[i];
528 - const child = ((this._idToElement.get(childID): any): Element);
545 + const child = this._idToElement.get(childID);
546 +
547 + if (child === undefined) {
548 + this._throwAndEmitError(
549 + Error(
550 + `Couldn't child element with id "${childID}": no matching node was found in the Store.`,
551 + ),
552 + );
553 +
554 + return null;
555 + }
556 +
557 const childWeight = child.isCollapsed ? 1 : child.weight;
558
559 if (index <= currentWeight + childWeight) {
@@ -538,7 +566,7 @@ export default class Store extends EventEmitter<{
566 }
567 }
568
541 - return ((currentElement: any): Element) || null;
569 + return currentElement || null;
570 }
571
572 getElementIDAtIndex(index: number): number | null {
@@ -560,32 +588,31 @@ export default class Store extends EventEmitter<{
588 getElementsWithErrorsAndWarnings(): Array<{id: number, index: number}> {
589 if (this._cachedErrorAndWarningTuples !== null) {
590 return this._cachedErrorAndWarningTuples;
563 - } else {
564 - const errorAndWarningTuples: ErrorAndWarningTuples = [];
565 -
566 - this._errorsAndWarnings.forEach((_, id) => {
567 - const index = this.getIndexOfElementID(id);
568 - if (index !== null) {
569 - let low = 0;
570 - let high = errorAndWarningTuples.length;
571 - while (low < high) {
572 - const mid = (low + high) >> 1;
573 - if (errorAndWarningTuples[mid].index > index) {
574 - high = mid;
575 - } else {
576 - low = mid + 1;
577 - }
578 - }
591 + }
592
580 - errorAndWarningTuples.splice(low, 0, {id, index});
593 + const errorAndWarningTuples: ErrorAndWarningTuples = [];
594 +
595 + this._errorsAndWarnings.forEach((_, id) => {
596 + const index = this.getIndexOfElementID(id);
597 + if (index !== null) {
598 + let low = 0;
599 + let high = errorAndWarningTuples.length;
600 + while (low < high) {
601 + const mid = (low + high) >> 1;
602 + if (errorAndWarningTuples[mid].index > index) {
603 + high = mid;
604 + } else {
605 + low = mid + 1;
606 + }
607 }
582 - });
608
584 - // Cache for later (at least until the tree changes again).
585 - this._cachedErrorAndWarningTuples = errorAndWarningTuples;
609 + errorAndWarningTuples.splice(low, 0, {id, index});
610 + }
611 + });
612
587 - return errorAndWarningTuples;
588 - }
613 + // Cache for later (at least until the tree changes again).
614 + this._cachedErrorAndWarningTuples = errorAndWarningTuples;
615 + return errorAndWarningTuples;
616 }
617
618 getErrorAndWarningCountForElementID(id: number): {
@@ -923,7 +950,11 @@ export default class Store extends EventEmitter<{
950 const nextLength = operations[i];
951 i++;
952
926 - const nextString = utfDecodeString(operations.slice(i, i + nextLength));
953 + const nextString = utfDecodeStringWithRanges(
954 + operations,
955 + i,
956 + i + nextLength - 1,
957 + );
958 stringTable.push(nextString);
959 i += nextLength;
960 }
@@ -1035,7 +1066,7 @@ export default class Store extends EventEmitter<{
1066 ),
1067 );
1068
1038 - continue;
1069 + break;
1070 }
1071
1072 parentElement.children.push(id);
@@ -1088,7 +1119,7 @@ export default class Store extends EventEmitter<{
1119 ),
1120 );
1121
1091 - continue;
1122 + break;
1123 }
1124
1125 i += 1;
@@ -1126,7 +1157,7 @@ export default class Store extends EventEmitter<{
1157 ),
1158 );
1159
1129 - continue;
1160 + break;
1161 }
1162
1163 const index = parentElement.children.indexOf(id);
@@ -1172,7 +1203,17 @@ export default class Store extends EventEmitter<{
1203 }
1204 };
1205
1175 - const root = ((this._idToElement.get(id): any): Element);
1206 + const root = this._idToElement.get(id);
1207 + if (root === undefined) {
1208 + this._throwAndEmitError(
1209 + Error(
1210 + `Cannot remove root "${id}": no matching node was found in the Store.`,
1211 + ),
1212 + );
1213 +
1214 + break;
1215 + }
1216 +
1217 recursivelyDeleteElements(id);
1218
1219 this._rootIDToCapabilities.delete(id);
@@ -1194,7 +1235,7 @@ export default class Store extends EventEmitter<{
1235 ),
1236 );
1237
1197 - continue;
1238 + break;
1239 }
1240
1241 const children = element.children;
@@ -1279,7 +1320,7 @@ export default class Store extends EventEmitter<{
1320
1321 this._revision++;
1322
1282 - // Any time the tree changes (e.g. elements added, removed, or reordered) cached inidices may be invalid.
1323 + // Any time the tree changes (e.g. elements added, removed, or reordered) cached indices may be invalid.
1324 this._cachedErrorAndWarningTuples = null;
1325
1326 if (haveErrorsOrWarningsChanged) {
packages/react-devtools-shared/src/devtools/views/Components/Element.js
+4 -5
@@ -122,7 +122,7 @@ export default function Element({data, index, style}: Props): React.Node {
122 isStrictModeNonCompliant,
123 key,
124 type,
125 - } = ((element: any): ElementType);
125 + } = element;
126
127 // Only show strict mode non-compliance badges for top level elements.
128 // Showing an inline badge for every element in the tree would be noisy.
@@ -173,17 +173,16 @@ export default function Element({data, index, style}: Props): React.Node {
173 "
174 </Fragment>
175 )}
176 +
177 {hocDisplayNames !== null && hocDisplayNames.length > 0 ? (
178 <Badge
179 className={styles.Badge}
180 hocDisplayNames={hocDisplayNames}
181 type={type}>
181 - <DisplayName
182 - displayName={hocDisplayNames[0]}
183 - id={((id: any): number)}
184 - />
182 + <DisplayName displayName={hocDisplayNames[0]} id={id} />
183 </Badge>
184 ) : null}
185 +
186 {showInlineWarningsAndErrors && errorCount > 0 && (
187 <Icon
188 type="error"
packages/react-devtools-shared/src/devtools/views/Components/HocBadges.js
+5 -6
@@ -25,12 +25,11 @@ export default function HocBadges({element}: Props): React.Node {
25
26 return (
27 <div className={styles.HocBadges}>
28 - {hocDisplayNames !== null &&
29 - hocDisplayNames.map(hocDisplayName => (
30 - <div key={hocDisplayName} className={styles.Badge}>
31 - {hocDisplayName}
32 - </div>
33 - ))}
28 + {hocDisplayNames.map(hocDisplayName => (
29 + <div key={hocDisplayName} className={styles.Badge}>
30 + {hocDisplayName}
31 + </div>
32 + ))}
33 </div>
34 );
35 }
packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js
+5 -3
@@ -17,7 +17,7 @@ import {
17 TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
18 TREE_OPERATION_UPDATE_ERRORS_OR_WARNINGS,
19 } from 'react-devtools-shared/src/constants';
20 -import {utfDecodeString} from 'react-devtools-shared/src/utils';
20 +import {utfDecodeStringWithRanges} from 'react-devtools-shared/src/utils';
21 import {ElementTypeRoot} from 'react-devtools-shared/src/frontend/types';
22 import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore';
23
@@ -170,8 +170,10 @@ function updateTree(
170 const stringTableEnd = i + stringTableSize;
171 while (i < stringTableEnd) {
172 const nextLength = operations[i++];
173 - const nextString = utfDecodeString(
174 - (operations.slice(i, i + nextLength): any),
173 + const nextString = utfDecodeStringWithRanges(
174 + operations,
175 + i,
176 + i + nextLength - 1,
177 );
178 stringTable.push(nextString);
179 i += nextLength;
packages/react-devtools-shared/src/utils.js
+12 -11
@@ -116,7 +116,7 @@ export function getWrappedDisplayName(
116 wrapperName: string,
117 fallbackName?: string,
118 ): string {
119 - const displayName = (outerType: any).displayName;
119 + const displayName = (outerType: any)?.displayName;
120 return (
121 displayName || `${wrapperName}(${getDisplayName(innerType, fallbackName)})`
122 );
@@ -152,15 +152,14 @@ export function getUID(): number {
152 return ++uidCounter;
153 }
154
155 -export function utfDecodeString(array: Array<number>): string {
156 - // Avoid spreading the array (e.g. String.fromCodePoint(...array))
157 - // Functions arguments are first placed on the stack before the function is called
158 - // which throws a RangeError for large arrays.
159 - // See github.com/facebook/react/issues/22293
155 +export function utfDecodeStringWithRanges(
156 + array: Array<number>,
157 + left: number,
158 + right: number,
159 +): string {
160 let string = '';
161 - for (let i = 0; i < array.length; i++) {
162 - const char = array[i];
163 - string += String.fromCodePoint(char);
161 + for (let i = left; i <= right; i++) {
162 + string += String.fromCodePoint(array[i]);
163 }
164 return string;
165 }
@@ -216,8 +215,10 @@ export function printOperationsArray(operations: Array<number>) {
215 const stringTableEnd = i + stringTableSize;
216 while (i < stringTableEnd) {
217 const nextLength = operations[i++];
219 - const nextString = utfDecodeString(
220 - (operations.slice(i, i + nextLength): any),
218 + const nextString = utfDecodeStringWithRanges(
219 + operations,
220 + i,
221 + i + nextLength - 1,
222 );
223 stringTable.push(nextString);
224 i += nextLength;