Skip to content

Commit 3a0a755

Browse files
authored
dynamically import monaco-editor and monaco-graphql (#4071)
* upd * upd * upd next.js * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * upd * make monaco nullable * all right * all right * all right * upd * changeset * upd * upd yarn * comment * Update packages/graphiql-react/src/utility/monaco-ssr.ts * upd * upd * upd * upd changeset * fix build * prettier * update README.md * fix typecheck * skip temp * try * remove “Module '@emotion/is-prop-valid' not found” warning emitted by Framer-Motion when it’s loaded from an ESM‑CDN bundle * upd * upd * upd * try this * use lite * try * try `monaco-graphql/esm/monaco-editor` * try editor.api.js * try import in monaco * and now * upd * upd yarn * try * cleanup * cleanup * rm * rm editor.api.js import * upd * specify ext for monaco-graphql, because it fails on esm.sh monaco-graphql/esm/lite.js * try * try * cleanup * cleanup * cleanup * fix vitest * prettier * increase timeout * increase more timeout * more timeout * increase default query timeout for 6s * Update .changeset/chilled-peaches-wonder.md * for 7s * for 8s * Update .changeset/chilled-peaches-wonder.md
1 parent d0a39f1 commit 3a0a755

34 files changed

+424
-340
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@graphiql/react': minor
3+
'monaco-graphql': patch
4+
'graphql-language-service': minor
5+
'graphiql': minor
6+
---
7+
8+
feat(graphql-language-service): export `getContextAtPosition`
9+
feat(graphiql): dynamically import `monaco-editor` and `monaco-graphql`
10+
11+
When using GraphiQL in Next.js app, you no longer need to use `next/dynamic`:
12+
13+
```diff
14+
-import dynamic from 'next/dynamic'
15+
-const GraphiQL = dynamic(() => import('graphiql').then(mod => mod.GraphiQL), {
16+
- ssr: false
17+
-})
18+
+import { GraphiQL } from 'graphiql'
19+
```
20+
21+
When using GraphiQL with [React Router’s SSR mode](https://reactrouter.com/api/framework-conventions/react-router.config.ts#ssr),
22+
you no longer need to add `.client` to the file name to mark the GraphiQL component as a [client module](https://reactrouter.com/api/framework-conventions/client-modules).

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const RESTRICTED_IMPORTS = [
1818
name: 'monaco-editor',
1919
message:
2020
'`monaco-editor` imports all languages; use `monaco-graphql/esm/monaco-editor` instead to import only `json` and `graphql` languages',
21+
allowTypeImports: true,
2122
},
2223
];
2324

examples/graphiql-cdn/index.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,11 @@
3333
rel="stylesheet"
3434
href="https://esm.sh/@graphiql/plugin-explorer/dist/style.css"
3535
/>
36-
<!-- Note: the ?standalone flag bundles the module along with all of its `dependencies`, excluding `peerDependencies`, into a single JavaScript file. -->
37-
<!-- `@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 -->
36+
<!--
37+
* Note:
38+
* The ?standalone flag bundles the module along with all of its `dependencies`, excluding `peerDependencies`, into a single JavaScript file.
39+
* `@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
40+
-->
3841
<script type="importmap">
3942
{
4043
"imports": {
@@ -55,10 +58,8 @@
5558
}
5659
</script>
5760
<script type="module">
58-
// Import React and ReactDOM
5961
import React from 'react';
6062
import ReactDOM from 'react-dom/client';
61-
// Import GraphiQL and the Explorer plugin
6263
import { GraphiQL, HISTORY_PLUGIN } from 'graphiql';
6364
import { createGraphiQLFetcher } from '@graphiql/toolkit';
6465
import { explorerPlugin } from '@graphiql/plugin-explorer';
Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
'use client';
22

33
import type { FC } from 'react';
4-
import dynamic from 'next/dynamic';
4+
import { GraphiQL } from 'graphiql';
55
import 'graphiql/setup-workers/webpack';
66
import 'graphiql/style.css';
77

8-
// dynamically import our GraphiQL component
9-
const GraphiQL = dynamic(() => import('graphiql').then(mod => mod.GraphiQL), {
10-
ssr: false,
11-
});
12-
138
async function fetcher(graphQLParams: Record<string, unknown>) {
149
const response = await fetch('https://graphql.earthdata.nasa.gov/api', {
1510
method: 'POST',
@@ -22,8 +17,6 @@ async function fetcher(graphQLParams: Record<string, unknown>) {
2217
return response.json();
2318
}
2419

25-
const Page: FC = () => {
20+
export const GraphiQLPage: FC = () => {
2621
return <GraphiQL fetcher={fetcher} />;
2722
};
28-
29-
export default Page;

examples/graphiql-nextjs/src/app/layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import type { Metadata } from 'next';
33
import './globals.css';
44

55
export const metadata: Metadata = {
6-
title: 'GraphiQL Next.js Example',
6+
description: 'Example of using GraphiQL with the Next.js App Router',
7+
// Empty object adds open graph and twitter meta-tags
8+
openGraph: {},
79
};
810

911
const RootLayout: FC<Readonly<{ children: ReactNode }>> = ({ children }) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Metadata } from 'next';
2+
3+
export { GraphiQLPage as default } from './graphiql';
4+
5+
export const metadata: Metadata = {
6+
title: 'GraphiQL Next.js Example',
7+
};

packages/graphiql-react/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ React hooks.
9191

9292
- **`useStorage`**: Provides a storage API that can be used to persist state in the browser (by default using `localStorage`).
9393
- **`useTheme`**: Manages the current theme and provides a method to update it.
94+
- **`useMonaco`**: Access `monaco-editor` exports and the `monaco-graphql` instance. Designed for safe use in SSR environments.
9495
- **`useGraphiQL`**: Access the current state.
9596
- **`useGraphiQLActions`**: Trigger actions that mutate the state. This hook **never** rerenders.
9697

packages/graphiql-react/src/components/operation-editor.tsx

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { getSelectedOperationName } from '@graphiql/toolkit';
22
import type { DocumentNode } from 'graphql';
3-
import { getOperationFacts } from 'graphql-language-service';
3+
import {
4+
getOperationFacts,
5+
getContextAtPosition,
6+
} from 'graphql-language-service';
47
import { FC, useEffect, useRef } from 'react';
5-
import { useStorage } from '../stores';
8+
import { useMonaco, useStorage } from '../stores';
69
import { useGraphiQL, useGraphiQLActions } from './provider';
710
import {
811
debounce,
@@ -12,24 +15,18 @@ import {
1215
pick,
1316
cleanupDisposables,
1417
cn,
18+
Uri,
19+
Range,
1520
} from '../utility';
1621
import type { MonacoEditor, EditorProps, SchemaReference } from '../types';
1722
import {
1823
KEY_BINDINGS,
1924
URI_NAME,
2025
STORAGE_KEY,
2126
MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS,
22-
MONACO_GRAPHQL_API,
2327
} from '../constants';
24-
import {
25-
type editor as monacoEditor,
26-
languages,
27-
Range,
28-
Uri,
29-
} from '../monaco-editor';
30-
import * as monaco from '../monaco-editor';
31-
import { getContextAtPosition } from 'graphql-language-service/esm/parser';
32-
import { toGraphQLPosition } from 'monaco-graphql/esm/utils';
28+
import type * as monaco from 'monaco-editor';
29+
import { toGraphQLPosition } from 'monaco-graphql/esm/utils.js';
3330

3431
interface OperationEditorProps extends EditorProps {
3532
/**
@@ -179,7 +176,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
179176
: null;
180177
}
181178

182-
const runAtCursorRef = useRef<monacoEditor.IActionDescriptor['run']>(null!);
179+
const runAtCursorRef = useRef<monaco.editor.IActionDescriptor['run']>(null!);
183180

184181
useEffect(() => {
185182
runAtCursorRef.current = editor => {
@@ -209,7 +206,12 @@ export const OperationEditor: FC<OperationEditorProps> = ({
209206
};
210207
}, [operationName, operations, run, setOperationName]);
211208

209+
const { monacoGraphQL, monaco } = useMonaco();
210+
212211
useEffect(() => {
212+
if (!monaco || !monacoGraphQL) {
213+
return;
214+
}
213215
const operationUri = Uri.file(`${uriInstanceId}${URI_NAME.operation}`);
214216
const variablesUri = Uri.file(`${uriInstanceId}${URI_NAME.variables}`);
215217
/**
@@ -218,10 +220,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
218220
*/
219221
const { validateVariablesJSON } = MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS;
220222
validateVariablesJSON![operationUri.toString()] = [variablesUri.toString()];
221-
MONACO_GRAPHQL_API.setDiagnosticSettings(
222-
MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS,
223-
);
224-
globalThis.__MONACO = monaco;
223+
monacoGraphQL.setDiagnosticSettings(MONACO_GRAPHQL_DIAGNOSTIC_SETTINGS);
225224
const model = getOrCreateModel({
226225
uri: operationUri.path.replace('/', ''),
227226
value: initialQuery,
@@ -266,16 +265,16 @@ export const OperationEditor: FC<OperationEditorProps> = ({
266265
model,
267266
];
268267
return cleanupDisposables(disposables);
269-
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount
268+
}, [monaco, monacoGraphQL]); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount
270269

271270
useEffect(() => {
272-
if (!schema) {
271+
if (!schema || !monaco || !monacoGraphQL) {
273272
return;
274273
}
275-
MONACO_GRAPHQL_API.setSchemaConfig([
274+
monacoGraphQL.setSchemaConfig([
276275
{ uri: `${uriInstanceId}${URI_NAME.schema}`, schema },
277276
]);
278-
MONACO_GRAPHQL_API.setExternalFragmentDefinitions([
277+
monacoGraphQL.setExternalFragmentDefinitions([
279278
...externalFragments.values(),
280279
]);
281280
if (!referencePlugin) {
@@ -285,7 +284,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
285284
let currentSchemaReference: SchemaReference | null = null;
286285

287286
const disposables = [
288-
languages.registerDefinitionProvider('graphql', {
287+
monaco.languages.registerDefinitionProvider('graphql', {
289288
provideDefinition(model, position, _token) {
290289
const graphQLPosition = toGraphQLPosition(position);
291290
const context = getContextAtPosition(
@@ -321,7 +320,7 @@ export const OperationEditor: FC<OperationEditorProps> = ({
321320
currentSchemaReference = null;
322321
},
323322
}),
324-
languages.registerReferenceProvider('graphql', {
323+
monaco.languages.registerReferenceProvider('graphql', {
325324
provideReferences(model, { lineNumber, column }, _context, _token) {
326325
if (!currentSchemaReference) {
327326
return;
@@ -343,6 +342,8 @@ export const OperationEditor: FC<OperationEditorProps> = ({
343342
setVisiblePlugin,
344343
externalFragments,
345344
uriInstanceId,
345+
monacoGraphQL,
346+
monaco,
346347
]);
347348

348349
return (

packages/graphiql-react/src/components/provider.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
ExecutionProps,
1313
PluginProps,
1414
SchemaProps,
15+
monacoStore,
1516
} from '../stores';
1617
import { StorageStore, useStorage } from '../stores/storage';
1718
import { ThemeStore } from '../stores/theme';
@@ -27,11 +28,9 @@ import {
2728
import {
2829
DEFAULT_PRETTIFY_QUERY,
2930
DEFAULT_QUERY,
30-
JSON_DIAGNOSTIC_OPTIONS,
3131
STORAGE_KEY,
3232
} from '../constants';
3333
import { getDefaultTabState } from '../utility/tabs';
34-
import { languages } from '../monaco-editor';
3534

3635
interface InnerGraphiQLProviderProps
3736
extends EditorProps,
@@ -117,6 +116,12 @@ useEffect(() => {
117116
}, [response])`,
118117
);
119118
}
119+
120+
useEffect(() => {
121+
const { actions } = monacoStore.getState();
122+
void actions.initialize();
123+
}, []);
124+
120125
return (
121126
<StorageStore storage={storage}>
122127
<ThemeStore defaultTheme={defaultTheme} editorTheme={editorTheme}>
@@ -330,16 +335,6 @@ const InnerGraphiQLProvider: FC<InnerGraphiQLProviderProps> = ({
330335
};
331336
}, []);
332337

333-
useEffect(() => {
334-
/**
335-
* Set diagnostics options for JSON
336-
*
337-
* Setting it on mount fix Uncaught TypeError: Cannot read properties of undefined (reading 'jsonDefaults')
338-
* @see https://github.com/graphql/graphiql/pull/4042#issuecomment-3017167375
339-
*/
340-
languages.json.jsonDefaults.setDiagnosticsOptions(JSON_DIAGNOSTIC_OPTIONS);
341-
}, []);
342-
343338
return (
344339
<GraphiQLContext.Provider value={storeRef}>
345340
{children}

packages/graphiql-react/src/components/request-headers-editor.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
cleanupDisposables,
1212
cn,
1313
} from '../utility';
14+
import { useMonaco } from '../stores';
1415

1516
interface RequestHeadersEditorProps extends EditorProps {
1617
/**
@@ -29,12 +30,16 @@ export const RequestHeadersEditor: FC<RequestHeadersEditorProps> = ({
2930
pick('initialHeaders', 'shouldPersistHeaders', 'uriInstanceId'),
3031
);
3132
const ref = useRef<HTMLDivElement>(null!);
33+
const monaco = useMonaco(state => state.monaco);
3234
useChangeHandler(
3335
onEdit,
3436
shouldPersistHeaders ? STORAGE_KEY.headers : null,
3537
'headers',
3638
);
3739
useEffect(() => {
40+
if (!monaco) {
41+
return;
42+
}
3843
const model = getOrCreateModel({
3944
uri: `${uriInstanceId}${URI_NAME.requestHeaders}`,
4045
value: initialHeaders,
@@ -49,7 +54,7 @@ export const RequestHeadersEditor: FC<RequestHeadersEditorProps> = ({
4954
model,
5055
];
5156
return cleanupDisposables(disposables);
52-
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount
57+
}, [monaco]); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount
5358

5459
return (
5560
<div

0 commit comments

Comments
 (0)