Optimize your Bundle Size with SWC and GraphQL Codegen

Jesse van der Velden

With the new client-preset for GraphQL Code Generator it is possible to write your GraphQL queries inlined, next to your component in a type-safe way. This can greatly improve the Developer Experience (DX).

Instead of writing your operations in a separate file, you can write them inline in your component, and use the graphql function to parse the operation and get the correct types.

// Instead of operations in a separate .graphql file and importing generated hooks:
import { useGetCartByIdQuery } from './graphql/generated-hooks'
const { data } = useGetCartByIdQuery({ variables: { id: '1' } })
 
// You can write your operations directly next to your component and get the correct types:
import { graphql } from './graphql'
const GetCartByIdDocument = graphql(/** GraphQL */ `
  query GetCartById($id: ID!) {
    id
    items {
      id
      ...CartItem
    }
  }
`)
// Awesome! We have the correct types for this operation even when we specify it next to our component!

However, this approach can come at a cost - the bundle size of your pages can increase significantly, especially as the number of queries and mutation grows. In this post, we will show you what the problem is and how to optimize your bundle size with the new @graphql-codegen/client-preset-swc-plugin plugin for your applications supported by the SWC compiler such as projects using Next.js or Vite.

The Generated Code When Using the client-preset

When using the client-preset without any additional optimization, your site’s bundle can quickly become bloated with unnecessary imports on every page/ component where you use GraphQL. This is because by default client-preset’s graphql function that enables a great TypeScript DX, has to have all the GraphQL operations available to it to parse the operation and have the correct types.

The generated code output for the graphql function is a function that looks similar to the following code, where code-splitting and tree-shaking is not easily possible.

const documents = {
  'fragment CartItems on CartItem {\n name\n quantity \n  }\n  ': types.CartItemsFragmentDoc,
  'query GetCartById($id: ID!) {\n cart(id: $id) {\n id \n items { \n id \n ...CartItem\n }\n totalItems\n}\n  }\n':
    types.GetCartByIdDocument
}
export function graphql(
  source: 'fragment CartItems on CartItem {\n name\n quantity\n }\n '
): (typeof documents)['\n fragment CartItem on CartItem {\n name\n quantity\n }\n ']
export function graphql(
  source: 'query GetCartById($id: ID!) {\n cart(id: $id) {\nid\nitems {\nid\n...CartItem\n}\ntotalItems\n }\n }\n'
): (typeof documents)['\n query GetCartById($id: ID!) {\n cart(id: $id) {\n id\n items {\n id \n ...CartItem \n } \n totalItems \n } \n } n']
export function graphql(source: string): unknown
export function graphql(source: string) {
  // Here we access the documents object, so the bundler cannot make assumption over what is
  //  actually used at runtime
  return (documents as any)[source] ?? {}
}

Every time you use the graphql function, all the operations in the project are imported, regardless of wether they are used or not. This can be a problem if you have a lot of documents (query, mutation, and fragments) in your project.

💡

For more information/ a history of this functionality, you can read more about it in this blog post from Laurin Quast about this new client-preset feature: Unleash the Power of Fragments with GraphQL Code Generator. It was based on the former gql-tag-operations-preset that is now replaced by the client-preset, which was originally explored by Maël Nison in this Github project.

The Solution: A Compiler Plugin

This is where a compiler can make a big difference. Instead of using the map which contains all GraphQL operations in the project, we can use the generated document types for that specific operation. So the example above would compile to:

import { CartItemFragmentDoc } from './graphql'
import { GetCartByIdDocument } from './graphql'
const GetCartByIdDocument = GetCartByIdDocument
const CartItemFragment = CartItemFragmentDoc

There was already a Babel plugin for the client-preset for projects using Babel in the client-preset package. Now, as SWC itself, and Next.js is becoming more popular and uses SWC as its default compiler, there was a need for a SWC plugin as well. SWC is a fast and modern JavaScript/TypeScript compiler written in Rust, so the Babel plugin couldn’t be used.

This is where the new @graphql-codegen/client-preset-swc-plugin comes in. This plugin uses the SWC compiler to optimize the generated code, reducing the bundle size for all your SWC projects!

How to Use the SWC Plugin

To use the SWC plugin, you need to install the plugin:

npm install -D @graphql-codegen/client-preset-swc-plugin
yarn add -D @graphql-codegen/client-preset-swc-plugin

To use the SWC plugin with Next.js, update your next.config.js to add the following:

next.config.js
const nextConfig = {
  // ...
  experimental: {
    swcPlugins: [
      [
        '@graphql-codegen/client-preset-swc-plugin',
        { artifactDirectory: './src/gql', gqlTagName: 'graphql' }
      ]
    ]
  }
}

or with Vite React, update your vite.config.ts to add the following:

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react({
      plugins: [
        [
          '@graphql-codegen/client-preset-swc-plugin',
          { artifactDirectory: './src/gql', gqlTagName: 'graphql' }
        ]
      ]
    })
  ]
})

or for other SWC projects add this to your .swcrc file:

.swcrc
{
  // ...
  jsc: {
    // ...
    experimental: {
      plugins: [
        [
          '@graphql-codegen/client-preset-swc-plugin',
          { artifactDirectory: './src/gql', gqlTagName: 'graphql' }
        ]
      ]
    }
  }
}

Then you’re ready to go! The plugin will automatically optimize your generated code when SWC compiles your files.

In conclusion, using the client-preset for GraphQL Code Generator is a powerful way to improve the DX of your project. However, without proper optimization, the bundle size can quickly become bloated. By using the @graphql-codegen/client-preset-swc-plugin, (or the Babel plugin) you can optimize the generated code and reduce the bundle size, and in the end improve the loading time of your application.

Like every open source project we maintain, we always use it ourselves in production - in our own products and with our clients. In Hive, our open source GraphQL Schema Registry we also use this new plugin and on this PR you can see the changes we’ve made to introduce it and the benefits we got from it. Feel free to explore Hive’s source code to find how The Guild is using GraphQL

Join our newsletter

Want to hear from us when there's something new?
Sign up and stay up to date!

*By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.