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

Allow QueryManager to intercept hook functionality #11617

Merged
merged 17 commits into from Mar 5, 2024
Merged

Conversation

phryneas
Copy link
Member

@phryneas phryneas commented Feb 23, 2024

This is an alternative to #11615

I considered adding this as a property of the global object, or just a property on context, but it seemed most fitting that an Apollo Client instance can add logic to the hooks. This was an argument especially against context, as a hook can be called with a client argument and shouldn't be behaving differently in that case.

The benefit of this approach in general is that hooks can be overwritten without having to actually import the hooks - making them tree-shakable.
With the old approach, if useClient would be overwritten, it would always need to be bundled, even if the hook might not be used at all in a modern code base.

@phryneas phryneas requested a review from a team as a code owner February 23, 2024 11:23
Copy link

changeset-bot bot commented Feb 23, 2024

🦋 Changeset detected

Latest commit: 682aa45

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@phryneas phryneas marked this pull request as draft February 23, 2024 11:23
Copy link
Contributor

github-actions bot commented Feb 23, 2024

size-limit report 📦

Path Size
dist/apollo-client.min.cjs 38.29 KB (+0.35% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/main.cjs" 46.11 KB (+0.31% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/main.cjs" (production) 43.65 KB (+0.33% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/index.js" 33.9 KB (0%)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/index.js" (production) 31.82 KB (0%)
import { ApolloProvider } from "dist/react/index.js" 1.23 KB (0%)
import { ApolloProvider } from "dist/react/index.js" (production) 1.22 KB (0%)
import { useQuery } from "dist/react/index.js" 5.26 KB (+1.26% 🔺)
import { useQuery } from "dist/react/index.js" (production) 4.35 KB (+1.84% 🔺)
import { useLazyQuery } from "dist/react/index.js" 5.5 KB (0%)
import { useLazyQuery } from "dist/react/index.js" (production) 4.58 KB (0%)
import { useMutation } from "dist/react/index.js" 3.51 KB (0%)
import { useMutation } from "dist/react/index.js" (production) 2.73 KB (0%)
import { useSubscription } from "dist/react/index.js" 3.19 KB (0%)
import { useSubscription } from "dist/react/index.js" (production) 2.38 KB (0%)
import { useSuspenseQuery } from "dist/react/index.js" 5.36 KB (+1.5% 🔺)
import { useSuspenseQuery } from "dist/react/index.js" (production) 4.02 KB (+2.09% 🔺)
import { useBackgroundQuery } from "dist/react/index.js" 4.83 KB (+1.59% 🔺)
import { useBackgroundQuery } from "dist/react/index.js" (production) 3.48 KB (+2.56% 🔺)
import { useLoadableQuery } from "dist/react/index.js" 4.97 KB (+0.04% 🔺)
import { useLoadableQuery } from "dist/react/index.js" (production) 3.63 KB (0%)
import { useReadQuery } from "dist/react/index.js" 3.11 KB (+2.61% 🔺)
import { useReadQuery } from "dist/react/index.js" (production) 3.06 KB (+2.6% 🔺)
import { useFragment } from "dist/react/index.js" 2.25 KB (+3.27% 🔺)
import { useFragment } from "dist/react/index.js" (production) 2.2 KB (+3.4% 🔺)

Copy link

netlify bot commented Feb 23, 2024

Deploy Preview for apollo-client-docs ready!

Name Link
🔨 Latest commit 682aa45
🔍 Latest deploy log https://app.netlify.com/sites/apollo-client-docs/deploys/65e6e5548f4e0f00085b4109
😎 Deploy Preview https://deploy-preview-11617--apollo-client-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240223113922.

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240223125210.

Comment on lines 255 to 256
// @ts-expect-error Cannot assign to 'useBackgroundQuery' because it is a function.ts(2630)
useBackgroundQuery = wrapped;
Copy link
Member Author

Choose a reason for hiding this comment

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

TS really doesn't like this.

On the other hand, this is valid JS - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function
image

@phryneas

This comment was marked as outdated.

@phryneas

This comment was marked as outdated.

This comment was marked as outdated.

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240223174232.

Copy link
Member

@jerelmiller jerelmiller left a comment

Choose a reason for hiding this comment

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

💯

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240226102810.

@phryneas
Copy link
Member Author

I don't understand why bundling useReadQuery now pulls in useSuspenseQuery and a lot of other code, so I've opened a discussion over here in bundlejs: okikio/bundlejs#69

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240226143959.

@phryneas
Copy link
Member Author

The tree-shaking is solved now - I had some help from @Andarist to find a pattern that works here.

This pattern is admittedly quite ugly, but it works along all bundlers and should be very solid.

@jerelmiller could you please take another look?

Copy link
Member

@jerelmiller jerelmiller left a comment

Choose a reason for hiding this comment

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

Had one question about the implementation but I'm liking where this is going!

Comment on lines 175 to 182
useBackgroundQuery = makeHookWrappable(
"useBackgroundQuery",
(_, options) =>
useApolloClient(typeof options === "object" ? options.client : undefined),
_useBackgroundQuery as any
);

return useBackgroundQuery.apply(null, arguments as any);
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps I'm missing something obvious or its been a long travel day, but couldn't you just set this to any variable name and call it? Any reason you need to override the original function?

Suggested change
useBackgroundQuery = makeHookWrappable(
"useBackgroundQuery",
(_, options) =>
useApolloClient(typeof options === "object" ? options.client : undefined),
_useBackgroundQuery as any
);
return useBackgroundQuery.apply(null, arguments as any);
const uBQ = makeHookWrappable(
"useBackgroundQuery",
(_, options) =>
useApolloClient(typeof options === "object" ? options.client : undefined),
_useBackgroundQuery as any
);
return uBQ.apply(null, arguments as any);

or if if you wanted to do it all inline

return makeHookWrappable(
  "useBackgroundQuery",
  (_, options) =>
    useApolloClient(typeof options === "object" ? options.client : undefined),
  _useBackgroundQuery as any
).apply(null, arguments as any);

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 based on a babel pattern:

function _extends() {
  _extends = Object.assign
    ? Object.assign.bind()
    : function (target) {
        for (var i = 1; i < arguments.length; i++) {
          var source = arguments[i];
          for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)) {
              target[key] = source[key];
            }
          }
        }
        return target;
      };
  return _extends.apply(this, arguments);
}
var a = _extends({}, b);

Essentially, it's a lazy override pattern, so makeHookWrappable wouldn't have to be called again in the future.

But, one sleep later, I agree with you that this can be simplified - we are not on a hot path here (something is seriously wrong if we ever are), so we might as well optimize a bit more for readability and amount of code.

I still prefer the first approach we had, overriding the hooks after creation, as that way we didn't have to touch their contents. But as we don't have that option, I believe what I've ended up doing now should be one of the simpler possible solutions. Can you please take another look?

@phryneas
Copy link
Member Author

/release:pr

Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/client@0.0.0-pr-11617-20240227102358.

@phryneas
Copy link
Member Author

phryneas commented Feb 27, 2024

The (unminified) diff for bundling useReadQuery with esbuild now comes down to this, which I'm pretty okay with.

> var wrapperSymbol = Symbol.for("apollo.hook.wrappers");
> function wrapHook(hookName, useHook, clientOrObsQuery) {
>   var queryManager = clientOrObsQuery["queryManager"];
>   var wrappers = queryManager && queryManager[wrapperSymbol];
>   var wrapper = wrappers && wrappers[hookName];
>   return wrapper ? wrapper(useHook) : useHook;
> }
> 
---
495a506,512
>   return wrapHook(
>     "useReadQuery",
>     _useReadQuery,
>     unwrapQueryRef(queryRef)["observable"]
>   )(queryRef);
> }
> function _useReadQuery(queryRef) {

Comment on lines +11 to +13
const removeComments = cleanup({
comments: ["some", /#__PURE__/, /#__NO_SIDE_EFFECTS__/],
});
Copy link
Member Author

Choose a reason for hiding this comment

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

We don't strictly need this here anymore in this specific case, but it would be good to have this in our bundling config anyways, so I'll leave it here.

@phryneas phryneas changed the title Allow Apollo Client instance to intercept hook functionality Allow QueryManager instance to intercept hook functionality Feb 27, 2024
@phryneas phryneas changed the title Allow QueryManager instance to intercept hook functionality Allow QueryManager to intercept hook functionality Feb 27, 2024
@phryneas phryneas marked this pull request as ready for review February 27, 2024 10:56
Copy link
Member

@jerelmiller jerelmiller left a comment

Choose a reason for hiding this comment

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

Nice work! I have to say, I like the wrapHook name a bit more 🙂

src/react/hooks/internal/wrapHook.ts Show resolved Hide resolved
@phryneas phryneas merged commit f1d8bc4 into main Mar 5, 2024
35 checks passed
@phryneas phryneas deleted the pr/wrapHooks-2 branch March 5, 2024 11:45
@github-actions github-actions bot mentioned this pull request Mar 5, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants