diff --git a/.changeset/dull-balloons-warn.md b/.changeset/dull-balloons-warn.md new file mode 100644 index 00000000000..6051c2248d9 --- /dev/null +++ b/.changeset/dull-balloons-warn.md @@ -0,0 +1,6 @@ +--- +'@graphiql/react': minor +'graphiql': major +--- + +update graphiql-cdn example to show how to load workers with esm.sh diff --git a/examples/graphiql-cdn/index.html b/examples/graphiql-cdn/index.html index 88816672c0c..1312edc635f 100644 --- a/examples/graphiql-cdn/index.html +++ b/examples/graphiql-cdn/index.html @@ -1,5 +1,5 @@ @@ -62,12 +61,29 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; // Import GraphiQL and the Explorer plugin - import { GraphiQL } from 'graphiql'; + import { GraphiQL, HISTORY_PLUGIN } from 'graphiql'; import { createGraphiQLFetcher } from '@graphiql/toolkit'; // Required to be before `@graphiql/plugin-code-exporter` import 'regenerator-runtime/runtime'; import { codeExporterPlugin } from '@graphiql/plugin-code-exporter'; + import createJSONWorker from 'https://esm.sh/monaco-editor/esm/vs/language/json/json.worker.js?worker'; + import createGraphQLWorker from 'https://esm.sh/monaco-graphql/esm/graphql.worker.js?worker'; + import createEditorWorker from 'https://esm.sh/monaco-editor/esm/vs/editor/editor.worker.js?worker'; + + globalThis.MonacoEnvironment = { + getWorker(_workerId, label) { + console.info('MonacoEnvironment.getWorker', { label }); + switch (label) { + case 'json': + return createJSONWorker(); + case 'graphql': + return createGraphQLWorker(); + } + return createEditorWorker(); + }, + }; + const fetcher = createGraphiQLFetcher({ url: 'https://countries.trevorblades.com', }); @@ -106,10 +122,14 @@ }, ], }); + + const plugins = [HISTORY_PLUGIN, codeExporter]; + function App() { return React.createElement(GraphiQL, { fetcher, - plugins: [codeExporter], + plugins, + defaultEditorToolsVisibility: true, }); } diff --git a/packages/graphiql-plugin-doc-explorer/src/components/search.tsx b/packages/graphiql-plugin-doc-explorer/src/components/search.tsx index 97d5c46c899..95fdd852f7c 100644 --- a/packages/graphiql-plugin-doc-explorer/src/components/search.tsx +++ b/packages/graphiql-plugin-doc-explorer/src/components/search.tsx @@ -58,7 +58,11 @@ export const Search: FC = () => { const navItem = explorerNavStack.at(-1)!; - const onSelect = (def: TypeMatch | FieldMatch) => { + const onSelect = (def: TypeMatch | FieldMatch | null) => { + // `null` when we remove search value + if (!def) { + return; + } push( 'field' in def ? { name: def.field.name, def: def.field } @@ -106,9 +110,9 @@ export const Search: FC = () => { results.types.length + results.fields.length === 0 ? ( -
  • +
    No results found -
  • + ) : ( results.within.map((result, i) => ( = ({ path }) => { const [{ width, height }, setDimensions] = useState({ diff --git a/packages/graphiql-react/src/components/query-editor.tsx b/packages/graphiql-react/src/components/query-editor.tsx index e10c0493341..2bf263550c3 100644 --- a/packages/graphiql-react/src/components/query-editor.tsx +++ b/packages/graphiql-react/src/components/query-editor.tsx @@ -89,6 +89,7 @@ export const QueryEditor: FC = ({ useEffect(() => { onClickReferenceRef.current = onClickReference; }, [onClickReference]); + /* useEffect(() => { void importCodeMirrorImports().then(CodeMirror => { diff --git a/packages/graphiql-react/src/components/toolbar-menu/index.tsx b/packages/graphiql-react/src/components/toolbar-menu/index.tsx index 7183993d976..a5f54241283 100644 --- a/packages/graphiql-react/src/components/toolbar-menu/index.tsx +++ b/packages/graphiql-react/src/components/toolbar-menu/index.tsx @@ -5,10 +5,10 @@ import { DropdownMenu } from '../dropdown-menu'; import { Tooltip } from '../tooltip'; import './index.css'; -type ToolbarMenuProps = { +interface ToolbarMenuProps { button: ReactNode; label: string; -}; +} const ToolbarMenuRoot: FC< ToolbarMenuProps & { diff --git a/packages/graphiql-react/src/monaco-editor.ts b/packages/graphiql-react/src/monaco-editor.ts index 3207d3d2ec7..b2d52a854a0 100644 --- a/packages/graphiql-react/src/monaco-editor.ts +++ b/packages/graphiql-react/src/monaco-editor.ts @@ -1 +1,7 @@ -export * from 'monaco-graphql/esm/monaco-editor'; +/** + * Can't use `monaco-graphql/esm/monaco-editor` due error in esm.sh example: + * Uncaught TypeError: Cannot read properties of undefined (reading 'jsonDefaults') + */ + +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +export * from 'monaco-editor'; diff --git a/packages/graphiql-react/src/stores/execution.ts b/packages/graphiql-react/src/stores/execution.ts index 5ac41909afc..f1d1b0805f8 100644 --- a/packages/graphiql-react/src/stores/execution.ts +++ b/packages/graphiql-react/src/stores/execution.ts @@ -344,7 +344,7 @@ async function tryParseJsonObject( return parsed; } -type IncrementalResult = { +interface IncrementalResult { data?: Record | null; errors?: ReadonlyArray; extensions?: Record; @@ -360,7 +360,7 @@ type IncrementalResult = { }>; id?: string; subPath?: ReadonlyArray; -}; +} const pathsMap = new WeakMap< ExecutionResult, @@ -368,9 +368,9 @@ const pathsMap = new WeakMap< >(); /** - * @param executionResult The complete execution result object which will be + * @param executionResult - The complete execution result object which will be * mutated by merging the contents of the incremental result. - * @param incrementalResult The incremental result that will be merged into the + * @param incrementalResult - The incremental result that will be merged into the * complete execution result. */ function mergeIncrementalResult( diff --git a/packages/graphiql-react/src/stores/schema.ts b/packages/graphiql-react/src/stores/schema.ts index 7dd3fd1c322..e415a2e95fd 100644 --- a/packages/graphiql-react/src/stores/schema.ts +++ b/packages/graphiql-react/src/stores/schema.ts @@ -255,7 +255,7 @@ export interface SchemaProps extends IntrospectionArgs { schema?: GraphQLSchema | IntrospectionQuery | null; } -type IntrospectionArgs = { +interface IntrospectionArgs { /** * Can be used to set the equally named option for introspecting a GraphQL * server. @@ -275,7 +275,7 @@ type IntrospectionArgs = { * @see {@link https://github.com/graphql/graphql-js/blob/main/src/utilities/getIntrospectionQuery.ts|Utility for creating the introspection query} */ schemaDescription?: boolean; -}; +} function generateIntrospectionQuery({ inputValueDeprecation, diff --git a/packages/graphiql-react/src/stores/storage.ts b/packages/graphiql-react/src/stores/storage.ts index e7f920e611f..98ddcccf7a7 100644 --- a/packages/graphiql-react/src/stores/storage.ts +++ b/packages/graphiql-react/src/stores/storage.ts @@ -3,11 +3,11 @@ import { FC, ReactElement, ReactNode, useEffect } from 'react'; import { createStore } from 'zustand'; import { createBoundedUseStore } from '../utility'; -type StorageStoreType = { +interface StorageStoreType { storage: StorageAPI; -}; +} -type StorageStoreProps = { +interface StorageStoreProps { children: ReactNode; /** * Provide a custom storage API. @@ -16,7 +16,7 @@ type StorageStoreProps = { * for details on the required interface. */ storage?: Storage; -}; +} export const storageStore = createStore(() => ({ storage: null!, diff --git a/packages/graphiql-react/src/stores/theme.ts b/packages/graphiql-react/src/stores/theme.ts index 0780a807bf1..c4670ef7d83 100644 --- a/packages/graphiql-react/src/stores/theme.ts +++ b/packages/graphiql-react/src/stores/theme.ts @@ -16,15 +16,15 @@ type MonacoTheme = | (typeof EDITOR_THEME)[keyof typeof EDITOR_THEME] | ({} & string); -type ThemeStoreType = { +interface ThemeStoreType { theme: Theme; /** * Set a new theme */ setTheme: (newTheme: Theme) => void; -}; +} -type ThemeStoreProps = { +interface ThemeStoreProps { children: ReactNode; /** * @default null @@ -38,7 +38,7 @@ type ThemeStoreProps = { dark: MonacoTheme; light: MonacoTheme; }; -}; +} export const themeStore = createStore(set => ({ theme: null, diff --git a/packages/graphiql-react/src/types.test-d.ts b/packages/graphiql-react/src/types.test-d.ts index b21c4366e55..a05b470ac75 100644 --- a/packages/graphiql-react/src/types.test-d.ts +++ b/packages/graphiql-react/src/types.test-d.ts @@ -8,10 +8,10 @@ import { AllSlices } from './types'; describe('Types', () => { it('should not have conflicting types', () => { - type OverlapError = { + interface OverlapError { ERROR: 'Conflicting keys found'; CONFLICT_KEYS: K; - }; + } type MergeWithoutOverlap = keyof A & keyof B extends never ? A & B diff --git a/packages/graphiql-react/src/utility/create-editor.ts b/packages/graphiql-react/src/utility/create-editor.ts index fb168d5ddd4..59afd9222a7 100644 --- a/packages/graphiql-react/src/utility/create-editor.ts +++ b/packages/graphiql-react/src/utility/create-editor.ts @@ -21,15 +21,8 @@ export const onEditorContainerKeyDown: KeyboardEventHandler< export function getOrCreateModel({ uri, value }: { uri: Uri; value: string }) { const model = monacoEditor.getModel(uri); - if (model) { - // eslint-disable-next-line no-console - console.info('✅ Model', uri.path, 'is already created'); - return model; - } - // eslint-disable-next-line no-console - console.info('🚀 Model', uri.path, "isn't yet created, creating..."); const language = uri.path.split('.').at(-1)!; - return monacoEditor.createModel(value, language, uri); + return model ?? monacoEditor.createModel(value, language, uri); } const colors = { diff --git a/packages/graphiql-react/src/utility/resize.ts b/packages/graphiql-react/src/utility/resize.ts index 009890b4368..9052c8d6bf5 100644 --- a/packages/graphiql-react/src/utility/resize.ts +++ b/packages/graphiql-react/src/utility/resize.ts @@ -4,7 +4,7 @@ import { debounce } from './debounce'; type ResizableElement = 'first' | 'second'; -type UseDragResizeArgs = { +interface UseDragResizeArgs { /** * Set the default sizes for the two resizable halves by passing their ratio * (first divided by second). @@ -19,12 +19,14 @@ type UseDragResizeArgs = { * Choose one of the two halves that should initially be hidden. */ initiallyHidden?: ResizableElement; + /** * Invoked when the visibility of one of the halves changes. * @param hiddenElement The element that is now hidden after the change * (`null` if both are visible). */ onHiddenElementChange?(hiddenElement: ResizableElement | null): void; + /** * The minimum width in pixels for the first half. If it is resized to a * width smaller than this threshold, the half will be hidden. @@ -42,7 +44,7 @@ type UseDragResizeArgs = { * is available). */ storageKey?: string; -}; +} export function useDragResize({ defaultSizeRelation = 1, diff --git a/packages/graphiql-react/src/utility/tabs.ts b/packages/graphiql-react/src/utility/tabs.ts index e8cd9c82140..4b8d3f5812f 100644 --- a/packages/graphiql-react/src/utility/tabs.ts +++ b/packages/graphiql-react/src/utility/tabs.ts @@ -2,7 +2,7 @@ import { storageStore } from '../stores'; -export type TabDefinition = { +export interface TabDefinition { /** * The contents of the query editor of this tab. */ @@ -15,12 +15,12 @@ export type TabDefinition = { * The contents of the headers editor of this tab. */ headers?: string | null; -}; +} /** * This object describes the state of a single tab. */ -export type TabState = TabDefinition & { +export interface TabState extends TabDefinition { /** * A GUID value generated when the tab was created. */ @@ -44,7 +44,7 @@ export type TabState = TabDefinition & { * The contents of the response editor of this tab. */ response: string | null; -}; +} /** * This object describes the state of all tabs. diff --git a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts index b4669a3be41..7215ba9f602 100644 --- a/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts +++ b/packages/graphiql/cypress/e2e/incremental-delivery.cy.ts @@ -1,12 +1,4 @@ -import { version } from 'graphql'; - -let describeOrSkip: Mocha.SuiteFunction | Mocha.PendingSuiteFunction = describe; - -if (parseInt(version, 10) < 17) { - describeOrSkip = describe.skip; -} - -describeOrSkip('IncrementalDelivery support via fetcher', () => { +describe('IncrementalDelivery support via fetcher', () => { describe('When operation contains @stream', () => { const testStreamQuery = /* GraphQL */ ` query StreamQuery($delay: Int) { diff --git a/packages/graphiql/cypress/support/commands.ts b/packages/graphiql/cypress/support/commands.ts index 0a03a48808b..0ca3ce67c35 100644 --- a/packages/graphiql/cypress/support/commands.ts +++ b/packages/graphiql/cypress/support/commands.ts @@ -8,13 +8,13 @@ /// -type Op = { +interface Op { query: string; variables?: Record; variablesString?: string; headersString?: string; response?: Record; -}; +} declare namespace Cypress { type MockResult = | { data: any } diff --git a/packages/graphiql/package.json b/packages/graphiql/package.json index 9477b301c55..88aae0d771f 100644 --- a/packages/graphiql/package.json +++ b/packages/graphiql/package.json @@ -83,5 +83,9 @@ "vite": "^6.3.4", "vite-plugin-dts": "^4.5.3", "vitest-canvas-mock": "0.3.3" + }, + "browser": { + "//": "esm.sh polyfills `process`, we don't need it", + "process": false } } diff --git a/packages/graphiql/src/GraphiQL.spec.tsx b/packages/graphiql/src/GraphiQL.spec.tsx index 483a261cac5..db20e556bc2 100644 --- a/packages/graphiql/src/GraphiQL.spec.tsx +++ b/packages/graphiql/src/GraphiQL.spec.tsx @@ -49,8 +49,10 @@ describe('GraphiQL', () => { spy.mockRestore(); }); - it('should construct correctly with fetcher', async () => { - act(() => { + it('should construct correctly with fetcher', () => { + // `return` fix error: + // Warning: An update to GraphiQLInterface inside a test was not wrapped in act(...). + return act(() => { expect(() => render()).not.toThrow(); }); }); diff --git a/packages/graphiql/src/GraphiQL.tsx b/packages/graphiql/src/GraphiQL.tsx index 6b45c93fbf1..26a1d5f0f77 100644 --- a/packages/graphiql/src/GraphiQL.tsx +++ b/packages/graphiql/src/GraphiQL.tsx @@ -40,6 +40,7 @@ import { EditorProps, cn, VisuallyHidden, + KEY_MAP, } from '@graphiql/react'; import { HistoryStore, HISTORY_PLUGIN } from '@graphiql/plugin-history'; import { @@ -196,6 +197,13 @@ const TAB_CLASS_PREFIX = 'graphiql-session-tab-'; type ButtonHandler = MouseEventHandler; +const LABEL = { + refetchSchema: `Re-fetch GraphQL schema (${KEY_MAP.refetchSchema.key})`, + shortCutDialog: 'Open short keys dialog', + settingsDialogs: 'Open settings dialog', + newTab: 'New tab', +}; + const GraphiQLInterface: FC = ({ forcedTheme, isHeadersEditorEnabled = true, @@ -309,8 +317,8 @@ const GraphiQLInterface: FC = ({ 'settings' | 'short-keys' | null >(null); const [clearStorageStatus, setClearStorageStatus] = useState< - 'success' | 'error' | null - >(null); + 'success' | 'error' | undefined + >(); const { logo, toolbar, footer, children } = Children.toArray( $children, @@ -412,7 +420,7 @@ const GraphiQLInterface: FC = ({ function handleOpenSettingsDialog(isOpen: boolean) { if (!isOpen) { setShowDialog(null); - setClearStorageStatus(null); + setClearStorageStatus(undefined); } } @@ -453,12 +461,12 @@ const GraphiQLInterface: FC = ({ ); })} - + = ({ /> - + - + @@ -603,7 +611,7 @@ const GraphiQLInterface: FC = ({