-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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): use graphiql-explorer #14280
Changes from 2 commits
3c8299d
08f6417
ca9f47c
311485b
f2c10ac
3c02259
a8fc37f
0cae8b2
0e3cd30
ee4c119
866c30f
7688af9
b5c209b
fa07a83
93f4f4d
d57901f
504eb84
cd709e8
0069988
44cbf89
d6502e7
67acb19
0556731
ea49673
b5229d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/*.js | ||
/*.html | ||
yarn.lock |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# gatsby-graphiql-explorer | ||
|
||
A package to extend the default [GraphiQL][graphiql] IDE with useful features for Gatsby users. | ||
|
||
_Note: accessible at http://localhost:8000/___graphql after running `gatby develop`_ | ||
|
||
![Gatsby GraphiQL Explorer](./assets/gatsby-graphiql-explorer.png) | ||
|
||
## Features | ||
|
||
- Offline support - for when you need to work on your excellent Gatsby app on a plane, train, or elsewhere off the grid | ||
- [GraphiQL Explorer][graphiql-explorer] - an interactive explorer plugin to visually create and interact with the GraphQL schema | ||
- _All_ the expected features you know and love from [GraphiQL][graphiql] | ||
|
||
[graphiql]: https://github.com/graphql/graphiql | ||
[graphiql-explorer]: https://github.com/OneGraph/graphiql-explorer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
{ | ||
"name": "gatsby-graphiql-explorer", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this probably need better package name - this is kind of express middleware, but not exactly, because it's not used as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's easy to bike shed here--I don't really have a problem with the name. We could also scope it, e.g. |
||
"version": "0.0.1", | ||
"description": "Stub description for gatsby-graphiql-explorer", | ||
pieh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"main": "index.js", | ||
"scripts": { | ||
"build:app": "webpack --config ./src/app/webpack.config.js", | ||
"build:babel": "babel src/index.js --out-dir . --ignore **/__tests__", | ||
"build": "npm-run-all --parallel build:app build:babel", | ||
"prepare": "cross-env NODE_ENV=production npm run build", | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"watch:app": "npm run build:app -- --watch", | ||
"watch:babel": "npm run build:babel -- --watch", | ||
"watch": "npm-run-all --parallel watch:app watch:babel" | ||
}, | ||
"keywords": [ | ||
"gatsby" | ||
], | ||
"author": "", | ||
"bugs": { | ||
"url": "https://github.com/gatsbyjs/gatsby/issues" | ||
}, | ||
"homepage": "https://github.com/gatsbyjs/gatsby/tree/master/packages/gatsby-graphiql-explorer#readme", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/gatsbyjs/gatsby.git" | ||
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
"@babel/runtime": "^7.0.0", | ||
"graphiql": "^0.13.0", | ||
"graphiql-explorer": "^0.3.7", | ||
"react": "^16.8.6", | ||
"react-dom": "^16.8.6", | ||
"whatwg-fetch": "^3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.0.0", | ||
"@babel/core": "^7.0.0", | ||
"@babel/plugin-proposal-class-properties": "^7.0.0", | ||
"@babel/plugin-transform-runtime": "^7.0.0", | ||
"@babel/preset-env": "^7.4.1", | ||
"@babel/preset-react": "^7.0.0", | ||
"babel-loader": "^8.0.0", | ||
"babel-preset-gatsby-package": "^0.1.4", | ||
"cross-env": "^5.0.5", | ||
"css-loader": "^1.0.0", | ||
"html-webpack-plugin": "^3.2.0", | ||
"npm-run-all": "4.1.5", | ||
"style-loader": "^0.21.0", | ||
"webpack-cli": "^3.3.2" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
body { | ||
margin: 0; | ||
padding: 0; | ||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", | ||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", | ||
sans-serif; | ||
-webkit-font-smoothing: antialiased; | ||
-moz-osx-font-smoothing: grayscale; | ||
} | ||
|
||
code { | ||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", | ||
monospace; | ||
} | ||
|
||
.graphiql-container { | ||
height: 100vh; | ||
width: 100vw; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
import React from "react" | ||
import ReactDOM from "react-dom" | ||
|
||
import GraphiQL from "graphiql" | ||
import GraphiQLExplorer from "graphiql-explorer" | ||
import { getIntrospectionQuery, buildClientSchema } from "graphql" | ||
|
||
import "whatwg-fetch" | ||
|
||
import "graphiql/graphiql.css" | ||
import "./app.css" | ||
|
||
const parameters = {} | ||
window.location.search | ||
.substr(1) | ||
.split(`&`) | ||
.forEach(function(entry) { | ||
var eq = entry.indexOf(`=`) | ||
if (eq >= 0) { | ||
parameters[decodeURIComponent(entry.slice(0, eq))] = decodeURIComponent( | ||
entry.slice(eq + 1) | ||
) | ||
} | ||
}) | ||
// Produce a Location query string from a parameter object. | ||
function locationQuery(params) { | ||
return ( | ||
`?` + | ||
Object.keys(params) | ||
.filter(function(key) { | ||
return Boolean(params[key]) | ||
}) | ||
.map(function(key) { | ||
return encodeURIComponent(key) + `=` + encodeURIComponent(params[key]) | ||
}) | ||
.join(`&`) | ||
) | ||
} | ||
|
||
// Derive a fetch URL from the current URL, sans the GraphQL parameters. | ||
const graphqlParamNames = { | ||
query: true, | ||
variables: true, | ||
operationName: true, | ||
} | ||
const otherParams = {} | ||
for (var k in parameters) { | ||
if (parameters.hasOwnProperty(k) && graphqlParamNames[k] !== true) { | ||
otherParams[k] = parameters[k] | ||
} | ||
} | ||
const fetchURL = locationQuery(otherParams) | ||
|
||
function graphQLFetcher(graphQLParams) { | ||
return fetch(fetchURL, { | ||
method: `post`, | ||
headers: { | ||
Accept: `application/json`, | ||
"Content-Type": `application/json`, | ||
}, | ||
body: JSON.stringify(graphQLParams), | ||
credentials: `include`, | ||
}).then(function(response) { | ||
return response.json() | ||
}) | ||
} | ||
|
||
// When the query and variables string is edited, update the URL bar so | ||
// that it can be easily shared. | ||
function onEditVariables(newVariables) { | ||
parameters.variables = newVariables | ||
updateURL() | ||
} | ||
function onEditOperationName(newOperationName) { | ||
parameters.operationName = newOperationName | ||
updateURL() | ||
} | ||
function updateURL() { | ||
history.replaceState(null, null, locationQuery(parameters)) | ||
} | ||
|
||
// We control query, so we need to recreate initial query text that show up | ||
// on visiting graphiql - in order it will be | ||
// - query from query string (if set) | ||
// - query stored in localStorage (which graphiql set when closing window) | ||
// - default empty query | ||
const DEFAULT_QUERY = | ||
parameters.query || | ||
(window.localStorage && window.localStorage.getItem(`graphiql:query`)) || | ||
`{ | ||
|
||
}` | ||
pieh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
class App extends React.Component { | ||
state = { schema: null, query: DEFAULT_QUERY, explorerIsOpen: true } | ||
pieh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
componentDidMount() { | ||
graphQLFetcher({ | ||
query: getIntrospectionQuery(), | ||
}).then(result => { | ||
this.setState({ schema: buildClientSchema(result.data) }) | ||
}) | ||
} | ||
|
||
_handleEditQuery = query => { | ||
parameters.query = query | ||
updateURL() | ||
this.setState({ query }) | ||
} | ||
|
||
_handleToggleExplorer = () => { | ||
this.setState({ explorerIsOpen: !this.state.explorerIsOpen }) | ||
} | ||
|
||
render() { | ||
const { query, schema } = this.state | ||
|
||
return ( | ||
<React.Fragment> | ||
<GraphiQLExplorer | ||
schema={schema} | ||
query={query} | ||
onEdit={this._handleEditQuery} | ||
explorerIsOpen={this.state.explorerIsOpen} | ||
onToggleExplorer={this._handleToggleExplorer} | ||
/> | ||
<GraphiQL | ||
ref={ref => (this._graphiql = ref)} | ||
fetcher={graphQLFetcher} | ||
schema={schema} | ||
query={query} | ||
onEditQuery={this._handleEditQuery} | ||
onEditVariables={onEditVariables} | ||
onEditOperationName={onEditOperationName} | ||
> | ||
<GraphiQL.Toolbar> | ||
<GraphiQL.Button | ||
onClick={() => this._graphiql.handlePrettifyQuery()} | ||
label="Prettify" | ||
title="Prettify Query (Shift-Ctrl-P)" | ||
/> | ||
<GraphiQL.Button | ||
onClick={() => this._graphiql.handleToggleHistory()} | ||
label="History" | ||
title="Show History" | ||
/> | ||
<GraphiQL.Button | ||
onClick={this._handleToggleExplorer} | ||
label="Explorer" | ||
title="Toggle Explorer" | ||
/> | ||
</GraphiQL.Toolbar> | ||
</GraphiQL> | ||
</React.Fragment> | ||
) | ||
} | ||
} | ||
|
||
ReactDOM.render(<App />, document.getElementById(`root`)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<title>GraphiQL</title> | ||
<meta name="robots" content="noindex" /> | ||
<meta name="referrer" content="origin" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
</head> | ||
<body> | ||
<div id="root" class="graphiql-container">Loading...</div> | ||
<script | ||
type="text/javascript" | ||
src="/___graphql/<%= htmlWebpackPlugin.files.chunks.main.entry %>?<%= htmlWebpackPlugin.files.chunks.main.hash %>" | ||
pieh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
></script> | ||
</body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
const path = require(`path`) | ||
const HtmlWebpackPlugin = require(`html-webpack-plugin`) | ||
const webpack = require(`webpack`) | ||
|
||
const mode = `production` | ||
module.exports = { | ||
entry: path.join(__dirname, `app.js`), | ||
mode, | ||
output: { | ||
path: path.join(__dirname, `..`, `..`), | ||
filename: `./app.js`, | ||
}, | ||
devtool: false, | ||
module: { | ||
rules: [ | ||
{ | ||
test: /\.js$/, | ||
exclude: /node_modules/, | ||
use: { | ||
loader: `babel-loader`, | ||
options: { | ||
presets: [ | ||
[ | ||
`@babel/preset-env`, | ||
{ | ||
corejs: 2, | ||
loose: true, | ||
modules: `commonjs`, | ||
useBuiltIns: `usage`, | ||
targets: [`>0.25%`, `not dead`], | ||
}, | ||
], | ||
[ | ||
`@babel/preset-react`, | ||
{ | ||
useBuiltIns: true, | ||
pragma: `React.createElement`, | ||
development: false, | ||
}, | ||
], | ||
], | ||
plugins: [ | ||
[ | ||
`@babel/plugin-proposal-class-properties`, | ||
{ | ||
loose: true, | ||
}, | ||
], | ||
], | ||
}, | ||
}, | ||
}, | ||
{ | ||
test: /\.css$/, | ||
use: [{ loader: `style-loader` }, { loader: `css-loader` }], | ||
}, | ||
], | ||
}, | ||
plugins: [ | ||
new HtmlWebpackPlugin({ | ||
template: path.resolve(__dirname, `index.ejs`), | ||
filename: `index.html`, | ||
inject: false, | ||
}), | ||
new webpack.DefinePlugin({ | ||
"process.env.NODE_ENV": JSON.stringify(`${mode}`), | ||
pieh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}), | ||
], | ||
stats: { | ||
warnings: false, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
const path = require(`path`) | ||
|
||
module.exports = (expressApp, { graphqlEndpoint }) => { | ||
const bundleUrlHandler = path.posix.join(graphqlEndpoint, `app.js`) | ||
expressApp.get(bundleUrlHandler, (req, res) => { | ||
res.set(`Cache-Control`, `public, max-age=31557600`) | ||
res.sendFile(path.join(__dirname, `app.js`)) | ||
}) | ||
|
||
expressApp.get(graphqlEndpoint, (req, res) => { | ||
res.sendFile(path.join(__dirname, `index.html`)) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple and concise 🤷♂ I think this will suffice!