Skip to content

Commit

Permalink
feat(gatsby): Improvements to GraphQL TypeScript Generation (#35581)
Browse files Browse the repository at this point in the history
  • Loading branch information
LekoArts committed May 5, 2022
1 parent 8bad9b3 commit 966aca8
Show file tree
Hide file tree
Showing 23 changed files with 298 additions and 138 deletions.
@@ -0,0 +1,76 @@
// https://docs.cypress.io/api/commands/readfile#Existence
// By default, cy.readFile() asserts that the file exists and will fail if it does not exist.

const FRAGMENT = `fragment TestingFragment on Site {
siteMetadata {
author
}
}`

const GRAPHQL_TYPE = `type CheckMePlease {
hello: String!
}`

const TS_TYPE = `type CheckMePlease = {
readonly hello: Scalars['String'];
};`

describe(`typecheck`, () => {
it(`passes without an error`, () => {
cy.exec(`npm run typecheck`)
})
})

describe('fragments.graphql', () => {
it('exists in .cache folder', () => {
cy.readFile('.cache/typegen/fragments.graphql')
})
it('contains test fragment', () => {
cy.readFile('.cache/typegen/fragments.graphql').then((file) => {
expect(file).to.include(FRAGMENT)
})
})
})

describe('graphql-config', () => {
it('exists in .cache folder with correct data', () => {
cy.readFile('.cache/typegen/graphql.config.json', 'utf-8').then((json) => {
expect(json).to.deep.equal({
"schema": ".cache/typegen/schema.graphql",
"documents": [
"src/**/**.{ts,js,tsx,jsx}",
".cache/typegen/fragments.graphql"
],
"extensions": {
"endpoints": {
"default": {
"url": "http://localhost:8000/___graphql"
}
}
}
})
})
})
})

describe('schema.graphql', () => {
it('exists in .cache folder', () => {
cy.readFile('.cache/typegen/schema.graphql')
})
it('contains test type', () => {
cy.readFile('.cache/typegen/schema.graphql').then((file) => {
expect(file).to.include(GRAPHQL_TYPE)
})
})
})

describe('gatsby-types.d.ts', () => {
it('exists in src folder', () => {
cy.readFile('src/gatsby-types.d.ts')
})
it('contains test type', () => {
cy.readFile('src/gatsby-types.d.ts').then((file) => {
expect(file).to.include(TS_TYPE)
})
})
})

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

@@ -0,0 +1,56 @@
const QUERY_BEFORE = `type GraphQLTypegenQuery = { readonly site: { readonly siteMetadata: { readonly title: string | null } | null } | null };`
const QUERY_AFTER = `type GraphQLTypegenQuery = { readonly site: { readonly siteMetadata: { readonly author: string | null, readonly title: string | null } | null } | null };`
const FRAGMENT_BEFORE = `fragment SiteInformation on Site {
buildTime
}`
const FRAGMENT_AFTER = `fragment SiteInformation on Site {
buildTime
trailingSlash
}`

beforeEach(() => {
cy.visit(`/graphql-typegen/`).waitForRouteChange()
})

after(() => {
cy.exec(`npm run reset`)
})

describe(`hot-reloading changes on GraphQL Typegen files`, () => {
it(`contains initial contents in files`, () => {
cy.readFile('src/gatsby-types.d.ts').then((file) => {
expect(file).to.include(QUERY_BEFORE)
})
cy.readFile('.cache/typegen/fragments.graphql').then((file) => {
expect(file).to.include(FRAGMENT_BEFORE)
})
})

it(`can edit a page query`, () => {
cy.exec(
`npm run update -- --file src/pages/graphql-typegen.tsx --replacements "# %AUTHOR%:author" --exact`
)

cy.waitForHmr()

cy.readFile('src/gatsby-types.d.ts').then((file) => {
expect(file).to.include(QUERY_AFTER)
})
})

it(`can edit a fragment`, () => {
cy.exec(
`npm run update -- --file src/pages/graphql-typegen.tsx --replacements "# %TRAILING_SLASH%:trailingSlash" --exact`
)

cy.waitForHmr()

cy.readFile('.cache/typegen/fragments.graphql').then((file) => {
expect(file).to.include(FRAGMENT_AFTER)
})
})

it(`successfully runs typecheck`, () => {
cy.exec(`npm run typecheck`)
})
})
1 change: 1 addition & 0 deletions e2e-tests/development-runtime/gatsby-config.js
Expand Up @@ -30,6 +30,7 @@ module.exports = {
`gatsby-source-pinc-data`,
`gatsby-source-query-on-demand-data`,
`gatsby-browser-tsx`,
`gatsby-node-typegen`,
`gatsby-transformer-sharp`,
`gatsby-transformer-json`,
{
Expand Down
2 changes: 2 additions & 0 deletions e2e-tests/development-runtime/package.json
Expand Up @@ -38,6 +38,7 @@
"develop": "cross-env CYPRESS_SUPPORT=y ENABLE_GATSBY_REFRESH_ENDPOINT=true GATSBY_EXPERIMENTAL_QUERY_ON_DEMAND=y GATSBY_GRAPHQL_TYPEGEN=y gatsby develop",
"serve": "gatsby serve",
"clean": "gatsby clean",
"typecheck": "tsc --noEmit",
"start": "npm run develop",
"format": "prettier --write \"src/**/*.js\"",
"test": "npm run start-server-and-test || (npm run reset && exit 1)",
Expand All @@ -64,6 +65,7 @@
"is-ci": "^2.0.0",
"prettier": "2.0.4",
"start-server-and-test": "^1.7.11",
"typescript": "^4.6.4",
"yargs": "^12.0.5"
},
"repository": {
Expand Down
@@ -0,0 +1,23 @@
import { GatsbyNode } from "gatsby"

export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"] = ({ actions }) => {
const { createTypes } = actions

createTypes(`
type CheckMePlease {
hello: String!
}
`)
}

export const createPages: GatsbyNode["createPages"] = async ({ graphql }) => {
await graphql<Queries.GatsbyNodeQuery>(`#graphql
query GatsbyNode {
site {
siteMetadata {
title
}
}
}
`)
}
@@ -0,0 +1 @@
{}
27 changes: 27 additions & 0 deletions e2e-tests/development-runtime/src/pages/graphql-typegen.tsx
@@ -0,0 +1,27 @@
import React from "react"
import { graphql, PageProps } from "gatsby"

function GraphQLTypegen({ data }: PageProps<Queries.GraphQLTypegenQuery>) {
return (
<p>
{data?.site?.siteMetadata?.title}
</p>
)
}

export const query = graphql`
query GraphQLTypegen{
site {
siteMetadata {
# %AUTHOR%
title
}
}
}
fragment SiteInformation on Site {
buildTime
# %TRAILING_SLASH%
}
`

export default GraphQLTypegen
14 changes: 14 additions & 0 deletions e2e-tests/development-runtime/tsconfig.json
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "esnext"],
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["./src/**/*", "./plugins/**/*"]
}
47 changes: 13 additions & 34 deletions packages/gatsby/src/services/graphql-typegen.ts
@@ -1,34 +1,21 @@
import { EventObject } from "xstate"
import { IBuildContext } from "../internal"
import { IDataLayerContext, IQueryRunningContext } from "../state-machines"
import {
writeGraphQLFragments,
writeGraphQLSchema,
} from "../utils/graphql-typegen/file-writes"
import { writeTypeScriptTypes } from "../utils/graphql-typegen/ts-codegen"

export async function graphQLTypegen(
{
program,
store,
parentSpan,
reporter,
}: IBuildContext | IQueryRunningContext | IDataLayerContext,
_: EventObject,
{
src: { compile },
}: {
src: {
type: string
compile?: "all" | "schema" | "definitions"
}
}
): Promise<void> {
export async function graphQLTypegen({
program,
store,
parentSpan,
reporter,
}: IBuildContext): Promise<void> {
// TypeScript requires null/undefined checks for these
// But this should never happen unless e.g. the state machine doesn't receive this information from a parent state machine
if (!program || !store || !compile || !reporter) {
if (!program || !store || !reporter) {
throw new Error(
`Missing required params in graphQLTypegen. program: ${!!program}. store: ${!!store}. compile: ${!!compile}`
`Missing required params in graphQLTypegen. program: ${!!program}. store: ${!!store}.`
)
}
const directory = program.directory
Expand All @@ -41,19 +28,11 @@ export async function graphQLTypegen(
)
activity.start()

if (compile === `all` || compile === `schema`) {
await writeGraphQLSchema(directory, store)
if (compile === `schema`) {
reporter.verbose(`Re-Generate GraphQL Schema types`)
}
}
if (compile === `all` || compile === `definitions`) {
await writeGraphQLFragments(directory, store)
await writeTypeScriptTypes(directory, store)
if (compile === `definitions`) {
reporter.verbose(`Re-Generate TypeScript types & GraphQL fragments`)
}
}
const { schema, definitions } = store.getState()

await writeGraphQLSchema(directory, schema)
await writeGraphQLFragments(directory, definitions)
await writeTypeScriptTypes(directory, schema, definitions)

activity.end()
}
12 changes: 12 additions & 0 deletions packages/gatsby/src/services/listen-for-mutations.ts
Expand Up @@ -18,15 +18,27 @@ export const listenForMutations: InvokeCallback = (callback: Sender<any>) => {
callback({ type: `QUERY_RUN_REQUESTED`, payload: event })
}

const emitSetSchema = (event: unknown): void => {
callback({ type: `SET_SCHEMA`, payload: event })
}

const emitGraphQLDefinitions = (event: unknown): void => {
callback({ type: `SET_GRAPHQL_DEFINITIONS`, payload: event })
}

emitter.on(`ENQUEUE_NODE_MUTATION`, emitMutation)
emitter.on(`WEBHOOK_RECEIVED`, emitWebhook)
emitter.on(`SOURCE_FILE_CHANGED`, emitSourceChange)
emitter.on(`QUERY_RUN_REQUESTED`, emitQueryRunRequest)
emitter.on(`SET_SCHEMA`, emitSetSchema)
emitter.on(`SET_GRAPHQL_DEFINITIONS`, emitGraphQLDefinitions)

return function unsubscribeFromMutationListening(): void {
emitter.off(`ENQUEUE_NODE_MUTATION`, emitMutation)
emitter.off(`WEBHOOK_RECEIVED`, emitWebhook)
emitter.off(`SOURCE_FILE_CHANGED`, emitSourceChange)
emitter.off(`QUERY_RUN_REQUESTED`, emitQueryRunRequest)
emitter.off(`SET_SCHEMA`, emitSetSchema)
emitter.off(`SET_GRAPHQL_DEFINITIONS`, emitGraphQLDefinitions)
}
}
19 changes: 1 addition & 18 deletions packages/gatsby/src/state-machines/data-layer/index.ts
Expand Up @@ -69,26 +69,9 @@ const recreatePagesStates: StatesConfig<IDataLayerContext, any, any> = {
invoke: {
id: `building-schema`,
src: `buildSchema`,
onDone: [
{
target: `graphQLTypegen`,
cond: (): boolean => !!process.env.GATSBY_GRAPHQL_TYPEGEN,
},
{
target: `creatingPages`,
},
],
},
exit: `assignGraphQLRunners`,
},
graphQLTypegen: {
invoke: {
src: {
type: `graphQLTypegen`,
compile: `schema`,
},
onDone: {
target: `creatingPages`,
actions: `assignGraphQLRunners`,
},
},
},
Expand Down

0 comments on commit 966aca8

Please sign in to comment.