Skip to content
This repository has been archived by the owner on Jun 26, 2020. It is now read-only.

Support Iterators/Iterables, Immutable, other inspector improvements #459

Merged
merged 16 commits into from
Apr 19, 2017

Conversation

cmmcleod
Copy link
Contributor

@cmmcleod cmmcleod commented Dec 1, 2016

Features / Improvements:

  • Improve inspection/rendering of nested Arrays
  • Improve inspection of TypedArrays (rendered similarly to Arrays)
  • Improve display of ArrayBuffers and DataViews (Shows size, no deep inspection)
  • Iterables are rendered as <constructor name>(…)
  • Iterables are read only
  • Iterables entries are rendered as lists/arrays
  • Inspecting Iterables/Immutable values is greatly improved (but keyed collections need improving. See below)
  • ES6 Map/Set objects no longer throw an error

Addresses much of Issue #52 and was inspired by PR #149, particularly @gaearon's comment

Code Change Summary:

  • Moved duplicated getIn(base, path) to separate file /agent/getIn.js
  • Refactored /agent/dehydrated.js to improve readability (in addition to enhancements)
  • Added readOnly and uninspectable propteries to meta for dehydrated values where appropriate
  • /fronted/DataView/DataView.js DataItem Component check for uninspectable when rendering
  • Added typed_array, array_buffer, data_view, iterator cases to Propval.js and previewCoplex.js in addition to some refactoring for readbility
  • Added several props to empty elements to test/example to demonstrate changes.

Caveats / For future Improvement

Typed Arrays are set read only in the inspector with these changes - Editing them (in the current released version) would cause them to be converted to objects, but it should be possible to handle this.

Iterators/Iterators are read only for fairly obvious reasons, however it might be reasonable to implement a live-edit feature for built-in iterables (i.e. Map, Set, TypedArray).

Improvement of Keyed iterables/iterators

With these changes the inspector / DataView renders entries of iterators as Arrays of [key, value] arrays i.e. 0: Array[2], 1: Array[2], ... rather than keyA: <value>, keyB: <value>, ....
Determining if an iterable is a keyed iterable is tricky because an iterator entry can be of any value. Simply checking that entries are arrays of length 2 is insufficient because a Set of arrays (see tuplesSet in example/test/target.js) would 'break' this approach. In addition ES6 Map and Immutable.Map allow complex keys and use Same-value-zero equality to determine uniqueness. This means the keys/attribute strings in the inspectors path cannot easily be converted to strings through either toString or JSON.stringify without breaking correctness in some cases.

Requesting Feedback & Help for:

  • Code style, improvements, etc
  • UI/UX/Styling/Color tweaks for Iterables, Typed Arrays, etc?
  • Ideas for handling keyed iterable case
  • Community assistance in testing with other collection libraries e.g. seamless-immutable, etc

Finally
Substantial unit test coverage is on it's way, I just wanted to get some eyeballs on this first!

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Dec 1, 2016

screen shot 2016-11-30 at 10 48 55 pm

screen shot 2016-11-30 at 10 49 14 pm

screen shot 2016-11-30 at 10 49 23 pm

screen shot 2016-11-30 at 10 52 28 pm

screen shot 2016-11-30 at 10 52 47 pm

@gaearon
Copy link
Contributor

gaearon commented Dec 1, 2016

Flow is failing, could you fix that?

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Dec 1, 2016

Yes, forgot to run typecheck. Most of this is fixed in the latest commits, however I'm getting a peculiar failure now:

$ npm run typecheck

> @ typecheck ..../react-devtools
> flow check

node_modules/invariant/invariant.js.flow:3
  3: declare module.exports: (
                   ^ Unexpected token .


Found 1 error

Running node v6.3.0 and npm 3.10.3. Does invariant need to be added to the .flowconfig ignore list?

$ npm ls invariant

└─┬ babel-core@6.3.21
  └─┬ babel-traverse@6.19.0
    └── invariant@2.2.2

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Dec 1, 2016

Running with node v4.6.2 and npm 2.15.11 (same as TravisCI) works fine

}
if (typeof obj[Symbol.iterator] === 'function') {
// Convert iterable to array and return array[index]
return [...obj][attr];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Won't there be n^2 complexity? getIn seems to be called in a loop, and we traversing the container every time it's called.
  2. Will line 25 work with key-value maps, such as decorated records creating properties accessors for its members (so check in line Components displayed as <Unknown ... /> when declaring js variables at the top. #20 will fail)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. getIn was duplicated code moved fro Bridge.js#441 and Agent.js#401 and updated to support iterators. The complexity of the previous implementation hasn't changed (with the exception of spreading the iterable on line 25).

  2. This does work with key-value maps (if they are iterable) with one caveat, in order to support maps where the key is not a primitive the tool must treat the [key, value] entry as an iterable as well resulting in something like this:

myMap: Map(..)
  0: [
    0: <complex key A>
    1:  "some value"
  ]
  1: [
    0: "string key B"
    1:  "some other value"
  ]
  ...

see Improvement of Keyed iterables/iterators in this comment

I hope I'm making sense here.

@gaearon
Copy link
Contributor

gaearon commented Feb 15, 2017

Determining if an iterable is a keyed iterable is tricky because an iterator entry can be of any value

Can you use iterable.entries === iterator? I think this is what we used in React for a while (we are removing support for it though).

@gaearon
Copy link
Contributor

gaearon commented Feb 15, 2017

Can you rebase please? Sorry for the trouble. I'd like to give this a review.

@cmmcleod
Copy link
Contributor Author

Thanks for the iterable.entries suggestion. I just noticed this in React the other day infact, too bad it's being removed.

Will update and add tests (this may take a day or two for me to get around to it)

@gaearon
Copy link
Contributor

gaearon commented Feb 15, 2017

Thanks. Excited about this PR!

@gaearon
Copy link
Contributor

gaearon commented Feb 24, 2017

Ping, do you think you'll have time to finish this?

@cmmcleod
Copy link
Contributor Author

Yeah, pretty confident I'll have this updated by the end of this weekend!

@gaearon
Copy link
Contributor

gaearon commented Feb 24, 2017

Awesome.

@cmmcleod
Copy link
Contributor Author

Fixed the conflicts.

Looking into using iterable.entries for Map types

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Mar 2, 2017

objMap: new Map([
    [{ a: 'a'}, true],
    [{ a: 'a'}, false],
    [{ b: 'b'}, [1, 2, 3]],
    [{ c: 'c'}, { k1: 'v1', k2: 'v2' }],
  ])

screen shot 2017-03-02 at 10 13 37

This looks ok I suppose, but not necessarily the most helpful for dev/debugging since you cannot directly identify the key. However if you are using Objects as keys then presumably you would typically be using other/additional debugging techniques..

@gaearon
Copy link
Contributor

gaearon commented Mar 2, 2017

How does Chrome display this?

@gaearon
Copy link
Contributor

gaearon commented Mar 2, 2017

Hmm, maybe we should just do this instead.

screen shot 2017-03-02 at 6 23 26 pm

What do you think? How hard would this be?

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Mar 2, 2017

Pretty straightforward actually:

screen shot 2017-03-02 at 10 36 06

@gaearon
Copy link
Contributor

gaearon commented Mar 2, 2017

Nice! If you could just add the [[Entries]] thing to make it clear what's going on, that would be rad.

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Mar 2, 2017

Cool, have this working now, but I'm just investigating/fixing a bug for nested iterables. Will update once I've fix it.

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Mar 2, 2017

Hmm.. So nesting keyed entries under [[Entries]] has opened up a can of worms because it breaks the way that getIn is expected to work.

I.e. getIn is expected to traverse the 'natural' path, but since we are adding [[Entries]] upon inspection this kind of throws a spanner in the works which means getIn becomes rather hacky and brittle.

I think either the initial implementation (without entries support) or the implementation mentioned in this comment are saner options, but I'm open to suggestions.

@gaearon
Copy link
Contributor

gaearon commented Mar 2, 2017

OK, let's display it in tuples, like you did originally.

Copy link
Contributor

@jaredly jaredly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really well written! Thanks for putting this together. I'm really glad it's able to accommodate e.g. Immutable without adding special cases for it.
I'd like to see us limit array & iterable expansion, but it's not a showstopper

if (val && typeof val[Symbol.iterator] === 'function') {
var iterVal = Object.create({}); // flow throws "object literal incompatible with object type"
var count = 0;
for (const entry of val) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will get fairly unhappy for long lists. Can we bail after ~100 and indicate that there's more we're not showing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can probably be done as a separate diff, I just would rather an extension update not make people's apps suddenly really slow under debugging

return 'array_buffer';
}

var type = typeof data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have a slight preference for doing this first, as all of the above checks are only if typeof -> 'object'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's a good point, easily refactored. Will do

if (level > 2) {
return createDehydrated(type, data, cleaned, path);
}
return data.map((item, i) => dehydrate(item, cleaned, path.concat([i]), level + 1));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would also be nice to limit to e.g. 100, but I'm fine w/ just putting a TODO

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a fixed limit seems like a reasonable (but temporary) compromise.

I think the more ideal solution would be to add an optional breadth argument (defaulting to some small number (20-30) and then adding a show more... link/button in the interface which would increase the breadth and inspect+render the next 'page' of items (regardless of whether the inspected object is a iterable, array, map, etc).

I'm happy to update the PR with a hardcoded limit and add a TODO

@gaearon
Copy link
Contributor

gaearon commented Mar 8, 2017

@jaredly Thank you very much for reviewing this.

@cmmcleod Let’s land this after:

  • Hardcoding a limit on 100 items with a temporary (Only first 100 items were shown) in UI or something like this.
  • Refactoring the typeof thing.

@cmmcleod
Copy link
Contributor Author

cmmcleod commented Mar 8, 2017

Sounds good. Will get this done and updated tonight

@cmmcleod
Copy link
Contributor Author

Took me a lot longer to get around to this than I would have liked!

  • Refactored typeof in dehydrate.js
  • Added hardcoded limit and TODO.

Unfortunately I didn't find any simple mechanism to add (Only first 100 items were shown), but I've been working on a better solution which I'd like to submit in a separate PR (click to load more type of solution).

@gaearon
Copy link
Contributor

gaearon commented Mar 22, 2017

Could you fix lint please? Something minor:

/home/travis/build/facebook/react-devtools/test/example/target.js
  454:12  error  'i' is already declared in the upper scope  no-shadow
  508:14  error  'i' is already declared in the upper scope  no-shadow

@gaearon
Copy link
Contributor

gaearon commented Mar 27, 2017

Just a heads up: I'm in the middle of some other work but I'll get back to this as soon as I can. Likely within one or two weeks.

@gaearon
Copy link
Contributor

gaearon commented Apr 19, 2017

One potentially confusing thing I noticed is it still lets you edit stuff inside the entries (but this won’t work since they’re not real arrays).

@gaearon
Copy link
Contributor

gaearon commented Apr 19, 2017

Not going to hold this any longer. 😄
This is an incredible PR, thank you so much!

I’ll create a new issue for the followup work.

@gaearon gaearon merged commit 361bc08 into facebook:master Apr 19, 2017
@gaearon
Copy link
Contributor

gaearon commented Apr 19, 2017

Please see #608 for followup items.

Let me know if you’d like to work on these! I’d be happy to give you contributor permissions if that would help you move faster.

@cmmcleod
Copy link
Contributor Author

Thanks for the merge! I'm definitely up for looking into some of the remaining issues and will follow up on #608.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants