Skip to content

Commit

Permalink
feat: support disabling codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
Diizzayy committed Oct 17, 2022
1 parent 12bf627 commit 1003a0e
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 59 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
run: pnpm install

- name: Run Tests
run: pnpm test
run: pnpm test -- --coverage

- uses: codecov/codecov-action@v3

Expand Down
4 changes: 0 additions & 4 deletions docs/content/1.getting-started/3.configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ Prevent GraphQL Code Generator from printing to console

Prevent adding `__typename` to generated types.

### `codegen.stitchSchemas`

Combine multiple GraphQL APIs into one unified gateway proxy schema that knows how to delegate parts of a request to the relevant underlying subschemas.

### `codegen.useTypeImports`

Use `import type {}` rather than `import {}` when importing only types.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"build": "nuxt-module-build",
"dev": "nuxi dev playground",
"dev:build": "nuxi build playground",
"test": "vitest run --coverage --watch false",
"test": "vitest run",
"lint": "eslint --ext .js,.ts,.vue .",
"lint:fix": "eslint --fix --ext .js,.ts,.vue .",
"prepare": "nuxt-module-build --stub && nuxi prepare playground"
Expand Down
44 changes: 35 additions & 9 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Import } from 'unimport'
import { genExport } from 'knitwork'

export interface GqlContext {
codegen?: boolean
template?: Record<string, string>
fns?: string[]
clients?: string[]
Expand All @@ -17,7 +18,7 @@ export interface GqlContext {

export function prepareContext (ctx: GqlContext, prefix: string) {
ctx.fns = Object.values(ctx.template).reduce((acc, template) => {
const fns = template.match(/\w+\s*(?=\(variables)/g)?.sort() || []
const fns = template.match(ctx?.codegen ? /\w+\s*(?=\(variables)/g : /\w+(?=:\s\(variables)/g)?.sort() || []

return [...acc, ...fns]
}, [] as string[])
Expand All @@ -38,21 +39,28 @@ export function prepareContext (ctx: GqlContext, prefix: string) {
'export const GqlSdks = {',
...ctx.clients.map(client => ` ${client}: ${client}GqlSdk,`),
'}',
`export const GqlOperations = ${JSON.stringify(ctx.clientOps)}`,
`export const GqClientOps = ${JSON.stringify(ctx.clientOps)}`,
...ctx.fns.map(f => fnExp(f))
].join('\n')

ctx.generateDeclarations = () => [
...ctx.clients.map(client => `import { getSdk as ${client}GqlSdk } from '#gql/${client}'`),
...(!ctx.codegen
? []
: ctx.clients.map(client => `import { getSdk as ${client}GqlSdk } from '#gql/${client}'`)),
...Object.entries(ctx.clientTypes || {}).map(([k, v]) => genExport(`#gql/${k}`, v)),
'declare module \'#gql\' {',
` type GqlClients = '${ctx.clients.join("' | '") || 'default'}'`,
' const GqlOperations = {}',
' const GqlSdks = {',
...ctx.clients.map(client => ` ${client}: ${client}GqlSdk,`),
' }',
...ctx.fns.map(f => fnExp(f, true)),
` type GqlSdkFuncs = ${ctx.clients.map(c => `ReturnType<typeof ${c}GqlSdk>`).join(' & ')}`,
` type GqlOps = '${Object.values(ctx.clientOps).flat().join("' | '")}'`,
' const GqClientOps = {}',
...(!ctx.codegen
? []
: [
' const GqlSdks = {',
...ctx.clients.map(client => ` ${client}: ${client}GqlSdk,`),
' }',
...ctx.fns.map(f => fnExp(f, true)),
` type GqlSdkFuncs = ${ctx.clients.map(c => `ReturnType<typeof ${c}GqlSdk>`).join(' & ')}`
]),
'}'
].join('\n')

Expand Down Expand Up @@ -97,6 +105,8 @@ export async function prepareOperations (ctx: GqlContext, path: string[]) {
}

export function prepareTemplate (ctx: GqlContext) {
if (!ctx.codegen) { return }

ctx.clientTypes = ctx.clientTypes || {}

ctx.clientTypes = Object.entries(ctx.template).reduce((acc, [key, template]) => {
Expand All @@ -107,3 +117,19 @@ export function prepareTemplate (ctx: GqlContext) {
return acc
}, {} as Record<string, string[]>)
}

export const mockTemplate = (operations: Record<string, string>) => {
const GqlFunctions: string[] = []

for (const [k, v] of Object.entries(operations)) {
GqlFunctions.push(` ${k}: (variables = undefined, requestHeaders = undefined) => withWrapper((wrappedRequestHeaders) => client.request(\`${v}\`, variables, {...requestHeaders, ...wrappedRequestHeaders}), '${k}', 'query')`)
}

return [
'export function getSdk(client, withWrapper = (action, _operationName, _operationType) => action()) {',
' return {',
GqlFunctions.join(',\n'),
' }',
'}'
].join('\n')
}
34 changes: 10 additions & 24 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { generate } from '@graphql-codegen/cli'
import type { CodegenConfig } from '@graphql-codegen/cli'

import type { Resolver } from '@nuxt/kit'
import { mapDocsToClients } from './utils'
import type { GqlConfig, GqlCodegen } from './types'

interface GenerateOptions {
Expand Down Expand Up @@ -49,32 +50,17 @@ function prepareConfig (options: GenerateOptions & GqlCodegen): CodegenConfig {
}
}

const generates: CodegenConfig['generates'] = Object.entries(options.clients).reduce((acc, [k, v]) => {
const clientDocuments = options.documents.filter((file: string) => {
const clientInExt = new RegExp(`\\.${k}\\.(gql|graphql)$`)
const clientInPath = new RegExp(`\\/${k}\\/(?=${file.split('/').pop().replace(/\./g, '\\.')})`)
const clientDocuments = mapDocsToClients(options.documents, Object.keys(options.clients))

return clientInExt.test(file) || clientInPath.test(file)
})

const noClientSpecified = options.documents.filter((file: string) => {
const clientInExt = /\.\w+\.(gql|graphql)$/.test(file)

const clientInPath = new RegExp(`\\/(${Object.keys(options.clients).join('|')})\\/(?=${file.split('/').pop().replace(/\./g, '\\.')})`).test(file)

return !clientInExt && !clientInPath
})

return {
...acc,
[`${k}.ts`]: {
config: codegenConfig,
schema: prepareSchema(v),
plugins: options.plugins,
documents: k !== 'default' ? clientDocuments : noClientSpecified
}
const generates: CodegenConfig['generates'] = Object.entries(options.clients).reduce((acc, [k, v]) => ({
...acc,
[`${k}.ts`]: {
config: codegenConfig,
schema: prepareSchema(v),
plugins: options.plugins,
documents: clientDocuments?.[k] || []
}
}, {})
}), {})

return { silent: options.silent, generates }
}
Expand Down
50 changes: 38 additions & 12 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { existsSync, statSync } from 'fs'
import { existsSync, statSync, readFileSync } from 'fs'
import { defu } from 'defu'
import { parse } from 'graphql'
import { useLogger, addPlugin, addImportsDir, addTemplate, resolveFiles, createResolver, defineNuxtModule, extendViteConfig } from '@nuxt/kit'
import type { NameNode, DefinitionNode } from 'graphql'
import { name, version } from '../package.json'
import generate from './generate'
import { deepmerge } from './runtime/utils'
import { mapDocsToClients } from './utils'
import type { GqlConfig, GqlClient, TokenOpts, GqlCodegen } from './types'
import { prepareContext, GqlContext, prepareOperations, prepareTemplate } from './context'
import { prepareContext, GqlContext, prepareOperations, prepareTemplate, mockTemplate } from './context'

const logger = useLogger('nuxt-graphql-client')

Expand All @@ -21,6 +24,7 @@ export default defineNuxtModule<GqlConfig>({
defaults: {
clients: {},
watch: true,
codegen: true,
autoImport: true,
functionPrefix: 'Gql'
},
Expand All @@ -44,11 +48,12 @@ export default defineNuxtModule<GqlConfig>({
onlyOperationTypes: true
}

config.codegen = defu<GqlCodegen, [GqlCodegen]>(config.codegen, codegenDefaults)
config.codegen = !!config.codegen && defu<GqlCodegen, [GqlCodegen]>(config.codegen, codegenDefaults)

const ctx: GqlContext = {
clientOps: {},
fnImports: [],
codegen: !!config?.codegen,
clients: Object.keys(config.clients)
}

Expand Down Expand Up @@ -146,13 +151,34 @@ export default defineNuxtModule<GqlConfig>({
plugins.push('typescript-operations', 'typescript-graphql-request')
}

ctx.template = await generate({
clients: config.clients as GqlConfig['clients'],
plugins,
documents,
resolver: srcResolver,
...(typeof config.codegen !== 'boolean' && config.codegen)
}).then(output => output.reduce((acc, c) => ({ ...acc, [c.filename.split('.ts')[0]]: c.content }), {}))
if (ctx.codegen) {
ctx.template = await generate({
clients: config.clients as GqlConfig['clients'],
plugins,
documents,
resolver: srcResolver,
...(typeof config.codegen !== 'boolean' && config.codegen)
}).then(output => output.reduce((acc, c) => ({ ...acc, [c.filename.split('.ts')[0]]: c.content }), {}))
} else {
const clientDocs = mapDocsToClients(documents, ctx.clients)

ctx.template = ctx.clients.reduce<Record<string, string>>((acc, k) => {
const entries: Record<string, string> = {}

for (const doc of clientDocs?.[k] || []) {
const definitions = parse(readFileSync(doc, 'utf-8'))?.definitions as (DefinitionNode & { name: NameNode })[]

for (const op of definitions) {
const name: string = op?.name?.value
const operation = op.loc?.source.body.slice(op.loc.start, op.loc.end) || undefined

if (name && operation) { entries[name] = operation }
}
}

return { ...acc, [k]: mockTemplate(entries) }
}, {})
}

await prepareOperations(ctx, documents)

Expand All @@ -178,8 +204,8 @@ export default defineNuxtModule<GqlConfig>({

for (const client of ctx.clients) {
addTemplate({
write: true,
filename: `gql/${client}.ts`,
write: ctx.codegen,
filename: `gql/${client}.${ctx.codegen ? 'ts' : 'mjs'}`,
getContents: () => ctx.template[client]
})
}
Expand Down
15 changes: 8 additions & 7 deletions src/runtime/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import type { GqlState, GqlConfig, GqlError, OnGqlError } from '../../types'
import { deepmerge } from '../utils'
// @ts-ignore
// eslint-disable-next-line import/named
import { GqlSdks, GqlOperations } from '#gql'
import type { GqlClients, GqlSdkFuncs } from '#gql'
import { GqlSdks, GqClientOps } from '#gql'
import type { GqlOps, GqlClients, GqlSdkFuncs } from '#gql'
import { useState, useNuxtApp, useAsyncData, useRuntimeConfig } from '#imports'

const useGqlState = (): Ref<GqlState> => {
Expand Down Expand Up @@ -205,12 +205,12 @@ export const useGqlCors = (cors: GqlCors) => {
}

export const useGql = (): (<
T extends keyof GqlSdkFuncs,
T extends GqlOps,
R extends ReturnType<GqlSdkFuncs[T]>,
P extends Parameters<GqlSdkFuncs[T]>['0'],
> (args: { operation: T, variables?: P }) => R) &
(<
T extends keyof GqlSdkFuncs,
T extends GqlOps,
R extends ReturnType<GqlSdkFuncs[T]>,
P extends Parameters<GqlSdkFuncs[T]>['0'],
> (operation: T, variables?: P) => R) => {
Expand All @@ -221,7 +221,7 @@ export const useGql = (): (<
const operation = (typeof args?.[0] !== 'string' && 'operation' in args?.[0] ? args[0].operation : args[0]) ?? undefined
const variables = (typeof args?.[0] !== 'string' && 'variables' in args?.[0] ? args[0].variables : args[1]) ?? undefined

const client = Object.keys(GqlOperations).find(k => GqlOperations[k].includes(operation)) ?? 'default'
const client = Object.keys(GqClientOps).find(k => GqClientOps[k].includes(operation)) ?? 'default'

const { instance } = state.value?.[client]

Expand Down Expand Up @@ -283,7 +283,7 @@ const useGqlErrorState = () => useState<GqlError>('_gqlErrors', () => null)
* @param {Object} options.options AsyncData options.
*/
export function useAsyncGql<
T extends keyof GqlSdkFuncs,
T extends GqlOps,
P extends Parameters<GqlSdkFuncs[T]>['0'],
R extends AsyncData<Awaited<ReturnType<GqlSdkFuncs[T]>>, GqlError>,
O extends Parameters<typeof useAsyncData>['2']> (options: { operation: T, variables?: P, options?: O }): Promise<R>
Expand All @@ -296,7 +296,7 @@ O extends Parameters<typeof useAsyncData>['2']> (options: { operation: T, variab
* @param {Object} options AsyncData options.
*/
export function useAsyncGql<
T extends keyof GqlSdkFuncs,
T extends GqlOps,
P extends Parameters<GqlSdkFuncs[T]>['0'],
R extends AsyncData<Awaited<ReturnType<GqlSdkFuncs[T]>>, GqlError>,
O extends Parameters<typeof useAsyncData>['2']> (operation: T, variables?: P, options?: O): Promise<R>
Expand All @@ -306,5 +306,6 @@ export function useAsyncGql (...args: any[]) {
const variables = (typeof args?.[0] !== 'string' && 'variables' in args?.[0] ? args[0].variables : args[1]) ?? undefined
const options = (typeof args?.[0] !== 'string' && 'options' in args?.[0] ? args[0].options : args[2]) ?? undefined
const key = hash({ operation, variables })
// @ts-ignore
return useAsyncData(key, () => useGql()(operation, variables), options)
}
2 changes: 1 addition & 1 deletion src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export interface GqlConfig<T = GqlClient> {
/**
* Configuration for the GraphQL Code Generator, setting this option to `false` results in limited TypeScript support.
*/
codegen?: GqlCodegen
codegen?: boolean | GqlCodegen

/**
* Enable hot reloading for GraphQL documents
Expand Down
29 changes: 29 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const mapDocsToClients = (documents: string[], clients: string[]) => {
const mappedDocs = new Set()

const docsWithClient = (client: string) => documents.filter(d => !mappedDocs.has(d)).filter((file: string) => {
const clientInExt = new RegExp(`\\.${client}\\.(gql|graphql)$`)
const clientInPath = new RegExp(`\\/${client}\\/(?=${file.split('/').pop()?.replace(/\./g, '\\.')})`)

const clientSpecified = clientInExt.test(file) || clientInPath.test(file)

if (clientSpecified) { mappedDocs.add(file) }

return clientSpecified
})

const docsWithoutClient = documents.filter(d => !mappedDocs.has(d)).filter((file: string) => {
const clientInExt = /\.\w+\.(gql|graphql)$/.test(file)
const clientInPath = new RegExp(`\\/(${clients.join('|')})\\/(?=${file.split('/').pop()?.replace(/\./g, '\\.')})`).test(file)

return !clientInExt && !clientInPath
})

return clients.reduce((acc, client) => {
const isDefault = client === 'default' || client === clients[0]

acc[client] = !isDefault ? docsWithClient(client) : [...docsWithClient(client), ...docsWithoutClient]

return acc
}, {} as Record<string, string[]>)
}
1 change: 1 addition & 0 deletions test/fixtures/multi-client/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
'graphql-client': {
codegen: false,
clients: {
spacex: 'https://api.spacex.land/graphql',
rmorty: 'https://rickandmortyapi.com/graphql',
Expand Down
7 changes: 7 additions & 0 deletions vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
teardownTimeout: 15000
}
})

0 comments on commit 1003a0e

Please sign in to comment.