$ npm install monaco-graphql
Changelog | API Docs | Discord Channel
GraphQL language plugin for the Monaco Editor. You can use it to build vscode/codespaces-like web or desktop IDEs using whatever frontend javascript libraries or frameworks you want, or none!
NOTE: This is in pre-release state as we build towards GraphiQL 2.0.x.
codemirror-graphql
has more features (such as JSON variables validation) and is more stable.
It provides the following features while editing GraphQL files:
fileMatch
expressionsmonaco-editor
basic languages supportFor now, we use language
id of graphql
until we can ensure we can dovetail nicely with the official graphql
language ID.
To use with webpack, here is an example to get you started:
yarn add monaco-graphql
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
// you can also configure these using the webpack or vite plugins for `monaco-editor`
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
// instantiates the worker & language features with the schema!
const MonacoGraphQLAPI = initializeMode({
schemas: [
{
schema: myGraphqlSchema as GraphQLSchema,
// anything that monaco.URI.from() is compatible with
uri: 'https://myschema.com',
uri: '/myschema.graphql',
// match the monaco file uris for this schema.
// accepts specific uris and anything `picomatch` supports.
// (everything except bracket regular expressions)
fileMatch: ['**/*.graphql'],
// note: not sure if ^ works if the graphql model is using urls for uris?
},
],
});
window.MonacoEnvironment = {
getWorker(_workerId: string, label: string) {
if (label === 'graphql') {
return new GraphQLWorker();
}
// if you are using vite or webpack plugin, it will be found here
return new Worker('editor.worker.js');
},
};
monaco.editor.create(document.getElementById('someElementId'), {
value: 'query { }',
language: 'graphql',
formatOnPaste: true,
});
The existing API works as before in terms of instantiating the schema
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
// enables our language worker right away, despite no schema
import 'monaco-graphql';
// you can also configure these using the webpack or vite plugins for `monaco-editor`
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
// lazily invoke the api config methods whenever we want!
monaco.languages.graphql.setSchemaConfig([
{
schema: myGraphqlSchema as GraphQLSchema,
// anything that monaco.URI.from() is compatible with
uri: 'https://myschema.com',
uri: '/myschema.graphql',
// match the monaco file uris for this schema.
// accepts specific uris and anything `picomatch` supports.
// (everything except bracket regular expressions)
fileMatch: ['**/*.graphql'],
// note: not sure if ^ works if the graphql model is using urls for uris?
},
]);
window.MonacoEnvironment = {
getWorker(_workerId: string, label: string) {
if (label === 'graphql') {
return new GraphQLWorker();
}
return new Worker('editor.worker.js');
},
};
monaco.editor.create(document.getElementById('someElementId'), {
value: 'query { }',
language: 'graphql',
formatOnPaste: true,
});
This will cover the basics, making an HTTP POST with the default introspectionQuery()
operation. To customize the entire fetcher, see advanced customization below. For more customization options, see the Monaco Editor API Docs
In monaco-graphql@0.5.0
we introduced a method getVariablesJSONSchema
that allows you to retrieve a JSONSchema
description for the declared variables for any given set of operations
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
window.MonacoEnvironment = {
getWorker(_workerId: string, label: string) {
if (label === 'graphql') {
return new GraphQLWorker();
}
return new Worker('editor.worker.js');
},
};
// the language service will be instantiated once the schema is available
const MonacoGraphQLAPI = initializeMode({
schemas: [
{
// anything that monaco.URI.from() is compatible with
uri: 'https://myschema.com',
// match the monaco file uris for this schema.
// accepts specific filenames and anything `picomatch` supports.
fileMatch: ['**/*.graphql'],
schema: myGraphqlSchema as GraphQLSchema,
},
],
});
const operationModel = monaco.editor.createModel(
'query {}',
'graphql',
'/operation.graphql',
);
const operationEditor = monaco.editor.create(
document.getElementById('someElementId'),
{
model: operationModel,
language: 'graphql',
formatOnPaste: true,
},
);
const variablesSchemaUri = monaco.editor.URI.file('/variables-schema.json');
const variablesModel = monaco.editor.createModel(
'{}',
'json',
'/variables.json',
);
const variablesEditor = monaco.editor.create(
document.getElementById('someElementId'),
{
model: variablesModel,
language: 'graphql',
formatOnPaste: true,
},
);
// high-level method for configuring json variables validation
MonacoGraphQLAPI.setDiagnosticSettings({
validateVariablesJson: {
// Urls, uris, anything that monaco.URI.from() is compatible with.
// Match operation model to variables editor,
// and the language service will automatically listen for changes,
// and compute the json schema using the GraphQLWorker.
// This is in the main process is applied to the global monaco json settings
// for validation, completion and more using monaco-json's built-in JSON Schema support.
[operationModel.uri.toString()]: [variablesModel.uri.toString()],
},
jsonDiagnosticSettings: {
allowComments: true, // allow json, parse with a jsonc parser to make requests
},
});
// TODO: document manual alternative approach
You can also experiment with the built-in I think jsonc
? (MSFT json5-ish syntax, for tsconfig.json
etc.) and the 3rd party monaco-yaml
language modes for completion of other types of variable input. you can also experiment with editor methods to parse detected input into different formats, etc (yaml
pastes as json
, etc.)
You could of course prefer to generate a jsonschema
form for variables input using a framework of your choice, instead of an editor. Enjoy!
MonacoGraphQLAPI
(typedoc)If you call any of these API methods to modify the language service configuration at any point at runtime, the webworker will reload relevant language features.
If you import 'monaco-graphql'
synchronously, you can access the api via monaco.languages.graphql.api
.
import 'monaco-graphql';
// now the api will be available on the `monaco.languages` global
monaco.languages.graphql.api;
import 'monaco-graphql';
// also this
import { languages } from 'monaco-editor';
// now the api will be available on the `monaco.languages` global
languages.graphql.api;
Otherwise, you can, like in the sync demo above:
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
const api = initializeMode(config);
monaco.languages.graphql.api.setSchemaConfig([SchemaConfig])
same as the above, except it overwrites the entire schema config.
you can provide multiple, and use fileMatch
to map to various uri "directory" globs or specific files.
uri
can be an url or file path, anything parsable
// you can load it lazily
import 'monaco-graphql';
monaco.languages.graphql.api.setSchemaConfig([
{
schema: GraphQLSchema,
fileMatch: ['**/*.graphql'],
uri: 'myschema.graphql',
},
]);
or you can load the language features only when you have your schema
import { initializeMode } from 'monaco-graphql/esm/initializeMode';
const schemas = [
{
schema: GraphQLSchema,
fileMatch: ['operations/*.graphql'],
uri: 'myschema.graphql',
},
];
const api = initializeMode({ schemas });
// add another schema. this will cause language workers and features to reset
api.setSchemaConfig([
...schemas,
{
introspectionJSON: myIntrospectionJSON,
fileMatch: ['specific/monaco/uri.graphql'],
uri: 'another-schema.graphql',
},
]);
or if you want, replace the entire configuration with a single schema. this will cause the worker to be entirely re-created and language services reset
api.setSchemaConfig([
{
introspectionJSON: myIntrospectionJSON,
fileMatch: ['**/*.graphql'],
uri: 'myschema.graphql',
},
]);
monaco.languages.graphql.api.setModeConfiguration()
This is where you can toggle monaco language features. all are enabled by default.
monaco.languages.graphql.api.setModeConfiguration({
documentFormattingEdits: true,
completionItems: true,
hovers: true,
documentSymbols: true,
diagnostics: true,
});
monaco.languages.graphql.api.setFormattingOptions()
this accepts an object { prettierConfig: prettier.Options }
, which accepts any prettier option.
it will not re-load the schema or language features, however the new prettier options will take effect.
this method overwrites the previous configuration, and will only accept static values that can be passed between the main/worker process boundary.
monaco.languages.graphql.api.setFormattingOptions({
// if you wanna be like that
prettierOptions: { tabWidth: 2, useTabs: true },
});
monaco.languages.graphql.api.setExternalFragmentDefinitions()
Append external fragments to be used by autocomplete and other language features.
This accepts either a string that contains fragment definitions, or TypeDefinitionNode[]
monaco.languages.graphql.api.getDiagnosticOptions
monaco.languages.graphql.api.setDiagnosticSettings({
validateVariablesJson: {
// Urls, uris, anything that monaco.URI.from() is compatible with.
// Match operation model to variables editor,
// and the language service will automatically listen for changes,
// and compute the json schema using the GraphQLWorker.
// This is in the main process is applied to the global monaco json settings
// for validation, completion and more using monaco-json's built-in JSON Schema support.
[operationModel.uri.toString()]: [variablesModel.uri.toString()],
},
jsonDiagnosticSettings: {
allowComments: true, // allow json, parse with a jsonc parser to make requests
},
});
you'll can refer to the webpack configuration in the full monaco webpack example to see how it works with webpack and the official monaco-editor-webpack-plugin
. there is probably an easier way to configure webpack worker-loader
for this.
You can configure vite to load monaco-editor
json mode and even the language editor worker using the example for our mode
the plain javascript webpack example should give you a starting point to see how to implement it with
use-monaco
seems to support the custom language worker configuration we want, and seems to be well built! we hope to help them build theiruseEffect
on didMount
to prevent breaking SSR.MonacoEnvironment.getWorkerUrl
which works better as an async import of your pre-build worker filesIf you want to pass a custom parser and/or validation rules, it is supported, however the setup is a bit more complicated.
You can add any LanguageServiceConfig
(typedoc) configuration options you like here to languageConfig
as below.
This is because we can't pass non-static configuration to the existing worker programmatically, so you must import these and build the worker custom with those functions. Part of the (worthwhile) cost of crossing runtimes!
you'll want to create your own my-graphql.worker.ts
file, and add your custom config such as schemaLoader
to createData
:
import type { worker as WorkerNamespace } from 'monaco-editor';
// @ts-ignore
import * as worker from 'monaco-editor/esm/vs/editor/editor.worker';
import { GraphQLWorker } from 'monaco-graphql/esm/GraphQLWorker';
import { myValidationRules } from './custom';
self.onmessage = () => {
try {
worker.initialize(
(
ctx: WorkerNamespace.IWorkerContext,
createData: monaco.languages.graphql.ICreateData,
) => {
createData.languageConfig.customValidationRules = myValidationRules;
return new GraphQLWorker(ctx, createData);
},
);
} catch (err) {
throw err;
}
};
then, in your application:
import EditorWorker from 'worker-loader!monaco-editor/esm/vs/editor/editor.worker';
// specify the path to your language worker
import GraphQLWorker from 'worker-loader!./my-graphql.worker';
window.MonacoEnvironment = {
getWorker(_workerId: string, label: string) {
if (label === 'graphql') {
return new GraphQLWorker();
}
return new EditorWorker();
},
};
or, if you have webpack configured for it:
window.MonacoEnvironment = {
getWorkerUrl(_workerId: string, label: string) {
if (label === 'graphql') {
return 'my-graphql.worker.js';
}
return 'editor.worker.js';
},
};
with vite you just need:
import { defineConfig } from 'vite';
import monacoEditorPlugin from 'vite-plugin-monaco-editor';
export default defineConfig({
plugins: [
monacoEditorPlugin({
customWorker: [
{
label: 'graphql',
entry: 'my-graphql.worker.js',
},
],
}),
],
});
If you are familiar with Codemirror/Atom-era terminology and features, here's some gotchas:
editor.setModelMarkers()
insertText
for field and argument completion© 2010 - cnpmjs.org x YWFE | Home | YWFE