Skip to content

Announcement: urql v4 Major Releases #3114

@kitten

Description

@kitten

It’s only been seven months since we’ve launched our urql v3 batch of major releases, but today, we’re even more excited to talk about our next batch of urql v4 releases!

What happened in the last episode of urql?
Expand for a quick summary of changes made in the prior urql v3 releases.

General changes

  • We dropped IE11 support and modernised our build output
  • We introduced stricter Variables generics typings across our bindings
  • Granular graphql imports have been removed since they caused build issues in rare cases

@urql/core

  • Not all ClientOptions are now reflects as properties on the Client
  • The createOperationContext method was removed from the Client
  • ClientOptions.url must now always be a string and defined

@urql/exchange-graphcache

  • Easier optimistic functions
    • The optimistic functions now may return objects that contain more, nested optimistic functions
    • We now use field names rather than aliases, so it’s easier to define reusable optimistic functions
  • Offline Mode Non-Blocking
    • The offline rehydration is now non-blocking and a network request would always be attempted first


Back in our last batch of major releases, little changed and little broke, relatively speaking. We chose to save up larger refactors and changes for when we'd be able to ship some big improvements along with them; and today's the day!

Symbol Legend:
We have a lot of changes to talk about, therefore, we’re not necessarily
grouping everything by whether it’s breaking or not, and will instead annotate
headings of sections according to whether they’re major, minor, or patch changes.

  • 💥: A major, breaking change, or an addition that may affect how urql used to work
  • ✨: A minor, feature change, which adds a new feature without breaking old code
  • 🦋: A patch, which often only improve what’s going on in the background

Table of Contents

After upgrading with your favourite package manager you may have duplicates of several packages, which you can fix by trying out the following command:

# for npm
npm dedupe
# for pnpm
pnpm dedupe
# for yarn
npx yarn-deduplicate

For the full release notes and changelogs, check the Release PR.


Sponsoring and Discord

In case you've missed it or don't come by the repository often, Hiya 👋 Welcome back!

We're also here to say that we now accept sponsorships on GitHub: https://github.com/sponsors/urql-graphql
Sponsorships are important to us of course, but they will take more of a central role in how this project sustains itself moving forward. We have more moving parts now than ever and want to dedicate as much time to the urql community as possible! 💖

We also now have an easier way to ask help and chat with us and other contributors, by joining our Discord: https://urql.dev/discord
Building a community on Discord allows us to also talk about and support you with our related projects and other libraries, like wonka and graphql-web-lite!

TSDocs... TSDocs everywhere ()

We’re replacing our API docs pages with TSDocs! 📚

Every one of our public API will now come with inline comments, called TSDocs, which give you a summary of what the API does, and they're in our opinion a big step forward from clunky, old API docs:

  • Any editor that supports them (typically via the Language Server Protocol) will show them on hover, alongside type hints. VSCode has especially nice visuals for them.
  • They give us space on every single library export to explain the API, deliver context, point out gotchas, and deliver the right information in the perfect place.
  • They easily link from API to API, so jumping between related types and functions, and reading multiple docs together becomes much easier.

We hope that this will ease the learning curve of having to learn urql's APIs, and reduces common misconceptions and questions from popping up early on, when you're starting to adopt urql.

We're preparing to launch a web app that allows you to browse the APIs of our packages as well. More news on this soon...

No more defaultExchanges (💥)

In prior versions we didn't need you to explicitly pass exchanges to the client we would automatically make these to be [dedupExchange, cacheExchange, fetchExchange].

We have now removed this and need you to explicitly pass us the exchanges, in doing so we have reduced the default budndle size for folks not using these exchanges. You can fix the missing exchanges by doing the following:

import { createClient, cacheExchange, fetchExchange } from '@urql/core'

const client = createClient({
  url: '',
  exchanges: [cacheExchange, fetchExchange],
})

A rewrite of our default fetch/HTTP transport ()

urql's default fetchExchange used to come with a humble but extensible set of features. In @urql/core@^3.0.0, we're moving and adding more features into the core package:

  • Multipart File Uploads are now supported without having to install @urql/exchange-multipart-fetch (a now deprecated package)
  • We now support the text/event-stream protocol (also known as GraphQL SSE for Incremental Delivery responses.
  • While we recently shipped fixes to our Incremental Delivery support, we continued to stabilise and refactor its support.
  • of course, the multipart/mixed response protocol is still supported as before.

With multipart/mixed and text/event-stream, we’re now supporting more protocol options for APIs, which enable APIs to use more transport methods for @defer, @stream, @live, and subscriptions and give API authors more options without having to add custom exchanges to urql.

Persisted Query Support is changing ()

As for, (Automatic) Persisted Queries support, this used to be implemented using the @urql/exchange-persisted-fetch library, which didn’t work for the subscriptionExchange, as it simply made a fetch/HTTP request on its own.

Instead, we’re deprecating the package and replacing it with @urql/exchange-persisted.

The API of this package will match the old one exactly, but it will only annotate query (and optionally, mutation) operations to have persisted query extensions on them.

Source Code

File Upload Support goes built-in ()

Multipart File Uploads are now supported in @urql/core and are activated when your variables contain a File or a Blob.

You won't have to install @urql/exchange-multipart-fetch anymore and this package has been deprecated.

hasNext, stale, and multiple results (💥)

@urql/core contains two subtle yet important changes to how OperationResults are defined and delivered in response to Operations.

In this version we're tightening our guarantees around results that are marked as hasNext: true and when results are marked as stale: true.

Any API transport that delivers multiple results may mark its ExecutionResults with hasNext: true. This for instance happens when:

  • A subscription, which hasn't been closed by your API, delivers new events.
  • A query or mutation are waiting for @defer-ed or @streasm-ed results.
  • A @live query is delivering updated results.

When the Client sees hasNext, it nows consistently knows to expect updated results.

Furthermore, stale: true, as before, indicates that the Client expects an updated API result for a given query soon. For instance, this may mean that an operation is being sent and will replace an existing result with a new one as soon as the API responds.

Knock-on changes to the subscriptionExchange (💥)

The subscriptionExchange had to be changed a little to accommodate the new @urql/exchange-persisted support above and to fix some issues we were seeing in how it's being used.

Internally, @urql/core/internal contains a makeFetchBody function (and other fetch utilities that are reusable) which constructs the JSON data that will be sent to the GraphQL API. This function is aware of how to handle persisted queries.

Unfortunately, our subscriptionExchange's forwardSubscription function was instead accepting an Operation-like structure. That's why this has changed to now accept a FetchBody as the first argument.

This helps transports like graphql-ws to function correctly, as they pass this input on 1:1 without filtering it, expecting it to basically be like our FetchBody structure, ready to be accepted by GraphQL APIs.

If you were previously relying on this first argument having a context property — the OperationContext — we're now instead passing the entire Operation as a second argument to forwardSubscription

@urql/core ditches the graphql package (💥)

@urql/core doesn’t rely on a peer dependency on graphql anymore.

When you installed @urql/core@^3.2.2, you will at least add 17.3kB (gzip) to your bundlesize, since it not only includes its own code but also parts of graphql (and wonka, our stream utilities.) We wouldn't consider this bad, but we can do better.

In @urql/core@^4.0.0, we’re replacing the parts of graphql we used to use (which includes parse, print, and GraphQLError). Instead, we’re now relying on a homegrown, spec-compliant implementation of the GraphQL query language, the @0no-co/graphql.web package.

This small change means @urql/core@^4.0.0 will only add at most 9.9kB (gzip) to your bundlesize!

How does this affect TypeScript and the standard graphql library?

Any output from @0no-co/graphql.web is tested (with 100% test coverage; if this puts you at ease) to be compatible with the reference output from the graphql library. This means all ASTs will remain compatible.

When you have the standard graphql library installed, all @urql/core APIs will also automatically accept types from graphql (e.g. import('graphql').DocumentNode) and @0no-co/graphql.web will additionally switch to using graphql’s AST types as well.

How do I reduce my bundlesize if I depend on graphql myself?

If any of the rest of your app uses graphql already, we do have another trick up our sleeves, if you want to avoid a slight increase in bundlesize of 2kB (gzip).

You can use the graphql-web-lite package and alias graphql imports to it.
The graphql-web-lite repostory contains instructions using which you can alias the graphql package in just a few minutes! ⏲️ We hope, even faster than it’d take you to make a coffee.

The graphql-web-lite package is built to slim down the default build of graphql, and is built against the graphql library, but also uses @0no-co/graphql.web internally. Once you’ve aliased graphql to graphql-web-lite, you’ll continue to be able to use its API, but keep size to a minimum.

Easier core-usage (🦋)

Sources now have .then() set which means that you can leverage await by default on sources returned by urql, this enable you to perform await client.mutation().

Sources also have .subscribe() now which accepts a callback that allows you to see all values passed back from the exchanges.

const { unsubscribe } = client.query().subscribe(operationResult => {
  console.log(operationResult)
})

No more need for the dedupExchange (🦋)

The dedupExchange has long been needed in @urql/core as a way to avoid sending multiple requests for operations subscribed to from multiple locations. This has now been added as default behavior to the client.

This means you can drop the dedupExchange all together, the exchange will be a noop in this major.

Integrations

Fixing @urql/svelte's missing subscription handler (💥)

When refactoring our Svelte bindings in a prior revision, we accidentally misplaced the handleSubscription support and it wasn't possible to merge subscription events like on our other bindings.

This has now been fixed and the subscriptionStore accepts it again as a second argument.

import { subscriptionStore, gql, getContextClient } from '@urql/svelte';

const todos = subscriptionStore({
  client: getContextClient(),
  query: gql`
    subscription {
      newNotification { id, text }
    }
  `,
}, function combineNotifications(notifications = [], data) {
  return [...notifications, data.newNotification];
});

urql and @urql/preact no longer create a default Client (💥)

In prior versions, urql and @urql/preact would create a default Client when no Provider was used in the tree. This by default would send GraphQL requests to /graphql. This wasn't really useful as you would need to add a Provider anyway when you went to production. We have removed this in this major and instead throw an error when we miss a Provider.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions