Skip to content

Commit 31ded5e

Browse files
authored
fix: monaco-graphql exports, validate on load (#3384)
- validates both graphql and json variables on load - reduces bundle size further, allowing users to specify which features they want, instead of importing all of them - adds a typescript mode to the vite/next.js demos to ensure our implementation doesn't break `typescript` language mode & worker - fix variables json validation by pointing to a meta schema - add `exports` aliases that are backwards compatible, but allow `monaco-graphql/initializeMode` and `monaco-graphql/graphql.worker` to be imported directly - add a `monaco-graphql/lite` import for minimal features - import the json language mode only if variables json validation is used
1 parent 46aa56e commit 31ded5e

File tree

21 files changed

+254
-87
lines changed

21 files changed

+254
-87
lines changed

.changeset/fluffy-pens-lick.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'monaco-graphql': patch
3+
---
4+
5+
import only `editor.api` & basic features, add `monaco-graphql/lite`
6+
7+
- switch from exporting `edcore.js` to `editor.api.js` as recommended, and minimal features to get the editor working
8+
- `edcore` imports `editor.all` which contains many monaco-editor features we don't use
9+
- dynamic import of `json` language mode only if the user supplies configuration for json validation
10+
- update monaco examples to show minimal `typescript` implementation alongside `graphql`
11+
- add new simplified `exports` with backwards compatibility:
12+
- `monaco-graphql/initializeMode`
13+
- `monaco-graphql/graphql.worker`
14+
- `monaco-graphql/monaco-editor`
15+
- introduce `monaco-graphql/lite` for users who want the most minimum version possible, and to only import the features they need

.github/workflows/pr.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ jobs:
1111
steps:
1212
- name: Checkout Code
1313
uses: actions/checkout@v3
14-
with:
15-
fetch-depth: 0
1614
- uses: actions/setup-node@v3
1715
with:
1816
node-version: 16
@@ -38,8 +36,6 @@ jobs:
3836

3937
steps:
4038
- uses: actions/checkout@v3
41-
with:
42-
fetch-depth: 0
4339
- uses: actions/setup-node@v3
4440
with:
4541
node-version: 16

custom-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ matchingbracket
130130
middlewares
131131
modulemap
132132
newhope
133+
nextjs
133134
nocheck
134135
nocursor
135136
nonmatchingbracket

examples/monaco-graphql-nextjs/next.config.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ const nextConfig = {
1919
// where vscode contains a version of `marked` with modules pre-transpiled, which seems to break the build.
2020
//
2121
// (the error mentions that exports.Lexer is a const that can't be re-declared)
22-
'../common/marked/marked.js': 'marked',
22+
// this import has moved a bit, so let's make it absolute from the module path
23+
'monaco-editor/esm/vs/common/marked/marked.js': 'marked',
2324
};
2425
if (!options.isServer) {
2526
config.plugins.push(
@@ -40,6 +41,16 @@ const nextConfig = {
4041
entry: 'monaco-graphql/esm/graphql.worker.js',
4142
},
4243
},
44+
// TOD: webpack monaco editor plugin breaks on languages: ['typescript']
45+
// so this was necessary
46+
// see: https://github.com/microsoft/monaco-editor/issues/2738
47+
{
48+
label: 'typescript',
49+
worker: {
50+
id: 'typescript',
51+
entry: 'monaco-editor/esm/vs/language/typescript/ts.worker.js',
52+
},
53+
},
4354
],
4455
}),
4556
);

examples/monaco-graphql-nextjs/src/constants.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { editor, Uri } from 'monaco-graphql/esm/monaco-editor';
22
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
3+
import { parse, print } from 'graphql';
34

4-
type ModelType = 'operations' | 'variables' | 'response';
5+
type ModelType = 'operations' | 'variables' | 'response' | 'typescript';
56

67
export const GRAPHQL_URL = 'https://countries.trevorblades.com';
78

@@ -18,35 +19,59 @@ export const STORAGE_KEY = {
1819
variables: 'variables',
1920
};
2021

21-
export const DEFAULT_VALUE: Record<ModelType, string> = {
22-
operations:
23-
localStorage.getItem(STORAGE_KEY.operations) ??
24-
`# CMD/CTRL + Return/Enter will execute the operation,
22+
// allow a typo to test validation on schema load
23+
/* cSpell:disable */
24+
const operations =
25+
localStorage.getItem(STORAGE_KEY.operations) ??
26+
`# CMD/CTRL + Return/Enter will execute the operation,
2527
# same in the variables editor below
2628
# also available via context menu & F1 command palette
2729
28-
query($code: ID!) {
30+
query Example($code: ID!, $filter: LanguageFilterInput!) {
2931
country(code: $code) {
3032
awsRegion
3133
native
3234
phone
35+
emoj
3336
}
34-
}`,
37+
languages(filter: $filter) {
38+
name
39+
}
40+
}`;
41+
/* cSpell:enable */
42+
43+
let prettyOp = '';
44+
const makeOpTemplate = (op: string) => {
45+
try {
46+
prettyOp = print(parse(op));
47+
return `
48+
const graphql = (arg: TemplateStringsArray): string => arg[0]
49+
50+
const op = graphql\`\n${prettyOp}\n\``;
51+
} catch {
52+
return prettyOp;
53+
}
54+
};
55+
56+
export const DEFAULT_VALUE: Record<ModelType, string> = {
57+
operations,
3558
variables:
3659
localStorage.getItem(STORAGE_KEY.variables) ??
3760
`{
3861
"code": "UA"
3962
}`,
4063
response: '',
64+
typescript: makeOpTemplate(operations),
4165
};
4266

4367
export const FILE_SYSTEM_PATH: Record<
4468
ModelType,
45-
`${string}.${'graphql' | 'json'}`
69+
`${string}.${'graphql' | 'json' | 'ts'}`
4670
> = {
4771
operations: 'operations.graphql',
4872
variables: 'variables.json',
4973
response: 'response.json',
74+
typescript: 'typescript.ts',
5075
};
5176

5277
export const MONACO_GRAPHQL_API = initializeMode({
@@ -70,14 +95,22 @@ export const MODEL: Record<ModelType, editor.ITextModel> = {
7095
operations: getOrCreateModel('operations'),
7196
variables: getOrCreateModel('variables'),
7297
response: getOrCreateModel('response'),
98+
typescript: getOrCreateModel('typescript'),
7399
};
74100

75-
function getOrCreateModel(
76-
type: 'operations' | 'variables' | 'response',
77-
): editor.ITextModel {
101+
MODEL.operations.onDidChangeContent(() => {
102+
const value = MODEL.operations.getValue();
103+
MODEL.typescript.setValue(makeOpTemplate(value));
104+
});
105+
106+
function getOrCreateModel(type: ModelType): editor.ITextModel {
78107
const uri = Uri.file(FILE_SYSTEM_PATH[type]);
79108
const defaultValue = DEFAULT_VALUE[type];
80-
const language = uri.path.split('.').pop();
109+
let language = uri.path.split('.').pop();
110+
console.log({ language });
111+
if (language === 'ts') {
112+
language = 'typescript';
113+
}
81114
return (
82115
editor.getModel(uri) ?? editor.createModel(defaultValue, language, uri)
83116
);

examples/monaco-graphql-nextjs/src/editor.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ import {
66
KeyCode,
77
languages,
88
} from 'monaco-graphql/esm/monaco-editor';
9+
10+
// to get typescript mode working
11+
import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution';
12+
import 'monaco-editor/esm/vs/editor/contrib/peekView/browser/peekView';
13+
import 'monaco-editor/esm/vs/editor/contrib/parameterHints/browser/parameterHints';
14+
import 'monaco-editor/esm/vs/language/typescript/monaco.contribution';
15+
916
import { createGraphiQLFetcher } from '@graphiql/toolkit';
1017
import * as JSONC from 'jsonc-parser';
1118
import {
@@ -78,12 +85,15 @@ export default function Editor(): ReactElement {
7885
const operationsRef = useRef<HTMLDivElement>(null);
7986
const variablesRef = useRef<HTMLDivElement>(null);
8087
const responseRef = useRef<HTMLDivElement>(null);
88+
const typescriptRef = useRef<HTMLDivElement>(null);
8189
const [operationsEditor, setOperationsEditor] =
8290
useState<editor.IStandaloneCodeEditor>();
8391
const [variablesEditor, setVariablesEditor] =
8492
useState<editor.IStandaloneCodeEditor>();
8593
const [responseEditor, setResponseEditor] =
8694
useState<editor.IStandaloneCodeEditor>();
95+
const [typescriptEditor, setTypescriptEditor] =
96+
useState<editor.IStandaloneCodeEditor>();
8797
const [schema, setSchema] = useState<IntrospectionQuery>();
8898
const [loading, setLoading] = useState(false);
8999
/**
@@ -132,6 +142,18 @@ export default function Editor(): ReactElement {
132142
}),
133143
);
134144
}
145+
if (!typescriptEditor) {
146+
setTypescriptEditor(
147+
editor.create(typescriptRef.current!, {
148+
model: MODEL.typescript,
149+
...DEFAULT_EDITOR_OPTIONS,
150+
smoothScrolling: true,
151+
readOnly: false,
152+
'semanticHighlighting.enabled': true,
153+
language: 'typescript',
154+
}),
155+
);
156+
}
135157
}, []); // eslint-disable-line react-hooks/exhaustive-deps -- only run once on mount
136158
/**
137159
* Handle the initial schema load
@@ -156,7 +178,10 @@ export default function Editor(): ReactElement {
156178
<div ref={operationsRef} className="left-editor" />
157179
<div ref={variablesRef} className="left-editor" />
158180
</div>
159-
<div ref={responseRef} className="pane" />
181+
<div className="pane">
182+
<div ref={responseRef} className="left-editor" />
183+
<div ref={typescriptRef} className="left-editor" />
184+
</div>
160185
</>
161186
);
162187
}

examples/monaco-graphql-react-vite/vite.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export default defineConfig({
1010
react(),
1111
monacoEditorPlugin({
1212
publicPath: 'workers',
13-
languageWorkers: ['json', 'editorWorkerService'],
13+
// note that this only loads the worker, not the full main process language support
14+
languageWorkers: ['json', 'typescript', 'editorWorkerService'],
1415
customWorkers: [
1516
{
1617
label: 'graphql',

packages/graphql-language-service/src/utils/getVariablesJSONSchema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ export function getVariablesJSONSchema(
351351
options?: JSONSchemaOptions,
352352
): JSONSchema6 {
353353
const jsonSchema: PropertiedJSON6 = {
354+
// this gets monaco-json validation working again
355+
// otherwise it shows an error for newer schema draft versions
356+
// variables and graphql types are simple and compatible with all versions of json schema
357+
// since draft 4. package.json and many other schemas still use draft 4
354358
$schema: 'http://json-schema.org/draft-04/schema',
355359
type: 'object',
356360
properties: {},

packages/monaco-graphql/README.md

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ yarn add monaco-graphql
5353
```ts
5454
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
5555

56-
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
56+
import { initializeMode } from 'monaco-graphql/initializeMode'; // `monaco-graphql/esm/initializeMode` still works
5757

5858
// you can also configure these using the webpack or vite plugins for `monaco-editor`
5959
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
@@ -93,7 +93,8 @@ monaco.editor.create(document.getElementById('someElementId'), {
9393

9494
## Lazy Example
9595

96-
The existing API works as before in terms of instantiating the schema
96+
The existing API works as before in terms of instantiating the schema.
97+
To avoid manually calling getWorker(), you can use the monaco editor plugins for webpack or vite (see examples, and below)
9798

9899
```ts
99100
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
@@ -151,7 +152,7 @@ any given set of operations
151152
```ts
152153
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
153154

154-
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
155+
import { initializeMode } from 'monaco-graphql/initializeMode';
155156

156157
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
157158

@@ -235,15 +236,41 @@ MonacoGraphQL.setCompletionSettings({
235236
});
236237
```
237238

238-
You can also experiment with the built-in I think `jsonc`? (MSFT json5-ish
239-
syntax, for `tsconfig.json` etc.) and the 3rd party `monaco-yaml` language modes
239+
You can also experiment with the built-in `jsonc`? (JSON
240+
syntax that allows comments and trailing commas, for `tsconfig.json`, etc.) and the 3rd party `monaco-yaml` language modes
240241
for completion of other types of variable input. you can also experiment with
241242
editor methods to parse detected input into different formats, etc (`yaml`
242243
pastes as `json`, etc.)
243244

244245
You could of course prefer to generate a `jsonschema` form for variables input
245246
using a framework of your choice, instead of an editor. Enjoy!
246247

248+
## `monaco-graphql/lite`
249+
250+
You can also import a "lite" version, and manually enable only the monaco features you want!
251+
252+
Warning: by default, completion and other features will not work, only highlighting and validation.
253+
254+
```ts
255+
import { initializeMode } from 'monaco-graphql/lite';
256+
257+
// enable completion
258+
import 'monaco-editor/esm/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution';
259+
260+
const api = initializeMode({
261+
schemas: [
262+
{
263+
// anything that monaco.URI.from() is compatible with
264+
uri: 'schema.graphql',
265+
// match the monaco file uris for this schema.
266+
// accepts specific filenames and anything `picomatch` supports.
267+
fileMatch: ['operation.graphql'],
268+
schema: myGraphqlSchema as GraphQLSchema,
269+
},
270+
],
271+
});
272+
```
273+
247274
## `MonacoGraphQLAPI` ([typedoc](https://graphiql-test.netlify.app/typedoc/classes/monaco_graphql.monacoMonacoGraphQLAPI.html))
248275

249276
If you call any of these API methods to modify the language service
@@ -271,7 +298,7 @@ const { api } = languages.graphql;
271298
Otherwise, you can, like in the sync demo above:
272299

273300
```ts
274-
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
301+
import { initializeMode } from 'monaco-graphql/initializeMode';
275302

276303
const api = initializeMode(config);
277304
```
@@ -299,7 +326,7 @@ monaco.languages.graphql.api.setSchemaConfig([
299326
or you can load the language features only when you have your schema
300327

301328
```ts
302-
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
329+
import { initializeMode } from 'monaco-graphql/initializeMode';
303330

304331
const schemas = [
305332
{
@@ -402,12 +429,20 @@ you'll can refer to the webpack configuration in the
402429
how it works with webpack and the official `monaco-editor-webpack-plugin`. there
403430
is probably an easier way to configure webpack `worker-loader` for this.
404431

432+
**Notes:**
433+
434+
- for additional features, please specify them in `features` for the webpack plugin, or import them directly
435+
- if you are trying to add `typescript` as a language, there is an outstanding [bug with the webpack plugin](https://github.com/microsoft/monaco-editor/issues/2738), see our [next.js example](../../examples/monaco-graphql-nextjs/next.config.js) for the workaround. do not specify `languages: ['typescript']` or `javascript`
436+
405437
### Vite
406438

407439
You can configure vite to load `monaco-editor` json mode and even the language
408440
editor worker using
409441
[the example for our mode](https://github.com/vdesjs/vite-plugin-monaco-editor#options)
410442

443+
Be sure to import additional editor features and language modes manually, as the vite plugin only allows you to specify `languageWorkers`.
444+
See the vite example to see how to add typescript support
445+
411446
## Web Frameworks
412447

413448
the plain javascript

0 commit comments

Comments
 (0)