Skip to content

Commit

Permalink
feat: standalone monaco API (#1575)
Browse files Browse the repository at this point in the history
- replace monaco.languages.graphql with an api export object
- combine api and defaults into one interface
- get rid of .d.ts typings and module extension which caused issues for normal .ts typings
- update documentation to reflect API changes
- add setSchema()
- use `createData.languageConfig` for LSP config
  • Loading branch information
acao authored Jun 11, 2020
1 parent 26fc538 commit 954aa3d
Show file tree
Hide file tree
Showing 20 changed files with 314 additions and 342 deletions.
1 change: 0 additions & 1 deletion examples/monaco-graphql-webpack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"private": true,
"license": "MIT",
"description": "A simple monaco example with webpack and typescript",
"typings": "src/types.d.ts",
"scripts": {
"build": "cross-env NODE_ENV=production webpack-cli",
"build-demo": "yarn build && yarn copy-demo",
Expand Down
25 changes: 11 additions & 14 deletions examples/monaco-graphql-webpack/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
/* global monaco */

import 'regenerator-runtime/runtime';
import 'monaco-graphql/esm/monaco.contribution';

// eslint-disable-next-line spaced-comment
/// <reference types='monaco-editor'/>
// eslint-disable-next-line spaced-comment
/// <reference types='monaco-graphql'/>
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';

import { api as GraphQLAPI } from 'monaco-graphql';

// NOTE: using loader syntax becuase Yaml worker imports editor.worker directly and that
// import shouldn't go through loader syntax.
Expand Down Expand Up @@ -42,7 +40,7 @@ schemaInput.onkeyup = e => {
// @ts-ignore
const val = e?.target?.value as string;
if (val && typeof val === 'string') {
monaco.languages.graphql.graphqlDefaults.setSchemaConfig({ uri: val });
GraphQLAPI.setSchemaConfig({ uri: val });
}
};

Expand Down Expand Up @@ -96,20 +94,19 @@ const operationEditor = monaco.editor.create(
{
model,
automaticLayout: true,
formatOnPaste: true,
formatOnType: true,
folding: true,
},
);

monaco.languages.graphql.graphqlDefaults.setFormattingOptions({
// TODO: get these types working properly
GraphQLAPI.setFormattingOptions({
prettierConfig: {
printWith: 120,
printWidth: 120,
},
});

monaco.languages.graphql.graphqlDefaults.setSchemaConfig({
// TODO: get these types working properly
uri: SCHEMA_URL,
});
GraphQLAPI.setSchemaConfig({ uri: SCHEMA_URL });

/**
* Basic Operation Exec Example
Expand All @@ -124,7 +121,7 @@ async function executeCurrentOp() {
if (parsedVariables && Object.keys(parsedVariables).length) {
body.variables = variables;
}
const result = await fetch(SCHEMA_URL, {
const result = await fetch(GraphQLAPI.schemaConfig.uri || SCHEMA_URL, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
Expand Down
4 changes: 0 additions & 4 deletions examples/monaco-graphql-webpack/src/types.d.ts

This file was deleted.

7 changes: 1 addition & 6 deletions examples/monaco-graphql-webpack/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ const rootPath = (...args) => relPath(...args);

const resultConfig = {
mode: process.env.NODE_ENV,
entry: {
app: './index.ts',
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
// 'json.worker': 'monaco-editor/esm/vs/language/json/json.worker.js',
'graphql.worker': 'monaco-graphql/esm/graphql.worker.js',
},
entry: './index.ts',
context: rootPath('src'),
output: {
path: rootPath('bundle'),
Expand Down
2 changes: 1 addition & 1 deletion packages/graphql-language-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
],
"main": "dist/index.js",
"module": "esm/index.js",
"typings": "dist/index.d.ts",
"types": "dist/index.d.ts",
"bin": {
"graphql": "./dist/temp-bin.js"
},
Expand Down
33 changes: 30 additions & 3 deletions packages/graphql-language-service/src/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
getHoverInformation,
} from 'graphql-language-service-interface';

import { RawSchema } from './types';

import {
defaultSchemaLoader,
SchemaConfig,
Expand All @@ -23,6 +25,8 @@ export type GraphQLLanguageConfig = {
parser?: typeof parse;
schemaLoader?: typeof defaultSchemaLoader;
schemaBuilder?: typeof defaultSchemaBuilder;
rawSchema?: RawSchema;
parseOptions?: ParseOptions;
schemaConfig: SchemaConfig;
};

Expand All @@ -35,12 +39,15 @@ export class LanguageService {
schemaConfig: SchemaConfig,
) => Promise<SchemaResponse | void> = defaultSchemaLoader;
private _schemaBuilder = defaultSchemaBuilder;

private _rawSchema: RawSchema | null = null;
private _parseOptions: ParseOptions | undefined = undefined;
constructor({
parser,
schemaLoader,
schemaBuilder,
schemaConfig,
rawSchema,
parseOptions,
}: GraphQLLanguageConfig) {
this._schemaConfig = schemaConfig;
if (parser) {
Expand All @@ -52,6 +59,12 @@ export class LanguageService {
if (schemaBuilder) {
this._schemaBuilder = schemaBuilder;
}
if (rawSchema) {
this._rawSchema = rawSchema;
}
if (parseOptions) {
this._parseOptions = parseOptions;
}
}

public get schema() {
Expand All @@ -65,14 +78,28 @@ export class LanguageService {
return this.loadSchema();
}

public async getSchemaResponse() {
/**
* setSchema statically, ignoring URI
* @param schema {RawSchema}
*/
public async setSchema(schema: RawSchema): Promise<GraphQLSchema> {
this._rawSchema = schema;
return this.loadSchema();
}

public async getSchemaResponse(): Promise<SchemaResponse> {
if (this._schemaResponse) {
return this._schemaResponse;
}
return this.loadSchemaResponse();
}

public async loadSchemaResponse(): Promise<SchemaResponse> {
if (this._rawSchema) {
return typeof this._rawSchema === 'string'
? this.parse(this._rawSchema)
: this._rawSchema;
}
if (!this._schemaConfig?.uri) {
throw new Error('uri missing');
}
Expand All @@ -92,7 +119,7 @@ export class LanguageService {
}

public async parse(text: string, options?: ParseOptions) {
return this._parser(text, options);
return this._parser(text, options || this._parseOptions);
}

public getCompletion = async (
Expand Down
1 change: 1 addition & 0 deletions packages/graphql-language-service/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* LICENSE file in the root directory of this source tree.
*
*/
export * from './types';
export * from './schemaLoader';
export * from './LanguageService';
export * from 'graphql-language-service-interface';
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-language-service/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import type { IntrospectionQuery } from 'graphql';
export type RawSchema = string | IntrospectionQuery;
55 changes: 31 additions & 24 deletions packages/monaco-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ yarn add monaco-graphql
```ts
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';

import 'monaco-graphql/esm/monaco.contribution';
import { api as GraphQLAPI } from 'monaco-graphql/esm/monaco.contribution';

import EditorWorker from 'worker-loader!monaco-editor/esm/vs/editor/editor.worker';
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
Expand All @@ -45,16 +45,14 @@ monaco.editor.create(document.getElementById('someElementId'), {
language: 'graphqlDev',
});

monaco.languages.graphql.graphqlDefaults.setSchemaUri(
'https://localhost:1234/graphql',
);
GraphQLAPI.setSchemaUri('https://localhost:1234/graphql');
```

This will cover the basics, making an HTTP POST with the default `introspectionQuery()` operation. To customize the entire fetcher, see [advanced customization]() below

## Advanced Usage

### `monaco.languages.graphql.graphqlDefaults` ([typedoc](http://graphiql-test.netlify/typedoc/interfaces/monaco_graphql.monaco.languages.graphql.languageservicedefaults))
### `GraphQLAPI` ([typedoc](http://graphiql-test.netlify/typedoc/classes/monaco_graphql.languageserviceapi.html))

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.

Expand All @@ -65,46 +63,55 @@ verbs prefixes for methods are meaningful:
- `set...` means to force re-write the whole settings entry for that method
- `update...` means a shallow merge of the object/value you pass with the rest of the existing settings

#### `graphqlDefaults.updateSchemaConfig()`
#### `GraphQLAPI.updateSchemaConfig()`

set schema `uri` (required) as well as `requestOptions`, `buildSchemaConfig` and `introspectionOptions`, with a shallow merge.
invoking these will cause the webworker to reload language services

```ts
monaco.languages.graphql.graphqlDefaults.updateSchemaConfig({
uri: '',
GraphQLAPI.updateSchemaConfig({
requestOptions: {
headers: { Authorization: 'Bear Auth 2134' },
},
});
```

#### `graphqlDefaults.setSchemaConfig()`
#### `GraphQLAPI.setSchemaConfig()`

same as the above, except it overwrites the entire schema config

```ts
monaco.languages.graphql.graphqlDefaults.updateSchemaConfig({
GraphQLAPI.updateSchemaConfig({
uri: 'https://my/schema',
requestOptions: {
headers: { Authorization: 'Bear Auth 2134' },
},
});
```

#### `graphqlDefaults.setSchemaUri()`
#### `GraphQLAPI.setSchemaUri()`

You can also just change the schema uri directly!

```ts
monaco.languages.graphql.graphqlDefaults.setSchemaUri(
'https://localhost:1234/graphql',
);
GraphQLAPI.setSchemaUri('https://localhost:1234/graphql');
```

#### `GraphQLAPI.setSchema()`

With either an AST string or an `introspectionQuery` JSON, set the schema directly, bypassing the schema fetcher.
We can't use a programattic `GraphQLSchema` instance, since this needs to be used by the webworker.

```ts
GraphQLAPI.setSchema(rawSchema);
```

#### `graphqlDefaults.setModeConfiguration()`
#### `GraphQLAPI.setModeConfiguration()`

This is where you can toggle monaco language features. all are enabled by default.

```ts
monaco.languages.graphql.graphqlDefaults.setModeConfiguration({
GraphQLAPI.setModeConfiguration({
documentFormattingEdits: true;
completionItems: true;
hovers: true;
Expand All @@ -113,27 +120,25 @@ monaco.languages.graphql.graphqlDefaults.setModeConfiguration({
})
```

#### `graphqlDefaults.setFormattingOptions()`
#### `GraphQLAPI.setFormattingOptions()`

this accepts an object `{ prettierConfig: prettier.Options }`, which accepts [any prettier option](https://prettier.io/docs/en/options.html).
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.

```ts
graphqlDefaults.setFormattingOptions({
GraphQLAPI.setFormattingOptions({
// if you wanna be like that
prettierOptions: { tabWidth: 2, useTabs: true },
});
```

### `monaco.languages.graphql.api` ([typedoc](http://graphiql-test.netlify/typedoc/classes/monaco_graphql.monacographqlapi))

#### `api.getSchema()`
#### `GraphQLAPI.getSchema()`

Returns either an AST `DocumentNode` or `IntrospectionQuery` result json using default or provided `schemaLoader`

### `api.parse()`
### `GraphQLAPI.parse()`

parse graphql from string using webworkers (less render-blocking/multi-threaded CPU/etc)

Expand All @@ -147,6 +152,8 @@ more examples coming soon!

If you want to pass a custom parser and/or schema fetching module, it is supported, however the setup is a bit more complicated.

You can add any `LanguageServiceConfig` configuration options you like here to `languageConfig` as below.

This is because we can't pass non-static configuration to the existing worker programatically, so you must import these and build the worker custom with those functions. Part of the (worthwile) cost of crossing runtimes!

you'll want to create your own `mygraphql.worker.ts` file, and add your custom config such as `schemaLoader` to `createData`:
Expand All @@ -167,7 +174,7 @@ self.onmessage = () => {
ctx: WorkerNamespace.IWorkerContext,
createData: monaco.languages.graphql.ICreateData,
) => {
createData.schemaLoader = mySchemaLoader;
createData.languageConfig.schemaLoader = mySchemaLoader;
return new GraphQLWorker(ctx, createData);
},
);
Expand Down Expand Up @@ -200,7 +207,7 @@ window.MonacoEnvironment = {
If you are familiar with Codemirror/Atom-era terminology and features, here's some gotchas:

- "hinting" => "code completion" in LSP terminology
- "linting" => "diagnostics" " "
- "linting" => "diagnostics" in lsp terminology
- the default keymap is different, more vscode like
- command palette and right click context menu are important
- you can extend the standard completion/linting/etc provided. for example, `editor.setModelMarkers()`
Expand Down
2 changes: 1 addition & 1 deletion packages/monaco-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"version": "0.1.4",
"main": "dist/monaco.contribution.js",
"module": "esm/monaco.contribution.js",
"typings": "src/typings/monaco.d.ts",
"types": "dist/monaco.contribution.d.ts",
"contributors": [
{
"name": "Peng Lyu",
Expand Down
Loading

0 comments on commit 954aa3d

Please sign in to comment.