Skip to content

Commit

Permalink
chore: remove graphql-utils and allow for the use of swc (#2176)
Browse files Browse the repository at this point in the history
## What's the purpose of this pull request?

This PR makes `@faststore/core` compatible with SWC by dropping the
`@faststore/graphql-utils` package, meaning we don't depend on a
babel-only plugin built by us.

## How it works?

### Why we did it 
[SWC](https://swc.rs/) is a compiler and bundler used by Next.js. It is
the default bundler since Next.js 12 the default code minifier since
Next.js 13. It promises considerable **performance** improvements when
compared to [babel](https://babeljs.io/), its main competitor.

Our use of a custom babel plugin - made available through
`@faststore/graphql-utils` - made us unable to use SWC, since babel
plugins are not compatible with SWC. This plugin helped us optimize
GraphQL queries, improve security of queries and mutations and handle
GraphQL operations definitions.

When this plugin was first introduced, there wasn't a solution that
could useful to us at the community, so we had to develop it ourselves.
Now, The Guild's
[client-preset](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client)
does exactly what we need. It provides a all-in-one tool for handling
persisted queries (optimization & security) and a GraphQL helper (`gql`)
for defining operations, among other things we already used as
standalone solutions, like type generation for the schema.

By using `client-preset` we're no longer bound to babel or SWC
exclusively, as their solution doesn't rely on any of them. There are
recommended plugins for both platforms, as it helps reduce bundle sizes.
We also helps reduce the code we have to maintain, adopt community
standards and get up do date in terms of bundling and compilation tools.

### Results

After the store was migrated to use `client-preset`, I ran a few builds
to see if there would be any difference in bundle size. The results were
not great, as the SWC version increased the bundle size considerably
while reducing the build time in a negligible way (~5s reduction,
although ~20% reduction percentage wise).

I wasn't able to make the SWC plugin work, and I think that's because
we're on Next 12. The support for SWC plugins within Next.js is still
experimental (even in Next.js 13) and these errors are expected. It will
probably work when we migrate to it, tho.

I also ran a comparison considering the client-preset with the community
babel plugin, and the results were comparable to the ones we had before,
time-wise as well.

**Click on the images** to read the data.

| Before (babel, custom plugin) | After (SWC, no plugins) | After
(babel, community plugin) |
| ------------- | ------------- | ------------- |
| <img
src="https://github.com/vtex/faststore/assets/8127610/3c2b5dc1-e4ce-41b1-86fa-6e21d933c67a"
width="400"> |<img
src="https://github.com/vtex/faststore/assets/8127610/38a72e9f-f19c-487d-8124-f668b4c011a6"
width="400"> | <img
src="https://github.com/vtex/faststore/assets/8127610/b5064fd9-6de2-4cd1-8880-e7b570dc2a05"
width="400">
|
![tempo-babel-custom-plugin](https://github.com/vtex/faststore/assets/8127610/6c045af0-413e-43fe-a124-95cc2a58304f)
|
![tempo-swc-no-plugin](https://github.com/vtex/faststore/assets/8127610/bb5e4e77-cc15-4ef5-86e2-4dd79e70a79b)
|
![tempo-babel-community-plugin](https://github.com/vtex/faststore/assets/8127610/279bada6-5168-4405-a536-063777050993)
|

### Implementation details

#### operationName
We previously used the operation name to identify queries and mutations
on the backend. `client-preset` creates hashes based on the operation
name to do the same thing. I updated the code to comply with this
change, but added the operation name to the query arguments so

1. It could be provided to `envelop` on the `execute` function
2. It could be used for debugging when inspecting network calls using
the browser dev tools

To do that, I used `client-preset` `onExecutableDocumentNode` hook and
extracted it manually at the `codegen.ts` config file.

#### @generated dir

Previously, the schema and other GraphQL files were inside the
`@generated/graphql` folder. I changed it to `@generated` directly so
`@generated/graphql` imports wouldn't break. It would be a pain to
change all files, and we don't currently have other generated files
other than graphql files, so it made sense to change it.

#### SWC minification

I enabled SWC minification at `next.config.js` since it is already the
default minification tool Next 13. It provided awesome results,
decreasing the build time in 10s to 15s.

## How to test it?

This is a breaking change, as we know use the `gql` helper function and
how to use it. We already exported it from `@faststore/core/api`, but it
was possible to import it from `@faststore/graphql-utils` as well.
Importing it from `@faststore/core/api` is the only possible and
recommended way from now on.

At the code, users also have to change the `gql` call to an actual
function call:

```ts
// Before
const query = gql`query here`

// After
const query = gql(`query here`)
```

This change only affects stores containing API Extensions.

Users shouldn't feel anything different when browsing the website.

### Starters Deploy Preview

vtex-sites/starter.store#334

## References

https://the-guild.dev/graphql/codegen/plugins/presets/preset-client

https://the-guild.dev/blog/optimize-bundle-size-with-swc-and-graphql-codegen
I read this code a bunch to figure out how client-preset worked and how
to use the options:
https://github.com/dotansimha/graphql-code-generator/blob/master/packages/presets/client/src/index.ts
https://nextjs.org/docs/messages/swc-minify-enabled
https://nextjs.org/docs/architecture/nextjs-compiler
dotansimha/graphql-code-generator#9057

---------

Co-authored-by: Mariana Caetano Pereira <67270558+Mariana-Caetano@users.noreply.github.com>
Co-authored-by: Fanny Chien <fanny.chien@vtex.com.br>
  • Loading branch information
3 people authored Jan 30, 2024
1 parent 7aa6d91 commit fa22917
Show file tree
Hide file tree
Showing 56 changed files with 1,564 additions and 1,669 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ packages/gatsby-**/**/*.json
!packages/gatsby-**/package.json
!packages/gatsby-**/tsconfig.json

# autogen graphql files
packages/core/@generated/schema.graphql
packages/core/@generated/persisted-documents.json

# lighthouse CI autogen files
.lighthouseci

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ In the `ServerProduct.ts` file, define the GraphQL fragment corresponding to the

import { gql } from '@faststore/core/api'

export const fragment = gql`
export const fragment = gql(`
fragment ServerProduct on Query {
product(locator: $locator) {
customData
}
}
`
`)
```
Now, you can consume `customData` by following the [Consuming FastStore API extension with custom components](/docs/api-extensions/consuming-api-extensions) guide.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Improvements to API extensions

In this guide, learn how to migrate your store version to v3.0.0 to leverage the latest improvements in the API extension.

Version 3.0.0 and above, introduces the following enhancements to API extension users:

- Deprecation of the `@faststore/graphql-utils` package in favor of the [`client-preset`](https://the-guild.dev/graphql/codegen/plugins/presets/preset-client) plugin.

- Refinement `gql` helper usage for a smoother and more efficient GraphQL query handling.

- Enhancement security measures to a more secure method for invoking queries and mutations to safeguard your store's data.

- Optimization call processes for queries or mutations defined within `@faststore/core`.

import { Callout } from 'nextra/components'

<Callout type="info" emoji="ℹ️">
For more details about these changes, also refer to the [GitHub releases](/tbd) related to this version.
</Callout>

The `@faststore/graphql-utils` has been replaced by open-source solutions maintained by the community that are now re-exported from `@faststore/core`. There are minor breaking changes on how developers should write GraphQL definitions and invoke queries and mutations, which were introduced in version 3.0.0.
## Before you begin
Make sure your store version is updated to v3.0.0 or above. If it’s not updated follow the instructions below:

1. Open your store code and navigate to the `package.json` file.

2. In `dependencies` > `@faststore/core`, change the version to the following:

```json
"@faststore/core": "^3.0.0",
```

3. Open the terminal and run `yarn` to update the version.

## Updating the `gql` helper usage

The `gql` helper serves as a function to define GraphQL operations such as queries, mutations, or fragments within the store code. Before, the function was imported directly from the `@faststore/graphql-utils` which was not recommended. See the example below:

```tsx filename="src/fragments/ClientProduct.tsx"
import { gql } from '@faststore/graphql-utils'

export const fragment = gql`
fragment ClientProduct on Query {
product(locator: $locator) {
id: productID
breadcrumbList {
itemListElement {
item
name
position
}
}
}
}
`
```

Now with the v3.0.0, you should import the `gql` helper from `@faststore/core/api`and be called as a function - with the argument between parenthesis. This also applies to query and mutation definitions inside the components. For example:

```tsx filename="src/fragments/ClientProduct.tsx" {1}
import { gql } from '@faststore/core/api'

export const fragment = gql(`
fragment ClientProduct on Query {
product(locator: $locator) {
id: productID
breadcrumbList {
itemListElement {
item
name
position
}
}
}
}
`)
```

## Using `useQuery` hook to call queries and mutations

Previously, it was possible to invoke queries and mutations by using their names - such as `MyCustomQuery` by providing it to the `useQuery` hook. For example:

```tsx
import { useQuery_unstable as useQuery } from '@faststore/core/experimental'

const query = gql(`
query MyCustomQuery {
customQuery() {
data
}
}
`)

// The useQuery call will now throw an error
function CustomComponent() {
// ...
const result = useQuery(`MyCustomQuery`, variables)
// ...
}
```

With v3.0.0, queries and mutations are now only invoked using more secure hashes, which are randomly generated and do to that you must pass the query or mutation object - result of the `gql` call - to `useQuery` directly. For example:

```tsx
import { gql } from '@faststore/core/api'
import { useQuery_unstable as useQuery } from '@faststore/core/experimental'

const query = gql(`
query MyCustomQuery {
customQuery() {
data
}
}
`)

// useQuery apropriately calls MyCustomQuery
function CustomComponent() {
// ...
const result = useQuery(query, variables)
// ...
}
```

### Calling queries or mutations defined by `@faststore/core`

A custom component can call a query or mutation defined by `@faststore/core`, such as `ClientManyProductsQuery`. In these cases, you replace the `useQuery` hook call with a call to the specific hook for that query.

<Callout type="warning" emoji="⚠️">
The availability of such hooks is limited.
</Callout>
```tsx
import { useClientManyProducts_unstable as useClientManyProducts } from '@faststore/core/experimental'

// ClientManyProductsQuery will be called with the variables passed by CustomComponent
function CustomComponent() {
// ...
const result = useClientManyProducts(variables)
// ...
}
```
3 changes: 1 addition & 2 deletions packages/api/src/typeDefs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { print } from 'graphql'

import { loadFilesSync } from '@graphql-tools/load-files'

// Empty string ('') is interpreted as the current dir. Referencing it as './' won't work.
export const typeDefs = loadFilesSync('', { extensions: ['.graphql'] })
export const typeDefs = loadFilesSync(__dirname, { extensions: ['.graphql'] })
.map(print)
.join('\n')
12 changes: 11 additions & 1 deletion packages/cli/src/commands/generate-graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core'
import { existsSync } from 'fs-extra'
import chalk from 'chalk'

import { tmpDir } from '../utils/directory'
import { coreDir, tmpDir } from '../utils/directory'
import { runCommandSync } from '../utils/runCommandSync'

export default class GenerateGraphql extends Command {
Expand Down Expand Up @@ -54,6 +54,16 @@ export default class GenerateGraphql extends Command {
cwd: isCore ? undefined : tmpDir,
})

// yarn generate:copy-back expects the DESTINATION var to be present so it can copy the files to the correct directory
runCommandSync({
cmd: `DESTINATION=${coreDir} yarn generate:copy-back`,
errorMessage:
"Failed to copy back typings files. 'yarn generate:copy-back' thrown errors",
throws: 'warning',
debug,
cwd: isCore ? undefined : tmpDir,
})

console.log(
`${chalk.green(
'success'
Expand Down
4 changes: 0 additions & 4 deletions packages/core/.babelrc

This file was deleted.

12 changes: 12 additions & 0 deletions packages/core/.babelrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { babelOptimizerPlugin } = require('@graphql-codegen/client-preset')

module.exports = {
presets: ['next/babel'],
/** Replaces gql function calls for imports to the document data */
plugins: [
[
babelOptimizerPlugin,
{ artifactDirectory: './@generated', gqlTagName: 'gql' },
],
],
}
Loading

0 comments on commit fa22917

Please sign in to comment.