Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting hook variable names in devtools extension #115

Closed
wants to merge 17 commits into from

Conversation

vibhorgupta-gh
Copy link
Collaborator

@vibhorgupta-gh vibhorgupta-gh commented Nov 21, 2020

Solves #72 #80 #116 #117 #118 #119

To obtain the names of the hooks, we adopt the following approach -

  1. Inject a function in the Devtools Backend (injectHookVariableNamesFunction)
  2. Obtain the information of hooks by modifying the stackTrace in the Devtools Backend
  3. Load the source maps and source code for the hook under observation
  4. Parse the AST of the source code and isolate the AST node for the hook under observation
  5. Extract the variable name for the hook from its AST node

final

@vibhorgupta-gh vibhorgupta-gh changed the title Injecting hook variable names in devtools [WIP]: Injecting hook variable names in devtools Nov 21, 2020
@vibhorgupta-gh vibhorgupta-gh changed the title [WIP]: Injecting hook variable names in devtools [WIP]: Supporting hook variable names in devtools extension Nov 21, 2020
@vibhorgupta-gh vibhorgupta-gh force-pushed the lineNumberOfHooks branch 2 times, most recently from 7401fb3 to dd932fe Compare November 30, 2020 18:04
Copy link
Collaborator

@bvaughn bvaughn left a comment

Choose a reason for hiding this comment

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

Just did a quick read through and left a few thoughts. Exciting progress!

packages/react-devtools-extensions/src/main.js Outdated Show resolved Hide resolved
packages/react-devtools-extensions/src/main.js Outdated Show resolved Hide resolved
packages/react-devtools-extensions/src/main.js Outdated Show resolved Hide resolved
packages/react-devtools-extensions/src/utils.js Outdated Show resolved Hide resolved
packages/react-devtools-shared/package.json Outdated Show resolved Hide resolved
packages/react-devtools-shared/package.json Show resolved Hide resolved
packages/react-devtools-shared/package.json Show resolved Hide resolved
packages/react-devtools-extensions/src/main.js Outdated Show resolved Hide resolved
vibhorgupta-gh and others added 2 commits December 1, 2020 02:39
1. Pass hookVariableName as a param with Hooks
2. Ignore non declarative primitive hooks
vibhorgupta-gh and others added 3 commits December 1, 2020 20:41
Changed WASM to refer to local copy of mappings.wasm
Warn only for Dev mode
@vibhorgupta-gh
Copy link
Collaborator Author

vibhorgupta-gh commented Dec 17, 2020

Hi @bvaughn, some updates:

  • Fixed existing tests in inspectedElementContext-test.js
  • Wrote unit tests for all hook declaration cases we could think of (sans tests which check for names being injected in sub hooks, still figuring out the best way to do those)
  • Wrote snapshot tests for all cases we could think of (again, sans the ones involving injecting names in sub hooks)

We had some questions:

  • Regarding end to end testing, The way we are doing it right now is basically defining a hook log (we picked the one we used to test in our sample app) and setting the appropriate source file name. We pick and drop the bundle file and the source map file from our systems into the resources folder. We then mock the fetch function and call injectHookVariableNamesFunction with this hook log. Not sure if this is the best method though, we'd ideally like there to be some sort of generator script that runs in a beforeAll block that just takes a sample React file in resources and uses webpack to generate the bundle and source map on runtime which we can utilize (instead of hardcoding these resources). Our progress in case of end to end tests is still ongoing in commit 3c08354. Commits prior to these (unit/snapshot tests) are mostly done. If you could review those and try breaking these test suites for some cases that we might have missed, that'd be helpful next steps.
  • The tests in packages/react-debug-tools/src/__tests__/ are currently failing as well, but we know what causes the fail, the hook tests in the file use an outdated structure of each hook node in hook log, updating which should fix everything. Shall we go ahead with the fix there?

@mknmohit
Copy link

mknmohit commented May 5, 2021

Excited to see this useful feature in future. Much appreciated your efforts 👍

@ankursnh023
Copy link

Any updates when this feature will be released?
@bvaughn @VibhorCodecianGupta

@bvaughn
Copy link
Collaborator

bvaughn commented Jun 7, 2021

I'm going to try to rebase this branch now. Looks like it's got a few conflicts. Hopefully it won't be too crazy 😅

The logic around suspending to inspect the selected element has changed a lot in recent months, to improve the Suspense/cache integration, so there are non-trivial conflicts in InspectedElementContext.

@bvaughn
Copy link
Collaborator

bvaughn commented Jun 7, 2021

  • Regarding end to end testing, The way we are doing it right now is basically defining a hook log (we picked the one we used to test in our sample app) and setting the appropriate source file name. We pick and drop the bundle file and the source map file from our systems into the resources folder. We then mock the fetch function and call injectHookVariableNamesFunction with this hook log. Not sure if this is the best method though, we'd ideally like there to be some sort of generator script that runs in a beforeAll block that just takes a sample React file in resources and uses webpack to generate the bundle and source map on runtime which we can utilize (instead of hardcoding these resources).

@VibhorCodecianGupta Agreed that this would be nicer than embedding the bundle.js file. That static file is a little confusing (how do I update it? Where is the source of truth?) and will likely cause problems for our Prettier and Lint tooling unless we ignore it, but really ideally we'd just generate it lazily as part of testing 😄 Assuming this wasn't super slow?

@bvaughn
Copy link
Collaborator

bvaughn commented Jun 7, 2021

I've merged master into this branch and resolved a lot of conflicts, but some of them are pretty large (like in the inspected element context) so I'm going to be working on merging/rewriting that section for a little bit. For now, I'm going to be pushing my changes to a separate branch: facebook#21641

If either @saphal1998 or @VibhorCodecianGupta would be interested in talking through some of these changes, I'd be happy to. I know it's been a while though and the MLH program is over, so if you're both too busy– I understand that as well!

Thanks for the work you did putting this PR together. :)

function getSourceMapURL(url: string, urlResponse: string): string {
const sourceMappingUrlRegExp = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg
const sourceMappingURLInArray = urlResponse.match(sourceMappingUrlRegExp);
if (sourceMappingURLInArray && sourceMappingURLInArray.length > 1) {
Copy link
Collaborator

@bvaughn bvaughn Jun 8, 2021

Choose a reason for hiding this comment

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

Code Sandbox uses base64 encoded source maps. So maybe we could have a branch here that loads the file or decodes the inline source map using atob()

@saphal1998
Copy link
Member

Hi @bvaughn! We would love to help out in any way we can!

Unfortunately though, both @VibhorCodecianGupta and I, have lost context of the nitty gritty of the work done, we'll just go over it this PR again this weekend, to get some context back so that we are in better position to give inputs.

Can you let us know if you have any specific things you want us to look into so that we can have a more focussed discussion on this?

@bvaughn
Copy link
Collaborator

bvaughn commented Jun 11, 2021

Hi @saphal1998!

I've opened a PR against the React repo over here: facebook#21641

I've jotted down a checklist of things to do or look at there. I'll just be chipping away at them. If there's anything you or @VibhorCodecianGupta would be interested in picking up, let's just chat and you could maybe open a PR to my branch 😄

@bvaughn
Copy link
Collaborator

bvaughn commented Jun 17, 2021

Hi again @saphal1998 and @VibhorCodecianGupta 😁

I've added some additional unit tests here:
https://github.com/bvaughn/react/blob/devtools-named-hooks/packages/react-devtools-extensions/src/__tests__/parseHookNames-test.js

Which includes some tests that use inline and external source maps (using this test component compiled by this script).

Unfortunately the test fails b'c there's a line number mismatch between the compiled source and source-map parsed original source, which causes the useState hook inside of useCustomHook to fail:

function useCustomHook() {
  const [stateValue] = useState(true);

  return stateValue;
}

~~I'm a little stumped and would welcome any ideas you two have 😄 ~~

Edit It seems that the majority of this was due to a Jest runner opting Errors into automatically taking source maps into accountability. This makes sense for testing simplicity in most cases, but was horribly breaking the logic in these tests specifically, so this behavior was disabled in facebook@9f4627f.

@@ -516,13 +529,22 @@ function buildTree(rootStack, readHookLog): HooksTree {

// For the time being, only State and Reducer hooks support runtime overrides.
const isStateEditable = primitive === 'Reducer' || primitive === 'State';
const hookSource: HookSource = {lineNumber: null, functionName: null, fileName: null, columnNumber: null}
if (stack && stack.length >= 1) {

Choose a reason for hiding this comment

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

OR

Suggested change
if (stack && stack.length >= 1) {
if (stack?.length) {

@@ -172,6 +172,7 @@ function HookView({

let displayValue;
let isComplexDisplayValue = false;
const customHookDisplayName = hookVariableName ? `${name}(${hookVariableName})` : name;

Choose a reason for hiding this comment

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

Nice 💯

value: mixed,
subHooks: Array<HooksNode>,
hookSource: HookSource,
...

Choose a reason for hiding this comment

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

I am just curious to know why you have spread operator here, I mean what's it's doing here? 🤔

@bvaughn
Copy link
Collaborator

bvaughn commented Jul 1, 2021

This feature has just been merged into the React main branch via facebook#21641

Thank you for all of your hard work! 🥳

@bvaughn bvaughn closed this Jul 1, 2021
@bvaughn bvaughn deleted the lineNumberOfHooks branch July 1, 2021 18:39
@cmacdonnacha
Copy link

Excellent work @bvaughn . How do we go about getting the latest update? Is it just a matter of updating the chrome extension?

@bvaughn
Copy link
Collaborator

bvaughn commented Jul 1, 2021

It will probably be released with the next major DevTools version, once it's been tested inside of Facebook for a bit. This is a pretty big feature and I want to give it a little of time to smoke test with a few hundred engineers before pushing it out to millions.

@saphal1998
Copy link
Member

@bvaughn thanks a lot for picking this up! We have not been able to give this any time at all due to other prior commitments, so we really appreciate this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment