-
Notifications
You must be signed in to change notification settings - Fork 47k
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
Improve hook names performance with extended source maps #21782
Comments
Hi @bvaughn, Can I give this a try? |
@houssemchebeb Awesome! Have you worked with source map extensions before? This will be an un-guided task (since I don't have any experience working with this stuff yet). We'd be happy to have your help on this! Just checking to make sure that the task is a good fit. :) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I'd like to propose an approach and offer some pointers. This is based on a conversation I had earlier with @bvaughn and some work I did a while ago on source maps in React Native and Metro. Context: Function maps in MetroFor React Native, we wanted to reliably "unminify" function names in stack traces, and even show inferred names for anonymous functions, without re-parsing the original code and without increasing the size of our production bundles. We came up with a way to store extra information in a bundle's source map and used it to store function maps generated from all the source files at compile time. This turns out to be similar to the problem of inferring hook names at build time, so let me first describe our solution for function names in detail. Step 1: AST → function mapA function map is an ordered list of (line, column, name) tuples describing the local function name at every location in a file. For example, if our code is function a(){} function b(){} Then our function map may be
(The function name between mappings in the list is the most recent function name encountered before that location.) We generate a function map by traversing the AST, maintaining a stack of function nodes, and recording a tuple every time we enter or exit a function. This is where we run various rules for inferring a useful name for a function given its AST node. (In Metro, we do this at the same time we transform each file with Babel, so we have easy access to the source AST.) Step 2: Function map → encoded function mapWe use a size-optimised encoding for function maps to avoid bloating the source maps unnecessarily. This is designed to be similar to how the Step 3: Encoded function maps → extended source mapWe collect all the encoded function maps in the Entry 0 in each metadata tuple is the encoded function map. No other entries are currently defined. Step 4: Lookup in an extended source mapStarting with a location in compiled code, we find the (standard) source mapping that describes it. This gives us an offset into the The description above is a little simplified: In Metro, we use the Proposal: React Hook mapsThe above solution for function maps can be adapted to implement "Hook maps", a new type of source metadata that will be understood by React DevTools. Step 1: AST -> Hook map
If names and source locations are all we need to store, it might be possible to use the same basic data structure for the hook map as for function maps: an ordered list of (line, column, name) tuples describing where each hook starts (and ends = where the next hook starts). We can use an out-of-bounds index (e.g. -1) to represent "no hook". The code for this should live in the React repo, probably in one of the DevTools packages. Step 2: Hook map -> encoded Hook map
Step 3: Encoded Hook maps → extended source map
This step will need to be done separately for each bundler we want to support, but it should reuse all the meaningful logic from steps 1 and 2. See also the "Seamless Babel integration" section below. Step 4: Lookup in an extended source map
When implementing this, check out this comment about normalising source paths - the default logic in SourceMetadataMapConsumer matches
Future: Seamless Babel integrationWriting plugins for all popular bundlers (for step 3) is doable, but getting the long tail of React developers to install these plugins - just to get a better DevTools experience for this one feature (for now) - might be difficult. Maybe doing this work for I don't have very specific ideas here, but I think the most sustainable approach would require some modifications to Babel and to the major bundlers. Basically, we could standardise a version of One possible conceptual tweak to this proposal is to store an opaque, extensible "React DevTools metadata blob" instead of specifically a "Hook map". This is kind of the same extensibility gambit I was going for with |
Thank you for the helpful, detailed write up, @motiz88! One thing is a little unclear to me. Are you literally suggesting that we reuse the |
|
@bvaughn Yes sure i'd be interested to take a up a small chunk of it. |
@mdaj06 What's your experience level with ASTs and source maps? (I ask because this issue would have to be mostly self-guided.) Moti's comment above (#21782 (comment)) outlines a good general strategy here. Would you be interested in tackling the first item? I believe @jstejada is also interested in working on this so we'll ideally want to coordinate any efforts between you two, if you are interested! |
@bvaughn i have a general understanding of ASTs and traversing them. I can research by myself for the bits where im stuck. "Step 1: AST → function map" this would be first item, if i am not wrong? if so sure i can start looking into it. |
That's right, yes |
@mdaj06 I posted a DevTools contributing guide this afternoon, in case it would be helpful to you. |
@bvaughn Sure thanks will have a look at it! |
I'll let you two chat then. |
hey @mdaj06! just wanted to check in with you on this task. I'm out next week, but I'm planning to start working on this pretty much full-time starting on July 26. I was wondering if you already got started on this task or made any progress on any of the items above? happy to connect and sync up to figure out how best we could approach collaborating on this, if you are still interested. thanks! |
Hey @jstejada i have started looking into the first item will update you on the progress soon! Let me know how we can collaborate on this task. |
…22010) ## Summary Adds support for statically extracting names for hook calls from source code, and extending source maps with that information so that DevTools does not have to directly parse source code at runtime, which will speed up the Named Hooks feature and allow it to be enabled by default. Specifically, this PR includes the following parts: - [x] Adding logic to statically extract relevant hook names from the parsed source code (i.e. the babel ast). Note that this logic differs slightly from the existing logic in that the existing logic also uses runtime information from DevTools (such as whether given hooks are a custom hook) to extract names for hooks, whereas this code is meant to run entirely at build time, so it does not rely on that information. - [x] Generating an encoded "hook map", which encodes the information about a hooks *original* source location, and it's corresponding name. This "hook map" will be used to generate extended source maps, included tentatively under an extra `x_react_hook_map` field. The map itself is formatted and encoded in a very similar way as how the `names` and `mappings` fields of a standard source map are encoded ( = Base64 VLQ delta coding representing offsets into a string array), and how the "function map" in Metro is encoded, as suggested in #21782. Note that this initial version uses a very basic format, and we are not implementing our own custom encoding, but reusing the `encode` function from `sourcemap-codec`. - [x] Updating the logic in `parseHookNames` to check if the source maps have been extended with the hook map information, and if so use that information to extract the hook names without loading the original source code. In this PR we are manually generating extended source maps in our tests in order to test that this functionality works as expected, even though we are not actually generating the extended source maps in production. The second stage of this work, which will likely need to occur outside this repo, is to update bundlers such as Metro to use these new primitives to actually generate source maps that DevTools can use. ### Follow-ups - Enable named hooks by default when extended source maps are present - Support looking up hook names when column numbers are not present in source map. - Measure performance improvement of using extended source maps (manual testing suggests ~4 to 5x faster) - Update relevant bundlers to generate extended source maps. ## Test Plan - yarn flow - Tests still pass - yarn test - yarn test-build-devtools - Named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, facebook). - For new functionality: - New tests for statically extracting hook names. - New tests for using extended source maps to look up hook names at runtime.
…fferent formats (#22096) ## Summary Follow up from #22010. The initial implementation of named hooks and for looking up hook name metadata in an extended source map both assumed that the source maps would always have a `sources` field available, and didn't account for the source maps in the [Index Map](https://sourcemaps.info/spec.html#h.535es3xeprgt) format, which contain a list of `sections` and don't have the `source` field available directly. In order to properly access metadata in extended source maps, this commit: - Adds a new `SourceMapMetadataConsumer` api, which is a fork / very similar in structure to the corresponding [consumer in Metro](https://github.com/facebook/metro/blob/2b44ec39b4bca93e3e1cf1f268b4be66f894924a/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L56) (as specified by @motiz88 in #21782. - Updates `parseHookNames` to use this new api ## Test Plan - yarn flow - yarn test - yarn test-build-devtools - added new regression tests covering the index map format - named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
…acebook#22010) ## Summary Adds support for statically extracting names for hook calls from source code, and extending source maps with that information so that DevTools does not have to directly parse source code at runtime, which will speed up the Named Hooks feature and allow it to be enabled by default. Specifically, this PR includes the following parts: - [x] Adding logic to statically extract relevant hook names from the parsed source code (i.e. the babel ast). Note that this logic differs slightly from the existing logic in that the existing logic also uses runtime information from DevTools (such as whether given hooks are a custom hook) to extract names for hooks, whereas this code is meant to run entirely at build time, so it does not rely on that information. - [x] Generating an encoded "hook map", which encodes the information about a hooks *original* source location, and it's corresponding name. This "hook map" will be used to generate extended source maps, included tentatively under an extra `x_react_hook_map` field. The map itself is formatted and encoded in a very similar way as how the `names` and `mappings` fields of a standard source map are encoded ( = Base64 VLQ delta coding representing offsets into a string array), and how the "function map" in Metro is encoded, as suggested in facebook#21782. Note that this initial version uses a very basic format, and we are not implementing our own custom encoding, but reusing the `encode` function from `sourcemap-codec`. - [x] Updating the logic in `parseHookNames` to check if the source maps have been extended with the hook map information, and if so use that information to extract the hook names without loading the original source code. In this PR we are manually generating extended source maps in our tests in order to test that this functionality works as expected, even though we are not actually generating the extended source maps in production. The second stage of this work, which will likely need to occur outside this repo, is to update bundlers such as Metro to use these new primitives to actually generate source maps that DevTools can use. ### Follow-ups - Enable named hooks by default when extended source maps are present - Support looking up hook names when column numbers are not present in source map. - Measure performance improvement of using extended source maps (manual testing suggests ~4 to 5x faster) - Update relevant bundlers to generate extended source maps. ## Test Plan - yarn flow - Tests still pass - yarn test - yarn test-build-devtools - Named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, facebook). - For new functionality: - New tests for statically extracting hook names. - New tests for using extended source maps to look up hook names at runtime.
…fferent formats (facebook#22096) ## Summary Follow up from facebook#22010. The initial implementation of named hooks and for looking up hook name metadata in an extended source map both assumed that the source maps would always have a `sources` field available, and didn't account for the source maps in the [Index Map](https://sourcemaps.info/spec.html#h.535es3xeprgt) format, which contain a list of `sections` and don't have the `source` field available directly. In order to properly access metadata in extended source maps, this commit: - Adds a new `SourceMapMetadataConsumer` api, which is a fork / very similar in structure to the corresponding [consumer in Metro](https://github.com/facebook/metro/blob/2b44ec39b4bca93e3e1cf1f268b4be66f894924a/packages/metro-symbolicate/src/SourceMetadataMapConsumer.js#L56) (as specified by @motiz88 in facebook#21782. - Updates `parseHookNames` to use this new api ## Test Plan - yarn flow - yarn test - yarn test-build-devtools - added new regression tests covering the index map format - named hooks still work on manual test of browser extension on a few different apps (code sandbox, create-react-app, internally).
#21641 adds the ability for DevTools to display hook "names". The way this works is:
What if we had a custom transform that inferred these names during compilation and inserted metadata into the source map itself? Then DevTools could bail after step 4 and skip the expensive AST parsing step and the heuristics for inferring hook names.
It could still fall back to the more expensive step if this metadata wasn't available, but this would enable the feature to better support large apps where parsing the AST is prohibitively expensive.
See this comment below for a detailed explanation of how this might be implemented: #21782 (comment)
The text was updated successfully, but these errors were encountered: