mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Stashing partial windowing implementation
This commit is contained in:
+1
-1
@@ -90,7 +90,7 @@
|
||||
"react-dom": "^16.8.0-alpha.1",
|
||||
"react-portal": "^3.1.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.2",
|
||||
"react-window": "^1.1.1",
|
||||
"react-window": "^1.5.0",
|
||||
"semver": "^5.5.1",
|
||||
"style-loader": "^0.23.1",
|
||||
"web-ext": "^1.10.1",
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { Bridge } from '../types';
|
||||
import type { Element } from 'src/devtools/types';
|
||||
|
||||
const debug = (methodName, ...args) => {
|
||||
// debug(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args);
|
||||
console.log(`%cAgent %c${methodName}`, 'color: blue; font-weight: bold;', 'font-weight: bold;', ...args);
|
||||
};
|
||||
|
||||
const THROTTLE_BY_MS = 350;
|
||||
@@ -34,6 +34,7 @@ export default class Agent extends EventEmitter {
|
||||
// bridge.on('...'), this...);
|
||||
|
||||
this.addListener('root', id => bridge.send('root', id));
|
||||
this.addListener('rootCommitted', id => bridge.send('rootCommitted', id));
|
||||
this.addListener('mount', data => bridge.send('mount', data));
|
||||
this.addListener('update', data => bridge.send('update', data));
|
||||
this.addListener('unmount', data => bridge.send('unmount', data));
|
||||
@@ -137,6 +138,8 @@ export default class Agent extends EventEmitter {
|
||||
debug('emit("root")', id);
|
||||
this.emit('root', id);
|
||||
}
|
||||
|
||||
this.emit('rootCommitted', id);
|
||||
};
|
||||
|
||||
onHookUnmount = ({ fiber }: { fiber: Fiber }) => {
|
||||
|
||||
+205
-8
@@ -2,11 +2,11 @@
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import type { Element } from './types';
|
||||
import type { Element, ElementTreeMetadata } from './types';
|
||||
import type { Bridge } from '../types';
|
||||
|
||||
const debug = (methodName, ...args) => {
|
||||
// console.log(`%cAgent %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args);
|
||||
console.log(`%cStore %c${methodName}`, 'color: red; font-weight: bold;', 'font-weight: bold;', ...args);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -14,8 +14,17 @@ const debug = (methodName, ...args) => {
|
||||
* ContextProviders can subscribe to the Store for specific things they want to provide.
|
||||
*/
|
||||
class Store extends EventEmitter {
|
||||
_elementToElementTreeMetadata: WeakMap<
|
||||
Element,
|
||||
ElementTreeMetadata
|
||||
> = new WeakMap();
|
||||
_idToElement: Map<string, Element> = new Map();
|
||||
_idToParentID: Map<string, string> = new Map();
|
||||
_pendingDeletions: Set<string> = new Set();
|
||||
|
||||
// Total number of visible elements (within all roots).
|
||||
// Used for windowing purposes.
|
||||
numElements: number = 0;
|
||||
|
||||
// This Array must be treated as immutable!
|
||||
// Passive effects will check it for changes between render and mount.
|
||||
@@ -25,12 +34,54 @@ class Store extends EventEmitter {
|
||||
super();
|
||||
|
||||
bridge.on('root', this.onBridgeRoot);
|
||||
bridge.on('rootCommitted', this.onBridgeRootCommitted);
|
||||
|
||||
bridge.on('mount', this.onBridgeMount);
|
||||
bridge.on('update', this.onBridgeUpdated);
|
||||
bridge.on('unmount', this.onBridgeUnmounted);
|
||||
}
|
||||
|
||||
getElement(id: string) {
|
||||
getElementAtIndex(index: number): Element | null {
|
||||
if (index < 0 || index >= this.numElements) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rootID;
|
||||
let root;
|
||||
|
||||
let rootWeight = 0;
|
||||
for (let i = 0; i < this.roots.length; i++) {
|
||||
rootID = this.roots[i];
|
||||
root = this._idToElement.get(rootID);
|
||||
const { weight } = this._elementToElementTreeMetadata.get(root);
|
||||
|
||||
if (rootWeight + weight > index) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let currentElement = root;
|
||||
let currentWeight = 0;
|
||||
|
||||
while (index !== currentWeight) {
|
||||
for (let i = 0; i < currentElement.children.length; i++) {
|
||||
const childID = currentElement.children[i];
|
||||
const child = this._idToElement.get(childID);
|
||||
const { weight } = this._elementToElementTreeMetadata.get(child);
|
||||
if (index <= currentWeight + weight) {
|
||||
currentWeight++;
|
||||
currentElement = child;
|
||||
break;
|
||||
} else {
|
||||
currentWeight += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentElement;
|
||||
}
|
||||
|
||||
getElementByID(id: string) {
|
||||
return this._idToElement.get(id);
|
||||
}
|
||||
|
||||
@@ -38,6 +89,117 @@ class Store extends EventEmitter {
|
||||
return this._idToParentID.get(id);
|
||||
}
|
||||
|
||||
getTreeMetadataForElement(element: Element) {
|
||||
return this._elementToElementTreeMetadata.get(element);
|
||||
}
|
||||
|
||||
_crawlForTreeMetadata(id: string, depth: number = 0): number {
|
||||
let weight = 1;
|
||||
|
||||
const element = this._idToElement.get(id);
|
||||
|
||||
// TODO: Figure out why sometimes items aren't being sent across the bridge.
|
||||
// It always seems to be one of the ListItems...
|
||||
if (element == null) {
|
||||
console.log(`%cNo element found for id "${id}"`, 'background-color: yellow; font-weight: bold;');
|
||||
return 0;
|
||||
}
|
||||
|
||||
element.children.forEach(childID => {
|
||||
weight += this._crawlForTreeMetadata(childID, depth + 1);
|
||||
});
|
||||
|
||||
this._elementToElementTreeMetadata.set(element, {
|
||||
depth,
|
||||
weight,
|
||||
});
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
_updateElementTreeMetadata(prevElement: Element, element: Element): void {
|
||||
if (prevElement.children === element.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compare children in case they have changed.
|
||||
// For each child that was removed, we need to shrink the list by this many elements.
|
||||
// For each child that was added, we need to grow the list by this many elements.
|
||||
|
||||
const prevChildren = prevElement.children;
|
||||
const prevNumChildren = prevChildren.length;
|
||||
|
||||
const children = element.children;
|
||||
const numChildren = children.length;
|
||||
|
||||
// TODO: The below diffing could be optimized more.
|
||||
|
||||
// Scan for deletions
|
||||
for (let i = 0; i < prevNumChildren; i++) {
|
||||
const childID = prevChildren[i];
|
||||
if (!children.includes(childID)) {
|
||||
const child = this._idToElement.get(childID);
|
||||
const { weight } = this._elementToElementTreeMetadata.get(child);
|
||||
|
||||
this.numElements -= weight;
|
||||
|
||||
let current = element;
|
||||
while (current !== null) {
|
||||
const datum = this._elementToElementTreeMetadata.get(current);
|
||||
datum.weight -= weight;
|
||||
|
||||
const parent = this._idToElement.get(datum.parentID);
|
||||
current =
|
||||
parent != null
|
||||
? this._elementToElementTreeMetadata.get(parent)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scan for additions
|
||||
for (let i = 0; i < numChildren; i++) {
|
||||
const childID = children[i];
|
||||
if (!prevChildren.includes(childID)) {
|
||||
const child = this._idToElement.get(childID);
|
||||
const { depth } = this._elementToElementTreeMetadata.get(element);
|
||||
const weight = this._crawlForTreeMetadata(
|
||||
childID,
|
||||
depth + 1
|
||||
);
|
||||
|
||||
this.numElements += weight;
|
||||
|
||||
let current = element;
|
||||
while (current !== null) {
|
||||
const datum = this._elementToElementTreeMetadata.get(current);
|
||||
datum.weight += weight;
|
||||
|
||||
const parent = this._idToElement.get(datum.parentID);
|
||||
current =
|
||||
parent != null
|
||||
? this._elementToElementTreeMetadata.get(parent)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__printTree() {
|
||||
let i = 0;
|
||||
this.roots.forEach(rootID => {
|
||||
const root = this._idToElement.get(rootID);
|
||||
const { weight } = this._elementToElementTreeMetadata.get(root);
|
||||
for (let j = i; j < i + weight; j++) {
|
||||
const element = this.getElementAtIndex(j)
|
||||
const { depth } = this._elementToElementTreeMetadata.get(element);
|
||||
|
||||
console.log(' '.repeat(depth) + element.displayName);
|
||||
}
|
||||
i += weight;
|
||||
});
|
||||
}
|
||||
|
||||
onBridgeMount = (element: Element) => {
|
||||
const { id } = element;
|
||||
debug('onBridgeMount()', element);
|
||||
@@ -54,23 +216,47 @@ class Store extends EventEmitter {
|
||||
debug('onBridgeRoot()', id);
|
||||
if (!this.roots.includes(id)) {
|
||||
this.roots = this.roots.concat(id);
|
||||
|
||||
// Generate tree metadata used for windowing.
|
||||
this.numElements += this._crawlForTreeMetadata(id);
|
||||
|
||||
this.emit('roots');
|
||||
}
|
||||
};
|
||||
|
||||
onBridgeRootCommitted = (rootID: string) => {
|
||||
this._pendingDeletions.forEach(id => {
|
||||
this._idToElement.delete(id);
|
||||
|
||||
if (this._idToParentID.has(id)) {
|
||||
this._idToParentID.delete(id);
|
||||
}
|
||||
});
|
||||
this._pendingDeletions.clear();
|
||||
|
||||
debug('onBridgeRootCommitted()', rootID);
|
||||
this.emit('rootCommitted', rootID);
|
||||
|
||||
this.__printTree();
|
||||
};
|
||||
|
||||
// TODO: Unmounting removes id-to-element before crawling, which breaks it.
|
||||
// Should I just ditch the idea of a WeakMap in favor of an explicit it-to-metadata mapping like with parents?
|
||||
onBridgeUnmounted = (id: string) => {
|
||||
debug('onBridgeUnmounted()', id);
|
||||
this._idToElement.delete(id);
|
||||
|
||||
if (this._idToParentID.has(id)) {
|
||||
this._idToParentID.delete(id);
|
||||
}
|
||||
this._pendingDeletions.add(id);
|
||||
|
||||
const index = this.roots.indexOf(id);
|
||||
if (index >= 0) {
|
||||
this.roots = this.roots
|
||||
.slice(0, index)
|
||||
.concat(this.roots.slice(index + 1));
|
||||
|
||||
const root = this._idToElement.get(id);
|
||||
const {weight} = this._elementToElementTreeMetadata.get(root);
|
||||
|
||||
this.numElements -= weight;
|
||||
|
||||
this.emit('roots');
|
||||
}
|
||||
};
|
||||
@@ -78,7 +264,18 @@ class Store extends EventEmitter {
|
||||
onBridgeUpdated = (element: Element) => {
|
||||
const { id } = element;
|
||||
debug('onBridgeUpdated()', element);
|
||||
|
||||
const prevElement = ((this._idToElement.get(id): any): Element);
|
||||
const prevElementTreeMetadata = ((this._elementToElementTreeMetadata.get(prevElement): any): ElementTreeMetadata);
|
||||
|
||||
this._idToElement.set(id, element);
|
||||
this._elementToElementTreeMetadata.set(
|
||||
element,
|
||||
prevElementTreeMetadata
|
||||
);
|
||||
|
||||
// Update tree metadata used for windowing.
|
||||
this._updateElementTreeMetadata(prevElement, element);
|
||||
|
||||
this.emit(id);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import typeof Store from './Store';
|
||||
|
||||
export const ElementTypeClassOrFunction = 1;
|
||||
export const ElementTypeContext = 2;
|
||||
export const ElementTypeForwardRef = 3;
|
||||
@@ -20,6 +22,12 @@ export type Element = {|
|
||||
children: Array<string>,
|
||||
|};
|
||||
|
||||
export type ElementTreeMetadata = {|
|
||||
depth: number,
|
||||
rootID: string,
|
||||
weight: number,
|
||||
|};
|
||||
|
||||
export type InspectedElement = {|
|
||||
id: string,
|
||||
context: Object | null,
|
||||
@@ -29,3 +37,8 @@ export type InspectedElement = {|
|
||||
canEditProps: boolean,
|
||||
source: Object,
|
||||
|};
|
||||
|
||||
export type TreeContext = {|
|
||||
size: number,
|
||||
store: Store,
|
||||
|};
|
||||
@@ -1,19 +1,18 @@
|
||||
// @flow
|
||||
|
||||
import React, { Fragment, useContext } from 'react';
|
||||
import { useElement } from './hooks';
|
||||
import { StoreContext } from './contexts';
|
||||
import { TreeContext } from './contexts';
|
||||
|
||||
import styles from './Element.css';
|
||||
|
||||
type Props = {|
|
||||
depth: number,
|
||||
id: string,
|
||||
index: number,
|
||||
style: Object,
|
||||
|};
|
||||
|
||||
export default function Element({ depth, id }: Props) {
|
||||
const store = useContext(StoreContext);
|
||||
const element = useElement(store, id);
|
||||
export default function Element({ index, style }: Props) {
|
||||
const {store} = useContext(TreeContext);
|
||||
const element = store.getElementAtIndex(index);
|
||||
|
||||
// DevTools are rendered in concurrent mode.
|
||||
// It's possible the store has updated since the commit that triggered this render.
|
||||
@@ -23,32 +22,32 @@ export default function Element({ depth, id }: Props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const elementTreeMetadata = store.getTreeMetadataForElement(element);
|
||||
|
||||
const { children, displayName, key } = element;
|
||||
const { depth } = elementTreeMetadata;
|
||||
|
||||
// TODO: Add state for toggling element open/close
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className={styles.Element}
|
||||
style={{ paddingLeft: `${1 + depth}rem` }}
|
||||
>
|
||||
{children.length > 0 && <span className={styles.ArrowOpen} />}
|
||||
<div
|
||||
className={styles.Element}
|
||||
style={{
|
||||
...style,
|
||||
paddingLeft: `${1 + depth}rem`
|
||||
}}
|
||||
>
|
||||
{children.length > 0 && <span className={styles.ArrowOpen} />}
|
||||
|
||||
<span className={styles.Component}>
|
||||
{displayName}
|
||||
{key && (
|
||||
<Fragment>
|
||||
<span className={styles.AttributeName}>key</span>=
|
||||
<span className={styles.AttributeValue}>"{key}"</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{children.map(childID => (
|
||||
<Element key={childID} depth={depth + 1} id={childID} />
|
||||
))}
|
||||
</Fragment>
|
||||
<span className={styles.Component}>
|
||||
{displayName}
|
||||
{key && (
|
||||
<Fragment>
|
||||
<span className={styles.AttributeName}>key</span>=
|
||||
<span className={styles.AttributeValue}>"{key}"</span>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import Store from '../store';
|
||||
import Tree from './Tree';
|
||||
import { StoreContext } from './contexts';
|
||||
import { StoreContext, TreeContext } from './contexts';
|
||||
import styles from './Elements.css';
|
||||
|
||||
import type { Bridge } from '../../types';
|
||||
@@ -17,11 +17,31 @@ export type Props = {|
|
||||
export default function Elements({ bridge, browserName, themeName }: Props) {
|
||||
const store = useMemo(() => new Store(bridge), []);
|
||||
|
||||
const [treeContext, setTreeContext] = useState(({
|
||||
size: store.numElements,
|
||||
store,
|
||||
}));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handler = () => {
|
||||
setTreeContext({
|
||||
size: store.numElements,
|
||||
store,
|
||||
});
|
||||
};
|
||||
|
||||
store.addListener('rootCommitted', handler);
|
||||
|
||||
return () => store.removeListener('rootCommitted', handler);
|
||||
}, [store]);
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={store}>
|
||||
<div className={styles.Elements}>
|
||||
<Tree />
|
||||
</div>
|
||||
<TreeContext.Provider value={treeContext}>
|
||||
<div className={styles.Elements}>
|
||||
<Tree />
|
||||
</div>
|
||||
</TreeContext.Provider>
|
||||
</StoreContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
+21
-33
@@ -1,44 +1,32 @@
|
||||
// @flow
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import Element from './Element';
|
||||
import { StoreContext } from './contexts';
|
||||
import { useElement, useRoots } from './hooks';
|
||||
import { TreeContext } from './contexts';
|
||||
|
||||
import styles from './Tree.css';
|
||||
|
||||
type TreeProps = {||};
|
||||
type Props = {||};
|
||||
|
||||
export default function Tree(props: TreeProps) {
|
||||
const store = useContext(StoreContext);
|
||||
const roots = useRoots(store);
|
||||
export default function Tree(props: Props) {
|
||||
const treeContext = useContext(TreeContext);
|
||||
|
||||
return (
|
||||
<div className={styles.Tree}>
|
||||
{roots.map(id => (
|
||||
<Root key={id} id={id} />
|
||||
))}
|
||||
</div>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
className={styles.Tree}
|
||||
height={height}
|
||||
itemCount={treeContext.size}
|
||||
itemData={treeContext}
|
||||
itemSize={20}
|
||||
width={width}
|
||||
>
|
||||
{Element}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
}
|
||||
|
||||
type RootProps = {|
|
||||
id: string,
|
||||
|};
|
||||
|
||||
function Root({ id }: RootProps) {
|
||||
const store = useContext(StoreContext);
|
||||
const element = useElement(store, id);
|
||||
|
||||
// DevTools are rendered in concurrent mode.
|
||||
// It's possible the store has updated since the commit that triggered this render.
|
||||
// So we need to guard against an undefined element.
|
||||
// TODO: Handle this by switching to a Suspense based approach.
|
||||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return element.children.map(childID => (
|
||||
<Element key={childID} depth={0} id={childID} />
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { TreeMetadata } from '../types';
|
||||
|
||||
import Store from '../store';
|
||||
|
||||
export const RootsContext = createContext<Array<string>>([]);
|
||||
export const StoreContext = createContext<Store>(((null: any): Store));
|
||||
export const TreeContext = createContext<TreeMetadata>(((null: any): TreeMetadata));
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useLayoutEffect, useState } from 'react';
|
||||
|
||||
import type { Element } from '../types';
|
||||
import type { Element, ElementTreeMetadata } from '../types';
|
||||
import Store from '../store';
|
||||
|
||||
// TODO useEffect has a bug where sometimes subscriptions don't get cleaned up correctly.
|
||||
@@ -10,16 +10,17 @@ import Store from '../store';
|
||||
// As a temporary work around, switch back to layout effect.
|
||||
|
||||
export function useElement(store: Store, id: string): ?Element {
|
||||
const [element, setElement] = useState<?Element>(store.getElement(id));
|
||||
const [element, setElement] = useState<?Element>(store.getElementByID(id));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handler = () => setElement(((store.getElement(id): any): Element));
|
||||
const handler = () =>
|
||||
setElement(((store.getElementByID(id): any): Element));
|
||||
|
||||
// Listen for changes to the element.
|
||||
store.addListener(id, handler);
|
||||
|
||||
// Check for changes that may have happened between render and mount.
|
||||
const newElement = store.getElement(id);
|
||||
const newElement = store.getElementByID(id);
|
||||
if (element !== newElement) {
|
||||
setElement(newElement);
|
||||
}
|
||||
|
||||
@@ -674,6 +674,13 @@
|
||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||
|
||||
"@babel/runtime@^7.0.0":
|
||||
version "7.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a"
|
||||
integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/template@^7.1.0", "@babel/template@^7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
|
||||
@@ -8413,11 +8420,14 @@ react-portal@^3.1.0:
|
||||
react-virtualized-auto-sizer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
|
||||
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
|
||||
|
||||
react-window@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.1.1.tgz#8a0cf488c9db19425fb804f118f5aac6227d7fd2"
|
||||
react-window@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.5.0.tgz#3e56b720b97666bce38a9e932bdd238d56e258f1"
|
||||
integrity sha512-55WeZKjMNF5JdCuKghc/H65DBecoeGgH8MOX3CgT7BJ66xb4ITRuXPUlz0qU6r50wetdF/oLhorYBRvKD4Z1IQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.0.0"
|
||||
memoize-one "^3.1.1"
|
||||
|
||||
react@^16.8.0-alpha.1:
|
||||
@@ -8586,6 +8596,11 @@ regenerator-runtime@^0.11.0:
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|
||||
|
||||
regenerator-runtime@^0.12.0:
|
||||
version "0.12.1"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||
|
||||
regenerator-runtime@^0.9.5:
|
||||
version "0.9.6"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029"
|
||||
|
||||
Reference in New Issue
Block a user