Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby): Improvements to GraphQL TypeScript Generation #35581

Merged
merged 10 commits into from
May 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
}
}
}
`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
27 changes: 27 additions & 0 deletions e2e-tests/development-runtime/src/pages/graphql-typegen.tsx
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading