Skip to content

Conversation

phryneas
Copy link
Member

@phryneas phryneas commented May 17, 2023

This is just the current WIP status to see what we would generate on publish.

Checklist:

  • If this PR contains changes to the library itself (not necessary for e.g. docs updates), please include a changeset (see CONTRIBUTING.md)
  • If this PR is a new feature, please reference an issue where a consensus about the design was reached (not necessary for small changes)
  • Make sure all of the significant new logic is covered by tests

@changeset-bot
Copy link

changeset-bot bot commented May 17, 2023

🦋 Changeset detected

Latest commit: 4f3b270

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 Minor

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
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

Please add a changeset via npx changeset before attempting a snapshot release.

@github-actions
Copy link
Contributor

github-actions bot commented May 17, 2023

size-limit report 📦

Path Size
dist/apollo-client.min.cjs 36.58 KB (+1.15% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/main.cjs" 42.98 KB (-6.46% 🔽)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/index.js" 32.46 KB (-5.75% 🔽)
import { ApolloProvider } from "dist/react/index.js" 1.31 KB (+24.61% 🔺)
import { useQuery } from "dist/react/index.js" 4.36 KB (-4.3% 🔽)
import { useLazyQuery } from "dist/react/index.js" 4.67 KB (-4.02% 🔽)
import { useMutation } from "dist/react/index.js" 2.58 KB (-5.25% 🔽)
import { useSubscription } from "dist/react/index.js" 2.34 KB (-7.95% 🔽)
import { useSuspenseQuery_experimental } from "dist/react/index.js" 3.46 KB (-6.98% 🔽)
import { useBackgroundQuery_experimental } from "dist/react/index.js" 2.89 KB (-8.05% 🔽)
import { useReadQuery_experimental } from "dist/react/index.js" 1.65 KB (+31.21% 🔺)
import { useFragment_experimental } from "dist/react/index.js" 2.13 KB (+7.45% 🔺)

@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/[email protected].

@phryneas
Copy link
Member Author

The released invariantErrorCodes.js can be found here:

https://cdn.jsdelivr.net/npm/@apollo/[email protected]/invariantErrorCodes.js

It boils down to:

// This file is meant to help with looking up the source of errors like
// "Invariant Violation: 35" and is automatically generated by the file
// @apollo/client/config/processInvariants.ts for each @apollo/client
// release. The numbers may change from release to release, so please
// consult the @apollo/client/invariantErrorCodes.js file specific to
// your @apollo/client version. This file is not meant to be imported.
globalThis["ApolloErrorCodes_0.0.0-pr-10887-20230517113257"] = {
  "@apollo/client version": "0.0.0-pr-10887-20230517113257",

  1: {
    file: "@apollo/client/cache/inmemory/entityStore.js",
    condition: "typeof dataId === \"string\"",
    message: "store.merge expects a string ID"
  },

  2: {
    file: "@apollo/client/cache/inmemory/key-extractor.js",
    condition: "extracted !== void 0",
    message: "Missing field '%s' while extracting keyFields from %s"
  },

  // ...
}

We can now create a HTML "error reader" file that imports this file from CDN and then accesses the error messages via globalThis["ApolloErrorCodes_${version}"][errorCode].message.

@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/[email protected].

@phryneas
Copy link
Member Author

I changed the format of the invariantErrorCodes.js to have a default export - we should be able to use dynamic imports on the browser error side.

I also added a global hook globalThis[Symbol.for('ApolloErrorMessageHandler')] to allow overriding the error formatter - that way we can offer an optional entry point that people can include in their dev builds that directly formats the errors correctly without the external link.

That way, error handling becomes opt-in instead of being opt-out.

@phryneas phryneas force-pushed the pr/error-extraction branch from b305e44 to 36840da Compare May 17, 2023 13:32
@phryneas
Copy link
Member Author

phryneas commented May 17, 2023

@phryneas
Copy link
Member Author

Testing it out locally...
image

And after opting in by

import { loadErrorMessages } from "@apollo/client/utilities/errors";
loadErrorMessages();

image

@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/[email protected].

@phryneas
Copy link
Member Author

phryneas commented May 19, 2023

All error warnings/messages are now extracted and can be loaded by

import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
loadErrorMessages();
// and/or
loadDevMessages();

or, more granularly, by a subset of

import { devDebug, devError, devLog, devWarn, errorCodes } from "@apollo/client/invariantErrorCodes";
import { loadErrorMessageHandler } from "@apollo/client/dev";

loadErrorMessageHandler(devDebug, devError, devLog, devWarn, errorCodes);

But as all errors are still in one shipped file @apollo/client/invariantErrorCodes.js, at least Next.js doesn't seem to tree-shake that nicely. I'll have to play around with that a bit more.

@phryneas
Copy link
Member Author

phryneas commented May 22, 2023

Update: it does treeshake, next.js just doesn't display it nicely on build 🤦

Here are three sizes I got from building a Next.js app (minified, with and without gzip; I have no idea what else is in those bundles, so they are just a comparison to each other, total numbers mean nothing):

250528 bytes, 78909 bytes gzipped: nothing added 
262359 bytes, 82231 bytes gzipped: loadErrorMessages
266499 bytes, 83423 bytes gzipped: loadErrorMessages, loadDevMessages

So in the end, in a "real life app", this change comes down to saving users 16kb (4.5kb gzipped), providing an opt-in solution, not an opt-out one.

Next.js also nicely shakes

import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";
if (process.env.NODE_ENV !== "production") {
  loadErrorMessages();
  loadDevMessages();
}

out on build, so we could document this approach for users.

@phryneas phryneas self-assigned this May 22, 2023
}

for (const codes of errorCodes) {
Object.assign(global[ApolloErrorMessageHandler], codes)
Copy link
Member Author

Choose a reason for hiding this comment

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

The only problem I could maybe see would be if someone would use multiple versions of Apollo Client on the same website, and multiple of them would ship their error messages. In that case, those could override each other.

We could solve that by changing ApolloErrorMessageHandler from Symbol.for(...) to just Symbol() to make that unique per version, or by including version in the Symbol.for string - if you think that is necessary.

Copy link
Member

Choose a reason for hiding this comment

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

I like this idea of making sure its different per version. Sounds like a small lift to make it work and would help us avoid (potentially) opened issues about this in the future.

Copy link
Member Author

Choose a reason for hiding this comment

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

Great - I did that now :)

Comment on lines -30 to -37
class LinkError extends Error {
public link?: ApolloLink;
constructor(message?: string, link?: ApolloLink) {
super(message);
this.link = link;
}
}

Copy link
Member Author

@phryneas phryneas May 22, 2023

Choose a reason for hiding this comment

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

This class was only created to be logged with invariant.warn - it didn't have any more use beyond that.

* pretty-stringified objects.
* Excess `optionalParams` will be swallowed.
*/
function newInvariantError(message?: string | number, ...optionalParams: unknown[]) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I had initially sub-classed the original InvariantError here, but that is much more bundle size (as classes are downlevelled), so I opted for a wrapper function instead.

Copy link
Member

Choose a reason for hiding this comment

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

[nit] Could we just call this invariantError? Not sure we need the new qualifier here. I find it to read a bit nicer:

if (somethingFailed) {
  invariantError('You did it wrong');
}

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 made the change from throw new InvariantError to throw newInvariantError pretty easy - and usually, stuff we throw is a class constructor and needs to be newed, so I fear we would at some point instinctivly start calling the invariantError function with the new keyword.

So I'd opt for keeping it like this, but please tell me if you really can't live with it, then I'll change it :)

@phryneas
Copy link
Member Author

This part should be ready for review - I will have to adjust the HTML template to the latest changes and then will make PRs against the docs & shortlinks in parallel.

@phryneas phryneas marked this pull request as ready for review May 22, 2023 13:50
@phryneas
Copy link
Member Author

/release:pr

@github-actions
Copy link
Contributor

A new release has been made for this PR. You can install it with npm i @apollo/[email protected].

@phryneas
Copy link
Member Author

phryneas commented May 22, 2023

I was just shown this tool and it might actually come in handy when reviewing this PR: https://npmdiff.dev/%40apollo%2Fclient/3.8.0-alpha.15/0.0.0-pr-10887-20230522141246/package/apollo-client.cjs/

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.

This is incredible work! Really appreciate you taking the time to get this in time for v3.8! Apologies for my annoying style comments 🤣.

}

for (const codes of errorCodes) {
Object.assign(global[ApolloErrorMessageHandler], codes)
Copy link
Member

Choose a reason for hiding this comment

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

I like this idea of making sure its different per version. Sounds like a small lift to make it work and would help us avoid (potentially) opened issues about this in the future.

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.

I'm super excited about this. Really appreciate the work you've done here!

@jerelmiller jerelmiller merged commit f8c0b96 into release-3.8 May 26, 2023
@jerelmiller jerelmiller deleted the pr/error-extraction branch May 26, 2023 15:57
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 26, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants