Skip to content

Commit

Permalink
105 generate fully typed composables based on queries (#108)
Browse files Browse the repository at this point in the history
* Generate fully typed composables based on queries #105

* lint:fix
* run github ci on PR's
* added peerDependencies, because
Build is done with some warnings:
- Inlined implicit external consola
- Inlined implicit external scule
- Inlined implicit external graphql

* moved parser, so it can be called from module.ts
* workaround for The inferred type of 'default' cannot be named without a reference
* moved parser, so it can be called from module.ts
* fix consola LogLevel issue
  • Loading branch information
vernaillen authored May 18, 2024
1 parent 458079e commit d041d99
Show file tree
Hide file tree
Showing 21 changed files with 833 additions and 747 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ name: ci-main

on:
push:
paths-ignore:
- "docs/**"
- "*.md"
branches:
- main
pull_request:
paths-ignore:
- "docs/**"
- "*.md"
branches:
- main

Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@
"vue-docgen-web-types": "^0.1.8",
"vue-tsc": "^2.0.19"
},
"peerDependencies": {
"consola": "^3.2.3",
"graphql": "^16.8.1",
"scule": "^1.3.0"
},
"release-it": {
"git": {
"commitMessage": "chore(release): release v${version}"
Expand Down
4 changes: 4 additions & 0 deletions playground/components/HeaderComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const links = [
label: 'Test',
to: '/test'
},
{
label: 'Generated Composables',
to: '/composables'
},
{
label: 'Auth',
to: '/auth'
Expand Down
12 changes: 7 additions & 5 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ export default defineNuxtConfig({
frontendUrl: 'https://demo.wpnuxt.com',
faustSecretKey: '',
showBlockInfo: false,
debug: false,
replaceSchema: false,
enableCache: true,
staging: false
},
graphqlMiddleware: {
downloadSchema: true
staging: false,
logLevel: 4,
downloadSchema: true,
generateComposables: {
enabled: true,
prefix: 'wp'
}
},
ui: {
icons: ['heroicons', 'uil', 'mdi']
Expand Down
43 changes: 43 additions & 0 deletions playground/pages/composables.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { useRuntimeConfig } from 'nuxt/app'
import { wpPosts, wpPostByUri } from '#wpnuxt'
const prefix = useRuntimeConfig().public.wpNuxt.generateComposables?.prefix
const { data: posts } = await wpPosts()
const { data: postsLimited } = await wpPosts({ limit: 1 })
const { data: postByUri } = await wpPostByUri({ uri: 'hello-world' })
</script>

<template>
<div>
<UContainer class="prose dark:prose-invert pt-5">
<h2>Examples of generated composables</h2>
<p>
prefix for composables: {{ prefix }}
</p>
<h3>{{ prefix }}Posts()</h3>
<ul>
<li
v-for="post in posts?.posts?.nodes"
:key="post.id"
>
{{ post.title }}
</li>
</ul>
<h3>{{ prefix }}Posts({ limit: 1 })</h3>
<ul>
<li
v-for="post in postsLimited?.posts?.nodes"
:key="post.id"
>
{{ post.title }}
</li>
</ul>
<h3>{{ prefix }}PostByUri({ uri: 'hello-world' })</h3>
<p>
{{ postByUri?.nodeByUri?.title }}
</p>
</UContainer>
</div>
</template>
2 changes: 0 additions & 2 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { useHead, useLatestPost, usePosts, useGeneralSettings } from '#imports'
const { data: posts } = await usePosts()
const { data: settings } = await useGeneralSettings()
const { data: latestPost } = await useLatestPost()
Expand Down
13 changes: 7 additions & 6 deletions playground/pages/test.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ const wpNuxtConfig = config.public.wpNuxt
<template>
<div>
<UContainer class="prose dark:prose-invert pt-5">
<h2>wpUri:</h2>
<h2>Examples of predefined composables</h2>
<h3>wpUri:</h3>
<pre>{{ wpUri }}</pre>
<h2>wpNuxtConfig:</h2>
<h3>wpNuxtConfig:</h3>
<pre>{{ wpNuxtConfig }}</pre>
<h2>await useViewer()</h2>
<h3>await useViewer()</h3>
<pre>{{ viewer }}</pre>
<h2>getCurrentUserName()</h2>
<h3>getCurrentUserName()</h3>
<pre>{{ userName }}</pre>
<h2>isStaging()</h2>
<h3>isStaging()</h3>
<pre>{{ staging }}</pre>
<h2>useGeneralSettings()</h2>
<h3>useGeneralSettings()</h3>
<pre>{{ settings }}</pre>
</UContainer>
</div>
Expand Down
928 changes: 340 additions & 588 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { promises as fsp } from 'node:fs'
import { upperFirst } from 'scule'
import type { Import } from 'unimport'
import type { WPNuxtConfigComposables, WPNuxtQuery } from './types'
import { getLogger } from './utils'
import { parseDoc } from './useParser'

export interface WPNuxtContext {
composables: WPNuxtConfigComposables
template?: string
fns: WPNuxtQuery[]
fnImports?: Import[]
generateImports?: () => string
generateDeclarations?: () => string
docs?: string[]
}

export async function prepareContext(ctx: WPNuxtContext) {
const logger = getLogger()
if (ctx.docs) {
await prepareFunctions(ctx)
}

const fnName = (fn: string) => ctx.composables.prefix + upperFirst(fn)

const fnExp = (q: WPNuxtQuery, typed = false) => {
const functionName = fnName(q.name)
if (!typed) {
return `export const ${functionName} = (params) => fetchContent('${q.name}', params)`
}
return ` export const ${functionName}: (params?: ${q.name}QueryVariables) => AsyncData<${q.name}Query | undefined, FetchError | null | undefined>`
}

ctx.generateImports = () => [
'import { useGraphqlQuery } from \'#graphql-composable\'',
...ctx.fns.map(f => fnExp(f))
].join('\n')

ctx.generateDeclarations = () => [
`import type { ${ctx.fns.map(o => getQueryTypeTemplate(o)).join(', ')} } from '#graphql-operations'`,
'import { AsyncData } from \'nuxt/app\'',
'import { FetchError } from \'ofetch\'',
'declare module \'#wpnuxt\' {',
...([
...ctx.fns!.map(f => fnExp(f, true))
]),
'}'
].join('\n')

ctx.fnImports = ctx.fns.map((fn): Import => ({ from: '#wpnuxt', name: fnName(fn.name) }))

logger.debug('generated WPNuxt composables: ')
ctx.fns.forEach(f => logger.debug(` ${fnName(f.name)}()`))
}

function getQueryTypeTemplate(q: WPNuxtQuery) {
return `${q.name}Query, ${q.name}QueryVariables`
}

async function prepareFunctions(ctx: WPNuxtContext) {
if (!ctx.docs) {
getLogger().error('no GraphQL query documents were found!')
return
}
for await (const doc of ctx.docs) {
const operations = await parseDoc(await fsp.readFile(doc, 'utf8'))
operations.forEach((query) => {
ctx.fns.push(query)
})
}
}
17 changes: 17 additions & 0 deletions src/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { statSync } from 'node:fs'
import { type Resolver, resolveFiles } from '@nuxt/kit'
import { prepareContext, type WPNuxtContext } from './context'

const allowDocument = (f: string, resolver: Resolver) => {
const isSchema = f.match(/([^/]+)\.(gql|graphql)$/)?.[0]?.toLowerCase().includes('schema')
return !isSchema && !!statSync(resolver.resolve(f)).size
}
export async function generateWPNuxtComposables(ctx: WPNuxtContext, queryOutputPath: string, resolver: Resolver) {
const gqlMatch = '**/*.{gql,graphql}'
const documents: string[] = []
const files = (await resolveFiles(queryOutputPath, [gqlMatch, '!**/schemas'], { followSymbolicLinks: false })).filter(doc => allowDocument(doc, resolver))
documents.push(...files)
ctx.docs = documents

await prepareContext(ctx)
}
Loading

0 comments on commit d041d99

Please sign in to comment.