diff options
| author | netop://ウィビ <paul@webb.page> | 2026-04-26 21:30:43 -0700 |
|---|---|---|
| committer | netop://ウィビ <paul@webb.page> | 2026-04-26 21:30:43 -0700 |
| commit | ab0a791cc0d75efe05ca8b6f9da8e21271fbf309 (patch) | |
| tree | 55ac984ba79be706ee2f83219fc2670bce89f75c /source/graphiql/render.ts | |
| parent | 84e0d5ad8ebb8e933823d9dabc8060294a4780dc (diff) | |
| download | gq-ab0a791cc0d75efe05ca8b6f9da8e21271fbf309.tar.gz gq-ab0a791cc0d75efe05ca8b6f9da8e21271fbf309.zip | |
adds awesome new GraphiQL renderer and an example
Diffstat (limited to 'source/graphiql/render.ts')
| -rwxr-xr-x | source/graphiql/render.ts | 239 |
1 files changed, 69 insertions, 170 deletions
diff --git a/source/graphiql/render.ts b/source/graphiql/render.ts index a0e4dc2..7adec57 100755 --- a/source/graphiql/render.ts +++ b/source/graphiql/render.ts @@ -3,213 +3,112 @@ /*** IMPORT ------------------------------------------- ***/ +import { default as dedent } from "@netopwibby/dedent"; import { filterXSS } from "xss"; /*** UTILITY ------------------------------------------ ***/ -import { getLoadingMarkup } from "./markup.ts"; +const filter = (val: string) => filterXSS(val, { + stripIgnoreTag: true, + stripIgnoreTagBody: ["script"], + whiteList: {} +}); -const CONFIG_ID = "playground-config"; -const loading = getLoadingMarkup(); - -const filter = (val: string) => { - return filterXSS(val, { - stripIgnoreTag: true, - stripIgnoreTagBody: ["script"], - whiteList: {} - }); -} - -const getCdnMarkup = ({ cdnUrl = "//cdn.jsdelivr.net/npm", faviconUrl, version }: { - cdnUrl?: string - faviconUrl?: string | null +const buildAssetUrl = ({ cdnUrl, suffix, version }: { + cdnUrl: string + suffix: string version?: string -}) => { - const buildCDNUrl = (packageName: string, suffix: string) => - filter(`${cdnUrl}/${packageName}${version ? `@${version}` : ""}/${suffix}` || ""); - - return ` - <link rel="stylesheet" href="${buildCDNUrl("graphql-playground-react", "build/static/css/index.css")}"/> - ${typeof faviconUrl === "string" ? `<link rel="shortcut icon" href="${filter(faviconUrl || "")}" />` : ""} - ${faviconUrl === undefined ? `<link rel="shortcut icon" href="${buildCDNUrl("graphql-playground-react", "build/favicon.png")}" />` : ""} - <script src="${buildCDNUrl("graphql-playground-react", "build/static/js/middleware.js")}"></script> - `; -} - -const renderConfig = (config: unknown) => { - return filterXSS(`<div id="${CONFIG_ID}">${JSON.stringify(config)}</div>`, { - whiteList: { div: ["id"] } - }); -}; +}) => filter(`${cdnUrl}/@eeeooolll/graphiql${version ? `@${version}` : ""}/dist/${suffix}`); /*** EXPORT ------------------------------------------- ***/ -export interface MiddlewareOptions { - codeTheme?: EditorColours; - config?: { [key: string]: unknown; }; - endpoint?: string; - env?: "electron" | "react"; - schema?: IntrospectionResult; - settings?: ISettings; - subscriptionEndpoint?: string; - tabs?: Tab[]; - workspaceName?: string; -} - -export type CursorShape = "line" | "block" | "underline"; -export type Theme = "dark" | "light"; - -export interface ISettings { - "editor.cursorShape": CursorShape; - "editor.fontFamily": string; - "editor.fontSize": number; - "editor.reuseHeaders": boolean; - "editor.theme": Theme; - "general.betaUpdates": boolean; - "request.credentials": string; - "request.globalHeaders": { [key: string]: string }; - "schema.polling.enable": boolean; - "schema.polling.endpointFilter": string; - "schema.polling.interval": number; - "tracing.hideTracingResponse": boolean; - "tracing.tracingSupported": boolean; -} - -export interface EditorColours { - atom: string; - attribute: string; - builtin: string; - comment: string; - cursorColor: string; - def: string; - editorBackground: string; - keyword: string; - leftDrawerBackground: string; - meta: string; - number: string; - property: string; - punctuation: string; - qualifier: string; - resultBackground: string; - rightDrawerBackground: string; - selection: string; - string: string; - string2: string; - variable: string; - ws: string; -} - -export interface IntrospectionResult { - __schema: { [key: string]: unknown; }; -} - -export interface RenderPageOptions extends MiddlewareOptions { +export interface RenderPageOptions { + /** Base CDN. Defaults to jsDelivr. */ cdnUrl?: string; - env?: "electron" | "react"; + /** GraphQL endpoint the embedded fetcher posts to. */ + endpoint?: string; + /** Optional `<link rel="shortcut icon">`. `null` skips, `undefined` falls back to default. */ faviconUrl?: string | null; + /** Document `<title>`. */ title?: string; + /** Pin `@eeeooolll/graphiql` to a specific version on the CDN. */ version?: string; } -export interface Tab { - endpoint: string; - headers?: { [key: string]: string }; - name?: string; - query: string; - responses?: string[]; - variables?: string; -} - /** - * Renders the GraphQL Playground HTML shell. + * Renders the GraphiQL HTML shell. + * + * Loads the prebuilt IIFE bundle from `@eeeooolll/graphiql` (registered as + * `window.EolGraphiQL`) and mounts it against `endpoint`. * * Usually called indirectly via `GraphQLHTTP({ graphiql: true })`; invoke it - * directly if you need to embed the Playground in a custom route. + * directly if you need to embed GraphiQL in a custom route. */ -export function renderPlaygroundPage(options: RenderPageOptions) { - const extendedOptions: - & Partial<{ - canSaveConfig: boolean - configString: string - }> - & RenderPageOptions = { - ...options, - canSaveConfig: false - }; - - if (options.config) - extendedOptions.configString = JSON.stringify(options.config, null, 2); - - if (!extendedOptions.endpoint && !extendedOptions.configString) - console.warn("WARNING: You did not provide an endpoint and do not have a .graphqlconfig. Make sure you have at least one of them."); - else if (extendedOptions.endpoint) - extendedOptions.endpoint = filter(extendedOptions.endpoint || ""); - - return ` +export function renderPlaygroundPage(options: RenderPageOptions): string { + const { + cdnUrl = "//cdn.jsdelivr.net/npm", + endpoint = "/graphql", + faviconUrl, + title = "GraphiQL", + version + } = options; + + const safeEndpoint = filter(endpoint); + const safeTitle = filter(title); + const scriptUrl = buildAssetUrl({ cdnUrl, suffix: "standalone.js", version }); + const styleUrl = buildAssetUrl({ cdnUrl, suffix: "standalone.css", version }); + + const faviconLink = + faviconUrl === null ? + "" : + typeof faviconUrl === "string" ? + `<link rel="shortcut icon" href="${filter(faviconUrl)}"/>` : + `<link rel="shortcut icon" href="${buildAssetUrl({ cdnUrl, suffix: "favicon.svg", version })}"/>`; + + return dedent` <!DOCTYPE html> <html lang="en"> <head> - <meta charset=utf-8/> - <meta name="viewport" content="user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, minimal-ui"/> - <link href="https://brick.a.ssl.fastly.net/Open+Sans:300,400,600,700/Source+Code+Pro:400,700" rel="stylesheet"/> - <title>${extendedOptions.title || "GraphQL Playground"}</title> - ${extendedOptions.env === "react" || extendedOptions.env === "electron" ? "" : getCdnMarkup(extendedOptions)} - </head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + <title>${safeTitle}</title> + ${faviconLink} + <link rel="stylesheet" href="${styleUrl}"/> - <body> <style> - html { - font-family: "Open Sans", sans-serif; - overflow: hidden; - } - - body { - background-color: #172a3a; - margin: 0; + *, + *::before, + *::after { + margin: 0; padding: 0; + box-sizing: inherit; } - #${CONFIG_ID} { - display: none; + html { + box-sizing: border-box; } - .playgroundIn { - animation: playgroundIn 0.5s ease-out forwards; + html, + body { + height: 100%; } - @keyframes playgroundIn { - from { - opacity: 0; - transform: translateY(10px); - } - - to { - opacity: 1; - transform: translateY(0); - } + #root { + height: 100vh; } </style> + </head> - ${loading.container} - ${renderConfig(extendedOptions)} - <div id="root"/> + <body> + <div id="root"></div> + + <script src="${scriptUrl}"></script> <script> window.addEventListener("load", () => { - ${loading.script} - const root = document.getElementById("root"); - root.classList.add("playgroundIn"); - const configText = document.getElementById("${CONFIG_ID}").innerText; - - if (configText && configText.length) { - try { - GraphQLPlayground.init(root, JSON.parse(configText)); - } catch(_) { - console.error("could not find config"); - } - } else { - GraphQLPlayground.init(root); - } + const fetcher = window.EolGraphiQL.createHttpFetcher({ url: "${safeEndpoint}" }); + + window.EolGraphiQL.mount(root, { fetcher }); }); </script> </body> |