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

Replace API Playgrounds with GraphiQL #3246

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
76,809 changes: 39,602 additions & 37,207 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/core/build/copy-static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import path from 'path';

const SCHEMAS_GLOB = '**/*.graphql';
const MESSAGES_GLOB = 'i18n/messages/**/*';
const GRAPHIQL_GLOB = 'graphiql/output/**/*';
const DEST_DIR = path.join(__dirname, '../dist');

function copyFiles(sourceGlob: string, destinationDir: string) {
Expand Down Expand Up @@ -42,17 +43,29 @@ function copyI18nMessages() {
}
}

function copyBundlededGraphiQLPage() {
try {
copyFiles(GRAPHIQL_GLOB, DEST_DIR);
console.log('Bundled GraphiQL page copied successfully!');
} catch (error) {
console.error('Error copying GraphiQL page:', error);
}
}

function build() {
copySchemas();
copyI18nMessages();
copyBundlededGraphiQLPage();
}

function watch() {
const watcher1 = chokidar.watch(SCHEMAS_GLOB, { cwd: path.join(__dirname, '../src') });
const watcher2 = chokidar.watch(MESSAGES_GLOB, { cwd: path.join(__dirname, '../src') });
const watcher3 = chokidar.watch(GRAPHIQL_GLOB, { cwd: path.join(__dirname, '../src') });

watcher1.on('change', copySchemas);
watcher2.on('change', copyI18nMessages);
watcher3.on('change', copyBundlededGraphiQLPage);

console.log('Watching for changes...');
}
Expand Down
17 changes: 15 additions & 2 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"scripts": {
"tsc:watch": "tsc -p ./build/tsconfig.build.json --watch",
"copy:watch": "ts-node build/copy-static.ts watch",
"build": "rimraf dist && tsc -p ./build/tsconfig.build.json && tsc -p ./build/tsconfig.cli.json && ts-node build/copy-static.ts build",
"build": "rimraf dist && tsc -p ./build/tsconfig.build.json && tsc -p ./build/tsconfig.cli.json && npm run build:graphiql && npm run build:copyfiles",
"build:copyfiles": "ts-node build/copy-static.ts build",
"build:graphiql": "webpack --config ./src/graphiql/webpack.config.js",
"watch": "concurrently npm:tsc:watch npm:copy:watch",
"lint": "eslint --fix .",
"test": "vitest --config vitest.config.mts --run",
Expand Down Expand Up @@ -81,7 +83,12 @@
"typeorm": "0.3.20"
},
"devDependencies": {
"react-dom": "^18.3.1",
"react": "^18.3.1",
"graphiql": "^3.7.2",
"@types/bcrypt": "^5.0.2",
"@graphiql/toolkit": "^0.11.1",
"html-inline-script-webpack-plugin": "^3.2.1",
"@types/cookie-session": "^2.0.48",
"@types/csv-parse": "^1.2.2",
"@types/express": "^4.17.21",
Expand All @@ -95,13 +102,19 @@
"@types/semver": "^7.5.8",
"better-sqlite3": "^11.3.0",
"chokidar": "^3.6.0",
"css-loader": "^7.1.2",
"esbuild-loader": "^4.2.2",
"fs-extra": "^11.2.0",
"glob": "^10.3.10",
"html-webpack-plugin": "^5.6.3",
"mysql": "^2.18.1",
"pg": "^8.11.3",
"rimraf": "^5.0.5",
"sql.js": "1.10.2",
"sqlite3": "^5.1.7",
"typescript": "5.3.3"
"style-loader": "^4.0.0",
"typescript": "5.3.3",
"webpack": "^5.96.1",
"webpack-cli": "^5.1.4"
}
}
25 changes: 22 additions & 3 deletions packages/core/src/api/config/configure-graphql-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { DynamicModule } from '@nestjs/common';
import { GraphQLModule, GraphQLTypesLoader } from '@nestjs/graphql';
import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
import { readFileSync } from 'fs';
import { buildSchema, extendSchema, GraphQLSchema, printSchema, ValidationContext } from 'graphql';
import path from 'path';

Expand Down Expand Up @@ -41,7 +42,7 @@ export interface GraphQLApiOptions {
typePaths: string[];
apiPath: string;
debug: boolean;
playground: boolean | any;
playground: boolean;
// eslint-disable-next-line @typescript-eslint/ban-types
resolverModule: Function;
validationRules: Array<(context: ValidationContext) => any>;
Expand Down Expand Up @@ -115,6 +116,20 @@ async function createGraphQLOptions(
apolloServerPlugins.unshift(new IdCodecPlugin(idCodecService));
}

const GraphiQLPlugin = {
async serverWillStart() {
return {
async renderLandingPage() {
return {
html: readFileSync(path.resolve(__dirname, '../../graphiql/output/index.html'), {
encoding: 'utf-8',
}).replace('__API_URL__', '/' + options.apiPath),
};
},
};
},
};

return {
path: '/' + options.apiPath,
typeDefs: printSchema(builtSchema),
Expand All @@ -125,13 +140,17 @@ async function createGraphQLOptions(
// We no longer rely on the upload facility bundled with Apollo Server, and instead
// manually configure the graphql-upload package. See https://github.com/vendure-ecommerce/vendure/issues/396
uploads: false,
playground: options.playground,
// similarly, the built-in playground is outdated and buggy, so we
// replace it with `GraphiQLPlugin` in the `plugins` array if
// `options.playground` is true
// https://github.com/vendure-ecommerce/vendure/issues/3244
playground: false,
csrfPrevention: false,
debug: options.debug || false,
context: (req: any) => req,
// This is handled by the Express cors plugin
cors: false,
plugins: apolloServerPlugins,
plugins: apolloServerPlugins.concat(options.playground ? [GraphiQLPlugin] : []),
validationRules: options.validationRules,
introspection: configService.apiOptions.introspection ?? true,
} as ApolloDriverConfig;
Expand Down
15 changes: 6 additions & 9 deletions packages/core/src/config/vendure-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ApolloServerPlugin } from '@apollo/server';
import { RenderPageOptions } from '@apollographql/graphql-playground-html';
import { DynamicModule, Type } from '@nestjs/common';
import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
import { LanguageCode } from '@vendure/common/lib/generated-types';
Expand Down Expand Up @@ -96,32 +95,30 @@ export interface ApiOptions {
shopApiPath?: string;
/**
* @description
* The playground config to the admin GraphQL API
* [ApolloServer playground](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructoroptions-apolloserver).
* Whether to display the admin API's GraphiQL interface
* [GraphiQL](https://github.com/graphql/graphiql/).
*
* @default false
*/
adminApiPlayground?: boolean | RenderPageOptions;
adminApiPlayground?: boolean;
/**
* @description
* The playground config to the shop GraphQL API
* [ApolloServer playground](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructoroptions-apolloserver).
* Whether to display the shop API's GraphiQL interface.
* [GraphiQL](https://github.com/graphql/graphiql/).
*
* @default false
*/
shopApiPlayground?: boolean | RenderPageOptions;
shopApiPlayground?: boolean;
/**
* @description
* The debug config to the admin GraphQL API
* [ApolloServer playground](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructoroptions-apolloserver).
*
* @default false
*/
adminApiDebug?: boolean;
/**
* @description
* The debug config to the shop GraphQL API
* [ApolloServer playground](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructoroptions-apolloserver).
*
* @default false
*/
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/graphiql/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<title>GraphiQL Playground</title>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}

#root {
height: 100vh;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
12 changes: 12 additions & 0 deletions packages/core/src/graphiql/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import { GraphiQL } from 'graphiql';
import React from 'react';
import { createRoot } from 'react-dom/client';
import 'graphiql/graphiql.css';

// this is replaced when the bundled graphiql code is read in
// src/api/config/config-graphql-module.ts
const fetcher = createGraphiQLFetcher({ url: '__API_URL__' });

const root = createRoot(document.getElementById('root'));
root.render(React.createElement(GraphiQL, { fetcher: fetcher }));
11 changes: 11 additions & 0 deletions packages/core/src/graphiql/output/index.html

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions packages/core/src/graphiql/output/main.js.LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*!
* get-value <https://github.com/jonschlinkert/get-value>
*
* Copyright (c) 2014-2018, Jon Schlinkert.
* Released under the MIT License.
*/

/*!
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

/*!
* is-primitive <https://github.com/jonschlinkert/is-primitive>
*
* Copyright (c) 2014-present, Jon Schlinkert.
* Released under the MIT License.
*/

/*!
* isobject <https://github.com/jonschlinkert/isobject>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
*/

/*!
* set-value <https://github.com/jonschlinkert/set-value>
*
* Copyright (c) Jon Schlinkert (https://github.com/jonschlinkert).
* Released under the MIT License.
*/

/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
45 changes: 45 additions & 0 deletions packages/core/src/graphiql/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const path = require('node:path');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlInlineScriptPlugin = require('html-inline-script-webpack-plugin');
const webpack = require('webpack');

/**
* @type {import('webpack').Configuration}
*/
module.exports = {
entry: path.resolve(__dirname, 'index.mjs'),
output: {
path: path.resolve(__dirname, 'output'),
clean: true,
publicPath: '',
},
module: {
rules: [
{
test: /\.m?js$/,
loader: 'esbuild-loader',
options: {
target: 'es2015',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.js', '.json', '.jsx', '.css', '.mjs'],
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, '/index.html'),
inject: 'body',
}),
new HtmlInlineScriptPlugin(),
],
};
8 changes: 2 additions & 6 deletions packages/create/templates/vendure-config.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,9 @@ export const config: VendureConfig = {
// but are best turned off for production for security
// reasons.
...(IS_DEV ? {
adminApiPlayground: {
settings: { 'request.credentials': 'include' },
},
adminApiPlayground: true,
adminApiDebug: true,
shopApiPlayground: {
settings: { 'request.credentials': 'include' },
},
shopApiPlayground: true,
shopApiDebug: true,
} : {}),
},
Expand Down
12 changes: 2 additions & 10 deletions packages/dev-server/dev-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,10 @@ export const devConfig: VendureConfig = {
apiOptions: {
port: API_PORT,
adminApiPath: ADMIN_API_PATH,
adminApiPlayground: {
settings: {
'request.credentials': 'include',
},
},
adminApiPlayground: true,
adminApiDebug: true,
shopApiPath: SHOP_API_PATH,
shopApiPlayground: {
settings: {
'request.credentials': 'include',
},
},
shopApiPlayground: true,
shopApiDebug: true,
},
authOptions: {
Expand Down
2 changes: 1 addition & 1 deletion packages/harden-plugin/src/harden.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { HardenPluginOptions } from './types';
*
* - It analyzes the complexity on incoming graphql queries and rejects queries that are too complex and
* could be used to overload the resources of the server.
* - It disables dev-mode API features such as introspection and the GraphQL playground app.
* - It disables dev-mode API features such as introspection and the GraphiQL playground app.
* - It removes field name suggestions to prevent trial-and-error schema sniffing.
*
* It is a recommended plugin for all production configurations.
Expand Down
2 changes: 1 addition & 1 deletion packages/harden-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface HardenPluginOptions {
* When set to `'prod'`, the plugin will disable dev-mode features of the GraphQL APIs:
*
* - introspection
* - GraphQL playground
* - GraphiQL playground
*
* @default 'prod'
*/
Expand Down
Loading