Skip to content
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
93 changes: 93 additions & 0 deletions .github/scripts/update-cdn-versions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env node

/**
* Generates examples/graphiql-cdn/index.html
*/

import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';

const PACKAGES = [
'react',
'react-dom',
'graphiql',
'@graphiql/plugin-explorer',
'@graphiql/react',
'@graphiql/toolkit',
'graphql',
];

async function fetchIntegrityHash(url) {
const response = await fetch(url, { redirect: 'follow' });
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
}
const content = await response.text();
const hash = crypto.createHash('sha384').update(content).digest('base64');
return [url, `sha384-${hash}`];
}

async function fetchLatestVersion(packageName) {
const url = `https://registry.npmjs.org/${packageName}/latest`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch ${packageName}: ${response.statusText}`);
}
const { version } = await response.json();
return [packageName, version]
}

async function main () {
const versions = Object.fromEntries(await Promise.all(PACKAGES.map(fetchLatestVersion)));
const cdnUrl = packageName => `https://esm.sh/${packageName}@${versions[packageName]}`;

// JS
const imports = {
'react': cdnUrl('react'),
'react/': `${cdnUrl('react-dom')}/`,
'react-dom': cdnUrl('react-dom'),
'react-dom/': `${cdnUrl('react-dom')}/`,
'graphiql': `${cdnUrl('graphiql')}?standalone&external=react,react-dom,@graphiql/react,graphql`,
'graphiql/': `${cdnUrl('graphiql')}/`,
'@graphiql/plugin-explorer': `${cdnUrl('@graphiql/plugin-explorer')}?standalone&external=react,@graphiql/react,graphql`,
'@graphiql/react': `${cdnUrl('@graphiql/react')}?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid`,
'@graphiql/toolkit': `${cdnUrl('@graphiql/toolkit')}?standalone&external=graphql`,
'graphql': cdnUrl('graphql'),
'@emotion/is-prop-valid': "data:text/javascript,"
};

const integrity = Object.fromEntries(await Promise.all([
cdnUrl('react'),
cdnUrl('react-dom'),
cdnUrl('graphiql'),
`${cdnUrl('graphiql')}?standalone&external=react,react-dom,@graphiql/react,graphql`,
cdnUrl('@graphiql/plugin-explorer'),
`${cdnUrl('@graphiql/react')}?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid`,
`${cdnUrl('@graphiql/toolkit')}?standalone&external=graphql`,
cdnUrl('graphql'),
].map(fetchIntegrityHash)));

const importMap = { imports, integrity };

// CSS
const graphiqlCss = `${cdnUrl('graphiql')}/dist/style.css`;
const graphiqlPluginExplorer = `${cdnUrl('@graphiql/plugin-explorer')}/dist/style.css`;

// Generate index.html
const templatePath = path.join(import.meta.dirname, '../../resources/index.html.template');
const template = fs.readFileSync(templatePath, 'utf8');

const indent = lines => lines.split('\n').map(line => ` ${line}`).join('\n');

const output = template
.replace('{{IMPORTMAP}}', indent(JSON.stringify(importMap, null, 2)))
.replace('{{GRAPHIQL_CSS_URL}}', graphiqlCss)
.replace('{{GRAPHIQL_CSS_INTEGRITY}}', (await fetchIntegrityHash(graphiqlCss))[1])
.replace('{{PLUGIN_EXPLORER_CSS_URL}}', graphiqlPluginExplorer)
.replace('{{PLUGIN_EXPLORER_CSS_INTEGRITY}}', (await fetchIntegrityHash(graphiqlPluginExplorer))[1]);

console.log(output);
}

main();
61 changes: 61 additions & 0 deletions .github/workflows/update-cdn-example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Update CDN Example Dependencies

on:
schedule:
# Run every Monday at 10:00 UTC
- cron: '0 10 * * 1'
workflow_dispatch: # Allow manual triggering
repository_dispatch:
types: [update-cdn-dependencies]

permissions:
contents: write
pull-requests: write

jobs:
update-dependencies:
name: Check and Update CDN Dependencies
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Install Dependencies
run: npm install -g npm-check-updates semver

- name: Check for Updates and Update index.html
id: update
run: |
node .github/scripts/update-cdn-versions.mjs > examples/graphiql-cdn/index.html

- name: Check for Changes
id: check-changes
run: |
if git diff --quiet; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.check-changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore(cdn-example): update dependencies to latest versions'
title: 'chore(cdn-example): update dependencies to latest versions'
body: |
This PR automatically updates the CDN example dependencies to their latest versions.

🤖 This PR was automatically generated automatically, beep boop
branch: automated/update-cdn-dependencies
delete-branch: true
labels: |
dependencies
automated
43 changes: 29 additions & 14 deletions examples/graphiql-cdn/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@
font-size: 4rem;
}
</style>
<link rel="stylesheet" href="https://esm.sh/graphiql/dist/style.css" />
<link
rel="stylesheet"
href="https://esm.sh/@graphiql/plugin-explorer/dist/style.css"
href="https://esm.sh/[email protected]/dist/style.css"
integrity="sha384-f6GHLfCwoa4MFYUMd3rieGOsIVAte/evKbJhMigNdzUf52U9bV2JQBMQLke0ua+2"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="https://esm.sh/@graphiql/[email protected]/dist/style.css"
integrity="sha384-vTFGj0krVqwFXLB7kq/VHR0/j2+cCT/B63rge2mULaqnib2OX7DVLUVksTlqvMab"
crossorigin="anonymous"
/>
<!--
* Note:
Expand All @@ -41,20 +48,27 @@
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/[email protected]",
"react/": "https://esm.sh/[email protected]/",

"react-dom": "https://esm.sh/[email protected]",
"react-dom/": "https://esm.sh/[email protected]/",

"graphiql": "https://esm.sh/graphiql?standalone&external=react,react-dom,@graphiql/react,graphql",
"graphiql/": "https://esm.sh/graphiql/",
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/plugin-explorer?standalone&external=react,@graphiql/react,graphql",
"@graphiql/react": "https://esm.sh/@graphiql/react?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",

"@graphiql/toolkit": "https://esm.sh/@graphiql/toolkit?standalone&external=graphql",
"react": "https://esm.sh/[email protected]",
"react/": "https://esm.sh/[email protected]/",
"react-dom": "https://esm.sh/[email protected]",
"react-dom/": "https://esm.sh/[email protected]/",
"graphiql": "https://esm.sh/[email protected]?standalone&external=react,react-dom,@graphiql/react,graphql",
"graphiql/": "https://esm.sh/[email protected]/",
"@graphiql/plugin-explorer": "https://esm.sh/@graphiql/[email protected]?standalone&external=react,@graphiql/react,graphql",
"@graphiql/react": "https://esm.sh/@graphiql/[email protected]?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid",
"@graphiql/toolkit": "https://esm.sh/@graphiql/[email protected]?standalone&external=graphql",
"graphql": "https://esm.sh/[email protected]",
"@emotion/is-prop-valid": "data:text/javascript,"
},
"integrity": {
"https://esm.sh/[email protected]": "sha384-nOZt6fKb998R5rObOHPrlA6K4MfsPJA1SM1RF6XLKrCs7R7DRWe1QMpy9pFMCd7u",
"https://esm.sh/[email protected]": "sha384-g9QErJ4ghsQsmmARwwncWld1ANkSBgPJme9hzmI2Vmq4+iZDrt3tq9hu1iwQI2xG",
"https://esm.sh/[email protected]": "sha384-iJccq+zsT06wL6UQ27mjQ6OoghntU/ZdWkOmza8f/iD4hVJXQOgZeH/230Pm2y3V",
"https://esm.sh/[email protected]?standalone&external=react,react-dom,@graphiql/react,graphql": "sha384-32Vv0P2Qy9UWdE0/n9/nFmGh8tM5/vMgpAarsa+UdD6So+aS6DVBQZDIjS2lU52e",
"https://esm.sh/@graphiql/[email protected]": "sha384-KrkvikOOZWjeIeO92D9EDOJQQ7QI7LQPDQrBqloLwTVOz4jNXTPfYA1PYOpI8/UI",
"https://esm.sh/@graphiql/[email protected]?standalone&external=react,react-dom,graphql,@graphiql/toolkit,@emotion/is-prop-valid": "sha384-vbrVVt6MhT20iaS0B9nwXO210Cix1sSiP+RyMEdFGEj5ZHbXJz96XxtckeWTrnkd",
"https://esm.sh/@graphiql/[email protected]?standalone&external=graphql": "sha384-ZsnupyYmzpNjF1Z/81zwi4nV352n4P7vm0JOFKiYnAwVGOf9twnEMnnxmxabMBXe",
"https://esm.sh/[email protected]": "sha384-uhRXaGfgCFqosYlwSLNd7XpDF9kcSUycv5yVbjjhH5OrE675kd0+MNIAAaSc+1Pi"
}
}
</script>
Expand Down Expand Up @@ -90,3 +104,4 @@
</div>
</body>
</html>

82 changes: 82 additions & 0 deletions resources/index.html.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!--
* Copyright (c) 2025 GraphQL Contributors
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GraphiQL 5 with React 19 and GraphiQL Explorer</title>
<style>
body {
margin: 0;
}

#graphiql {
height: 100dvh;
}

.loading {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
}
</style>
<link
rel="stylesheet"
href="{{GRAPHIQL_CSS_URL}}"
integrity="{{GRAPHIQL_CSS_INTEGRITY}}"
crossorigin="anonymous"
/>
<link
rel="stylesheet"
href="{{PLUGIN_EXPLORER_CSS_URL}}"
integrity="{{PLUGIN_EXPLORER_CSS_INTEGRITY}}"
crossorigin="anonymous"
/>
<!--
* Note:
* The ?standalone flag bundles the module along with all of its `dependencies`, excluding `peerDependencies`, into a single JavaScript file.
* `@emotion/is-prop-valid` is a shim to remove the console error ` module "@emotion /is-prop-valid" not found`. Upstream issue: https://github.com/motiondivision/motion/issues/3126
-->
<script type="importmap">
{{IMPORTMAP}}
</script>
<script type="module">
import React from 'react';
import ReactDOM from 'react-dom/client';
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
import { createGraphiQLFetcher } from '@graphiql/toolkit';
import { explorerPlugin } from '@graphiql/plugin-explorer';
import 'graphiql/setup-workers/esm.sh';

const fetcher = createGraphiQLFetcher({
url: 'https://countries.trevorblades.com',
});
const plugins = [HISTORY_PLUGIN, explorerPlugin()];

function App() {
return React.createElement(GraphiQL, {
fetcher,
plugins,
defaultEditorToolsVisibility: true,
});
}

const container = document.getElementById('graphiql');
const root = ReactDOM.createRoot(container);
root.render(React.createElement(App));
</script>
</head>
<body>
<div id="graphiql">
<div class="loading">Loading…</div>
</div>
</body>
</html>