mirror of
https://github.com/zebrajr/react.git
synced 2025-12-07 00:20:28 +01:00
298 lines
7.6 KiB
JavaScript
298 lines
7.6 KiB
JavaScript
// @flow
|
|
|
|
import Symbol from 'es6-symbol';
|
|
import LRU from 'lru-cache';
|
|
import {
|
|
TREE_OPERATION_ADD,
|
|
TREE_OPERATION_REMOVE,
|
|
TREE_OPERATION_REORDER_CHILDREN,
|
|
TREE_OPERATION_UPDATE_TREE_BASE_DURATION,
|
|
} from './constants';
|
|
import {ElementTypeRoot} from 'react-devtools-shared/src/types';
|
|
import {
|
|
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
|
|
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
|
|
} from './constants';
|
|
import {ComponentFilterElementType, ElementTypeHostComponent} from './types';
|
|
import {
|
|
ElementTypeClass,
|
|
ElementTypeForwardRef,
|
|
ElementTypeFunction,
|
|
ElementTypeMemo,
|
|
} from 'react-devtools-shared/src/types';
|
|
import {localStorageGetItem, localStorageSetItem} from './storage';
|
|
|
|
import type {ComponentFilter, ElementType} from './types';
|
|
|
|
const cachedDisplayNames: WeakMap<Function, string> = new WeakMap();
|
|
|
|
// On large trees, encoding takes significant time.
|
|
// Try to reuse the already encoded strings.
|
|
let encodedStringCache = new LRU({max: 1000});
|
|
|
|
export function getDisplayName(
|
|
type: Function,
|
|
fallbackName: string = 'Anonymous',
|
|
): string {
|
|
const nameFromCache = cachedDisplayNames.get(type);
|
|
if (nameFromCache != null) {
|
|
return nameFromCache;
|
|
}
|
|
|
|
let displayName;
|
|
|
|
// The displayName property is not guaranteed to be a string.
|
|
// It's only safe to use for our purposes if it's a string.
|
|
// github.com/facebook/react-devtools/issues/803
|
|
if (typeof type.displayName === 'string') {
|
|
displayName = type.displayName;
|
|
}
|
|
|
|
if (!displayName) {
|
|
displayName = type.name || fallbackName;
|
|
}
|
|
|
|
cachedDisplayNames.set(type, displayName);
|
|
return displayName;
|
|
}
|
|
|
|
let uidCounter: number = 0;
|
|
|
|
export function getUID(): number {
|
|
return ++uidCounter;
|
|
}
|
|
|
|
export function utfDecodeString(array: Array<number>): string {
|
|
return String.fromCodePoint(...array);
|
|
}
|
|
|
|
export function utfEncodeString(string: string): Array<number> {
|
|
let cached = encodedStringCache.get(string);
|
|
if (cached !== undefined) {
|
|
return cached;
|
|
}
|
|
|
|
const encoded = new Array(string.length);
|
|
for (let i = 0; i < string.length; i++) {
|
|
encoded[i] = string.codePointAt(i);
|
|
}
|
|
encodedStringCache.set(string, encoded);
|
|
return encoded;
|
|
}
|
|
|
|
export function printOperationsArray(operations: Array<number>) {
|
|
// The first two values are always rendererID and rootID
|
|
const rendererID = operations[0];
|
|
const rootID = operations[1];
|
|
|
|
const logs = [`opertions for renderer:${rendererID} and root:${rootID}`];
|
|
|
|
let i = 2;
|
|
|
|
// Reassemble the string table.
|
|
const stringTable = [
|
|
null, // ID = 0 corresponds to the null string.
|
|
];
|
|
const stringTableSize = operations[i++];
|
|
const stringTableEnd = i + stringTableSize;
|
|
while (i < stringTableEnd) {
|
|
const nextLength = operations[i++];
|
|
const nextString = utfDecodeString(
|
|
(operations.slice(i, i + nextLength): any),
|
|
);
|
|
stringTable.push(nextString);
|
|
i += nextLength;
|
|
}
|
|
|
|
while (i < operations.length) {
|
|
const operation = operations[i];
|
|
|
|
switch (operation) {
|
|
case TREE_OPERATION_ADD: {
|
|
const id = ((operations[i + 1]: any): number);
|
|
const type = ((operations[i + 2]: any): ElementType);
|
|
|
|
i += 3;
|
|
|
|
if (type === ElementTypeRoot) {
|
|
logs.push(`Add new root node ${id}`);
|
|
|
|
i++; // supportsProfiling
|
|
i++; // hasOwnerMetadata
|
|
} else {
|
|
const parentID = ((operations[i]: any): number);
|
|
i++;
|
|
|
|
i++; // ownerID
|
|
|
|
const displayNameStringID = operations[i];
|
|
const displayName = stringTable[displayNameStringID];
|
|
i++;
|
|
|
|
i++; // key
|
|
|
|
logs.push(
|
|
`Add node ${id} (${displayName || 'null'}) as child of ${parentID}`,
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case TREE_OPERATION_REMOVE: {
|
|
const removeLength = ((operations[i + 1]: any): number);
|
|
i += 2;
|
|
|
|
for (let removeIndex = 0; removeIndex < removeLength; removeIndex++) {
|
|
const id = ((operations[i]: any): number);
|
|
i += 1;
|
|
|
|
logs.push(`Remove node ${id}`);
|
|
}
|
|
break;
|
|
}
|
|
case TREE_OPERATION_REORDER_CHILDREN: {
|
|
const id = ((operations[i + 1]: any): number);
|
|
const numChildren = ((operations[i + 2]: any): number);
|
|
i += 3;
|
|
const children = operations.slice(i, i + numChildren);
|
|
i += numChildren;
|
|
|
|
logs.push(`Re-order node ${id} children ${children.join(',')}`);
|
|
break;
|
|
}
|
|
case TREE_OPERATION_UPDATE_TREE_BASE_DURATION:
|
|
// Base duration updates are only sent while profiling is in progress.
|
|
// We can ignore them at this point.
|
|
// The profiler UI uses them lazily in order to generate the tree.
|
|
i += 3;
|
|
break;
|
|
default:
|
|
throw Error(`Unsupported Bridge operation ${operation}`);
|
|
}
|
|
}
|
|
|
|
console.log(logs.join('\n '));
|
|
}
|
|
|
|
export function getDefaultComponentFilters(): Array<ComponentFilter> {
|
|
return [
|
|
{
|
|
type: ComponentFilterElementType,
|
|
value: ElementTypeHostComponent,
|
|
isEnabled: true,
|
|
},
|
|
];
|
|
}
|
|
|
|
export function getSavedComponentFilters(): Array<ComponentFilter> {
|
|
try {
|
|
const raw = localStorageGetItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
|
|
if (raw != null) {
|
|
return JSON.parse(raw);
|
|
}
|
|
} catch (error) {}
|
|
return getDefaultComponentFilters();
|
|
}
|
|
|
|
export function saveComponentFilters(
|
|
componentFilters: Array<ComponentFilter>,
|
|
): void {
|
|
localStorageSetItem(
|
|
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
|
|
JSON.stringify(componentFilters),
|
|
);
|
|
}
|
|
|
|
export function getAppendComponentStack(): boolean {
|
|
try {
|
|
const raw = localStorageGetItem(LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY);
|
|
if (raw != null) {
|
|
return JSON.parse(raw);
|
|
}
|
|
} catch (error) {}
|
|
return true;
|
|
}
|
|
|
|
export function setAppendComponentStack(value: boolean): void {
|
|
localStorageSetItem(
|
|
LOCAL_STORAGE_SHOULD_PATCH_CONSOLE_KEY,
|
|
JSON.stringify(value),
|
|
);
|
|
}
|
|
|
|
export function separateDisplayNameAndHOCs(
|
|
displayName: string | null,
|
|
type: ElementType,
|
|
): [string | null, Array<string> | null] {
|
|
if (displayName === null) {
|
|
return [null, null];
|
|
}
|
|
|
|
let hocDisplayNames = null;
|
|
|
|
switch (type) {
|
|
case ElementTypeClass:
|
|
case ElementTypeForwardRef:
|
|
case ElementTypeFunction:
|
|
case ElementTypeMemo:
|
|
if (displayName.indexOf('(') >= 0) {
|
|
const matches = displayName.match(/[^()]+/g);
|
|
if (matches != null) {
|
|
displayName = matches.pop();
|
|
hocDisplayNames = matches;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return [displayName, hocDisplayNames];
|
|
}
|
|
|
|
// Pulled from react-compat
|
|
// https://github.com/developit/preact-compat/blob/7c5de00e7c85e2ffd011bf3af02899b63f699d3a/src/index.js#L349
|
|
export function shallowDiffers(prev: Object, next: Object): boolean {
|
|
for (let attribute in prev) {
|
|
if (!(attribute in next)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (let attribute in next) {
|
|
if (prev[attribute] !== next[attribute]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function getInObject(object: Object, path: Array<string | number>): any {
|
|
return path.reduce((reduced: Object, attr: string | number): any => {
|
|
if (reduced) {
|
|
if (hasOwnProperty.call(reduced, attr)) {
|
|
return reduced[attr];
|
|
}
|
|
if (typeof reduced[Symbol.iterator] === 'function') {
|
|
// Convert iterable to array and return array[index]
|
|
return [...reduced][attr];
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}, object);
|
|
}
|
|
|
|
export function setInObject(
|
|
object: Object,
|
|
path: Array<string | number>,
|
|
value: any,
|
|
) {
|
|
const length = path.length;
|
|
const last = path[length - 1];
|
|
if (object != null) {
|
|
const parent = getInObject(object, path.slice(0, length - 1));
|
|
if (parent) {
|
|
parent[last] = value;
|
|
}
|
|
}
|
|
}
|