Skip to content

Commit

Permalink
chore: write graphql schema, fragments and config file to cache (#26829)
Browse files Browse the repository at this point in the history
* chore: write graphql schema, fragments and config file to cache

To provide graphql config baseline support for:
- vscode-graphql
- graphql-codegen
- graphiql 2 (upcoming)

- `fragments.graphql` is generated from plugins/core so implicit fragment support works across all config-consuming tooling
- `schema.graphql` is written to file to make tooling faster, and to ensure tooling works without `develop` process running
- `graphql.config.json` is written to the `.cache` directory, as `endpoints` config used by `vscode-graphql` for executing queries needs to know the current port.

each of the tools above can be configured to load `.cache/graphql.config.json` instead of from the root directory
this could not be achieved with a plugin, because the existing plugin interfaces don't provide a complete enough schema, or a collation of fragments.

* migrate to redux store and plugins

* consolidate to a single plugin, use store emitter

* chore: address review comments

* chore: gitignore index.js
  • Loading branch information
acao committed Sep 21, 2020
1 parent 5658b87 commit 8ad565f
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/gatsby-plugin-graphql-config/.babelrc
@@ -0,0 +1,3 @@
{
"presets": [["babel-preset-gatsby-package"]]
}
32 changes: 32 additions & 0 deletions packages/gatsby-plugin-graphql-config/.gitignore
@@ -0,0 +1,32 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

decls
dist

/*.js
34 changes: 34 additions & 0 deletions packages/gatsby-plugin-graphql-config/.npmignore
@@ -0,0 +1,34 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
*.un~
yarn.lock
src
flow-typed
coverage
decls
examples
69 changes: 69 additions & 0 deletions packages/gatsby-plugin-graphql-config/README.md
@@ -0,0 +1,69 @@
# gatsby-plugin-graphql-config

Persists gatsby graphql schema and fragments to the .cache directory, as well as a [graphql config](https://graphql-config.com) file to enable full-featured tooling for:

- [`vscode-graphql`](https://marketplace.visualstudio.com/items?itemName=Prisma.vscode-graphql), and other IDE extensions that use the official GraphQL LSP
- [`eslint-plugin-graphql`](https://github.com/apollographql/eslint-plugin-graphql)
- [`graphql code generator`](https://graphql-code-generator.com/) for gatsby projects using typescript
- eventually [`graphiql`](https://github.com/graphql/graphiql) will use it, even!

## Install

`npm install --save gatsby-plugin-graphql-config`

## How to use

First, add it to your plugin configuration:

```javascript
// In your gatsby-config.js
plugins: [`gatsby-plugin-graphql-config`]
```

**simplest setup**:
if you are able to configure your tools to seek a different `basePath` for loading graphql config, point them to `.cache` directory.

**manual setup for repos with no other graphql projects**:

If your project is _only_ a gatsby project, you can place a `graphql.config.js` file at the root of your gatsby project like this:

`<my project>/graphql.config.js`:

```js
// <my project>/graphql.config.js
module.exports = require("./.cache/graphql.config.json")
```

if it's in a subdirectory such as a `site/` folder, you would use this:

`<my project>/graphql.config.js`:

```js
module.exports = require("./site/.cache/graphql.config.json")
```

**for repositories with multiple graphql projects**

if your repository has multiple graphql projects including gatsby, you will want a config similar to this at the root:

`<my project>/graphql.config.js`:

```js
module.exports = {
projects: {
site: require("packages/site/.cache/graphql.config.json"),
server: {
schema: "packages/server/src/**/*.{graphql,gql}",
documents: "packages/server/src/queries/**/*.{ts,tsx,js,jsx}",
},
},
}
```

### How it works

It writes out these files to the gatsby `.cache` directory:

- `schema.graphql` - a complete representation of the schema, including plugins
- `fragments.graphql` - all user, plugin and gatsby-core provided fragments in one file
- `graphql.config.json` - a graphql-config@3 compatible config file with absolute file resolutions
39 changes: 39 additions & 0 deletions packages/gatsby-plugin-graphql-config/package.json
@@ -0,0 +1,39 @@
{
"name": "gatsby-plugin-graphql-config",
"description": "Gatsby plugin to write out a graphql-config with develop process endpoint configured",
"version": "1.0.0",
"author": "Rikki Schulte <rikki.schulte@gmail.com>",
"bugs": {
"url": "https://github.com/gatsbyjs/gatsby/issues"
},
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"babel-preset-gatsby-package": "^0.5.2",
"cpx": "^1.5.0",
"cross-env": "^5.2.1",
"rewire": "^4.0.1"
},
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-plugin-graphql-config#readme",
"keywords": [
"gatsby",
"gatsby-plugin",
"graphql",
"config"
],
"license": "MIT",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/gatsbyjs/gatsby.git",
"directory": "packages/gatsby-plugin-graphql-config"
},
"scripts": {
"build": "babel src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.js\"",
"watch": "babel -w src --out-dir . --ignore \"**/__tests__\" --extensions \".ts,.js\"",
"prepare": "cross-env NODE_ENV=production npm run build"
},
"engines": {
"node": ">=10.13.0"
}
}
122 changes: 122 additions & 0 deletions packages/gatsby-plugin-graphql-config/src/gatsby-node.ts
@@ -0,0 +1,122 @@
import * as fs from "fs-extra"
import { resolve, join } from "path"
import { GraphQLSchema, printSchema } from "graphql"
import type { GatsbyReduxStore } from "gatsby/src/redux"
import type { IStateProgram } from "gatsby/src/internal"

async function cacheGraphQLConfig(program: IStateProgram): Promise<void> {
try {
const base = program.directory
const configJSONString = JSON.stringify(
{
schema: resolve(base, `.cache/schema.graphql`),
documents: [
resolve(base, `src/**/**.{ts,js,tsx,jsx,esm}`),
resolve(base, `.cache/fragments.graphql`),
],
extensions: {
endpoints: {
default: {
url: `${program.https ? `https://` : `http://`}${program.host}:${
program.port
}/___graphql`,
},
},
},
},
null,
2
)

fs.writeFileSync(
resolve(base, `.cache`, `graphql.config.json`),
configJSONString
)
console.log(`[gatsby-plugin-graphql-config] wrote config file to .cache`)
} catch (err) {
console.error(
`[gatsby-plugin-graphql-config] failed to write config file to .cache`
)
console.error(err)
}
}

const createFragmentCacheHandler = (
cacheDirectory: string,
store: GatsbyReduxStore
) => async (): Promise<void> => {
try {
const currentDefinitions = store.getState().definitions

const fragmentString = Array.from(currentDefinitions.entries())
.filter(([_, def]) => def.isFragment)
.map(([_, def]) => `# ${def.filePath}\n${def.printedAst}`)
.join(`\n`)

await fs.writeFile(
join(cacheDirectory, `fragments.graphql`),
fragmentString
)

console.log(`[gatsby-plugin-graphql-config] wrote fragments file to .cache`)
} catch (err) {
console.error(
`[gatsby-plugin-graphql-config] failed writing fragments file to .cache`
)
console.error(err)
}
}

const cacheSchema = async (
cacheDirectory: string,
schema: GraphQLSchema
): Promise<void> => {
try {
console.log(`printing schema`)
const schemaSDLString = printSchema(schema, { commentDescriptions: true })

await fs.writeFile(join(cacheDirectory, `schema.graphql`), schemaSDLString)

console.log(`[gatsby-plugin-graphql-config] wrote SDL file to .cache`)
} catch (err) {
console.error(
`[gatsby-plugin-graphql-config] failed writing schema file to .cache`
)
console.error(err)
}
}

const createSchemaCacheHandler = (
cacheDirectory: string,
store: GatsbyReduxStore
) => async (): Promise<void> => {
const { schema } = store.getState()
await cacheSchema(cacheDirectory, schema)
}

export async function onPostBootstrap({
store,
emitter,
}: {
store: GatsbyReduxStore
emitter: any
}): Promise<void> {
const { program, schema } = store.getState()

const cacheDirectory = resolve(program.directory, `.cache`)

if (!fs.existsSync(cacheDirectory)) {
return
}
// cache initial schema
await cacheSchema(cacheDirectory, schema)
// cache graphql config file
await cacheGraphQLConfig(program)
// Important! emitter.on is an internal Gatsby API. It is highly discouraged to use in plugins and can break without a notice.
// FIXME: replace it with a more appropriate API call when available.
emitter.on(
`SET_GRAPHQL_DEFINITIONS`,
createFragmentCacheHandler(cacheDirectory, store)
)
emitter.on(`SET_SCHEMA`, createSchemaCacheHandler(cacheDirectory, store))
}
1 change: 1 addition & 0 deletions packages/gatsby-plugin-graphql-config/src/index.ts
@@ -0,0 +1 @@
// no-op
3 changes: 3 additions & 0 deletions packages/gatsby-plugin-graphql-config/tsconfig.json
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
4 changes: 4 additions & 0 deletions packages/gatsby/src/query/query-compiler.js
Expand Up @@ -31,6 +31,8 @@ const {
import { getGatsbyDependents } from "../utils/gatsby-dependents"
const { store } = require(`../redux`)
import * as actions from "../redux/actions/internal"
import { boundActionCreators } from "../redux/actions"

import { websocketManager } from "../utils/websocket-manager"
const { default: FileParser } = require(`./file-parser`)
const {
Expand Down Expand Up @@ -173,6 +175,8 @@ export const processQueries = ({
parentSpan
)

boundActionCreators.setGraphQLDefinitions(definitionsByName)

return processDefinitions({
schema,
operations,
Expand Down
19 changes: 19 additions & 0 deletions packages/gatsby/src/redux/actions/internal.ts
Expand Up @@ -16,6 +16,8 @@ import {
IPageQueryRunAction,
IRemoveStaleJobAction,
ISetSiteConfig,
IDefinitionMeta,
ISetGraphQLDefinitionsAction,
} from "../types"

import { gatsbyConfigSchema } from "../../joi-schemas/joi"
Expand Down Expand Up @@ -117,6 +119,23 @@ export const queryExtracted = (
}
}

/**
* Set Definitions for fragment extraction, etc.
*
* Used by developer tools such as vscode-graphql & graphiql
*
* query-compiler.js.
* @private
*/
export const setGraphQLDefinitions = (
definitionsByName: Map<string, IDefinitionMeta>
): ISetGraphQLDefinitionsAction => {
return {
type: `SET_GRAPHQL_DEFINITIONS`,
payload: definitionsByName,
}
}

/**
*
* Report that the Relay Compiler found a graphql error when attempting to extract a query
Expand Down
13 changes: 13 additions & 0 deletions packages/gatsby/src/redux/reducers/definitions.ts
@@ -0,0 +1,13 @@
import { ActionsUnion, IGatsbyState } from "../types"

export const definitionsReducer = (
state: IGatsbyState["definitions"] = new Map(),
action: ActionsUnion
): IGatsbyState["definitions"] => {
switch (action.type) {
case `SET_GRAPHQL_DEFINITIONS`:
return action.payload
default:
return state
}
}
2 changes: 2 additions & 0 deletions packages/gatsby/src/redux/reducers/index.ts
Expand Up @@ -3,6 +3,7 @@ import { reducer as logReducer } from "gatsby-cli/lib/reporter/redux/reducer"
import { pagesReducer } from "./pages"
import { redirectsReducer } from "./redirects"
import { schemaReducer } from "./schema"
import { definitionsReducer } from "./definitions"
import { staticQueryComponentsReducer } from "./static-query-components"
import { statusReducer } from "./status"
import { webpackReducer } from "./webpack"
Expand Down Expand Up @@ -31,6 +32,7 @@ import { staticQueriesByTemplateReducer } from "./static-queries-by-template"
* @property exports.nodesTouched Set<string>
*/
export {
definitionsReducer as definitions,
programReducer as program,
nodesReducer as nodes,
nodesByTypeReducer as nodesByType,
Expand Down

0 comments on commit 8ad565f

Please sign in to comment.