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

Refactor createRequest parsing and hashing #383

Merged
merged 5 commits into from
Aug 12, 2019

Conversation

kitten
Copy link
Member

@kitten kitten commented Aug 9, 2019

Fix #377
Supersedes #380 by also refactoring hashing

@kitten kitten force-pushed the refactor/hashing-parsing-documents branch from 016f372 to f63e197 Compare August 10, 2019 22:15
@@ -135,8 +135,6 @@
},
"dependencies": {
"fast-json-stable-stringify": "^2.0.0",
"graphql-tag": "^2.10.1",
"prop-types": "^15.6.0",
Copy link
Contributor

Choose a reason for hiding this comment

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

Glad we got rid of prop-types too!

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, I've noticed we weren't using it and we probably just forgot to remove it

let key: number;
let query: DocumentNode;
if (typeof q === 'string') {
key = hash(q.replace(/[\s,]+/, ' ').trim());
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm reading this correctly, this RegExp will just replace the first whitespace character or , it encounters (or both if they're adjacent) with a whitespace character. Can you explain a bit more what purpose that's serving? I'm reading through the hash code above to try to get more context but admittedly bitwise operators are not my strong suit .

Copy link
Member Author

Choose a reason for hiding this comment

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

This is borrowed from graphql-tag itself and meant to stabilise hashes. Differently formatted queries will yield the same hash when the formatting is applied, so the intention is for two queries to have the same hash, even when they've been parsed & stringified or formatted through other means

Copy link
Member Author

Choose a reason for hiding this comment

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

this RegExp will just replace the first whitespace character or , it encounters

You're right, I misread your comment. Sorry! That was a copy&paste issue. I've just fixed it in the latest commit

@andyrichardson
Copy link
Contributor

This looks great! So this gets rid of GraphQL tag and also ensures that we have referential equality on document nodes with identical keys right?

Also, what are the changes to the hash creation - different/optimized algo or something functionally different?

@kitten
Copy link
Member Author

kitten commented Aug 11, 2019

@andyrichardson yep, this basically moves the logic that we've been relying on for parsing to our own codebase, so referential equality for two identically parsed queries is still guaranteed.

We had some duplication around that with graphql-tag as well and it just didn't feel right to have that in the repo in a half-baked state while also relying on graphql-tag

This will also have the added bonus of our bundle appearing to be smaller on bubdlephobia. This is of course not always guaranteed since some or most users will still rely on graphql-tag and pull that into their bundle. But since some are very interested in urql due to its attention to bundlesize, this will definitely help.

The hashing method is the exact same. It's still using djb2 for its simplicity and speed. I've applied the same trick to it however that I have over on styled-components. The hashing function is split into the usual "hash" function and a "phash" (read: partial hash) function.

This is a micro optimisation but essentially allows us to hash once and modify the hashed number with more string inputs. So that allows us to skip an extra step of stringifiying the query hash to add the variables hash on top.

Edit: Sorry! Found a small inconsistency when hashing DocumentNodes vs strings. That's fixed and tested now. It's an uncommon edgecase though

Copy link
Collaborator

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

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

This looks superb! 💯

if (typeof q === 'string') {
key = hashQuery(q);
query = docs[key] !== undefined ? docs[key] : parse(q);
} else if ((q as any)[keyProp] !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm not mistaken, I believe we can get rid of all of these any type coercions. At this point in the branching, q should be correctly inferred as being of type DocumentNode.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is but the keyProp is our own property which is therefore not typed. I could of course cast it but casting to an interface here or any is essentially the same and doesn't make a difference as it's just for this function

Copy link
Contributor

@parkerziegler parkerziegler left a comment

Choose a reason for hiding this comment

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

Looks good to me! Cool to learn a bit more about our key hashing ✅

@kitten kitten merged commit e781a28 into master Aug 12, 2019
@kitten kitten deleted the refactor/hashing-parsing-documents branch August 12, 2019 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove dependency on graphql-tag
4 participants