Skip to content

Commit bc95fb4

Browse files
acaorebornix
andauthored
feat: Monaco Mode - Phase 2 - Mode & Worker (#1459)
* custom `graphqlDev` language, graphql webworker and mode * diagnostics, completion, hover, formatting * schema loading via config * new, simplified language service * example project using webpack, with netlify preview Co-authored-by: Rikki Schulte <[email protected]> Co-authored-by: Peng Lyu <[email protected]> Co-authored-by: rebornix <[email protected]>
1 parent fddc11d commit bc95fb4

File tree

34 files changed

+1677
-47
lines changed

34 files changed

+1677
-47
lines changed

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
**/flow-typed
2929
**/dist
3030
**/esm
31+
**/bundle
3132
packages/graphiql/webpack
3233
packages/graphiql/storybook
3334
packages/graphiql/lsp
35+
packages/graphiql/monaco
3436
packages/graphiql/*.html
3537
**/renderExample.js
3638
**/*.min.js

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ packages/graphiql/storybook
1515
packages/graphiql/lsp
1616
dist
1717
esm
18+
bundle
1819
cypress/screenshots
1920
node_modules/
2021
npm-debug.log
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module.exports = {
2+
sourceMaps: true,
3+
presets: [
4+
[
5+
require.resolve('@babel/preset-env'),
6+
{
7+
// corejs: { version: 3, proposals: true },
8+
// useBuiltIns: 'usage',
9+
targets: { browsers: ['last 2 chrome versions'] },
10+
bugfixes: true,
11+
},
12+
],
13+
require.resolve('@babel/preset-typescript'),
14+
],
15+
plugins: [
16+
require.resolve('@babel/plugin-syntax-dynamic-import'),
17+
require.resolve('@babel/plugin-proposal-class-properties'),
18+
[
19+
'@babel/plugin-transform-runtime',
20+
{
21+
regenerator: true,
22+
},
23+
],
24+
],
25+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "example-monaco-graphql-webpack",
3+
"version": "1.0.0-alpha.3",
4+
"private": true,
5+
"license": "MIT",
6+
"description": "A simple monaco example with webpack and typescript",
7+
"scripts": {
8+
"build": "cross-env NODE_ENV=production webpack ",
9+
"build-demo": "yarn build && yarn copy-demo",
10+
"copy-demo": "mkdirp ../../packages/graphiql/monaco && copy 'bundle/*' '../../packages/graphiql/monaco'",
11+
"start": "cross-env NODE_ENV=development webpack-dev-server"
12+
},
13+
"dependencies": {
14+
"graphql": "14.6.0",
15+
"monaco-graphql": "^2.3.4-alpha.4",
16+
"prettier": "^2.0.2"
17+
},
18+
"devDependencies": {
19+
"@babel/plugin-proposal-class-properties": "7.8.3",
20+
"@babel/plugin-syntax-dynamic-import": "7.8.3",
21+
"@babel/preset-env": "7.9.5",
22+
"@types/prettier": "^2.0.0",
23+
"babel-loader": "^8.1.0",
24+
"cross-env": "^7.0.0",
25+
"css-loader": "^3.5.1",
26+
"html-webpack-plugin": "^4.2.0",
27+
"monaco-editor": "^0.20.0",
28+
"monaco-editor-webpack-plugin": "^1.9.0",
29+
"react-dom": "^16.12.0",
30+
"style-loader": "^1.1.3",
31+
"webpack": "4.42.1",
32+
"webpack-cli": "^3.3.11",
33+
"webpack-dev-server": "^3.10.1",
34+
"worker-loader": "^2.0.0"
35+
}
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
4+
<head>
5+
<meta charset="UTF-8" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
7+
<title>Monaco Example!</title>
8+
<style>
9+
.div {
10+
margin: 0;
11+
padding: 0;
12+
}
13+
14+
.full-height {
15+
height: 100vh;
16+
}
17+
18+
.column {
19+
width: 50%;
20+
}
21+
</style>
22+
</head>
23+
24+
<body style="margin: 0; padding:0;">
25+
<div style="display: flex;">
26+
<div class="full-height column">
27+
<div id="operation" style="height:70vh;"></div>
28+
<div id="variables" style="height:30vh;"></div>
29+
</div>
30+
<div id="results" class="full-height column"></div>
31+
</div>
32+
</body>
33+
34+
</html>
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
2+
3+
import 'regenerator-runtime/runtime';
4+
import 'monaco-graphql/esm/monaco.contribution';
5+
6+
// NOTE: using loader syntax becuase Yaml worker imports editor.worker directly and that
7+
// import shouldn't go through loader syntax.
8+
// @ts-ignore
9+
import EditorWorker from 'worker-loader!monaco-editor/esm/vs/editor/editor.worker';
10+
// @ts-ignore
11+
import JSONWorker from 'worker-loader!monaco-editor/esm/vs/language/json/json.worker';
12+
// @ts-ignore
13+
import GraphQLWorker from 'worker-loader!monaco-graphql/esm/graphql.worker';
14+
15+
const SCHEMA_URL = 'https://swapi-graphql.netlify.com/.netlify/functions/index';
16+
17+
// @ts-ignore
18+
window.MonacoEnvironment = {
19+
getWorker(_workerId: string, label: string) {
20+
if (label === 'graphqlDev') {
21+
return new GraphQLWorker();
22+
}
23+
if (label === 'json') {
24+
return new JSONWorker();
25+
}
26+
return new EditorWorker();
27+
},
28+
};
29+
30+
// const schemaInput = document.createElement('input');
31+
// schemaInput.type = 'text'
32+
33+
// // @ts-ignore
34+
// schemaInput.value = SCHEMA_URL
35+
36+
// schemaInput.onchange = (e) => {
37+
// e.preventDefault()
38+
// console.log(e.target.value)
39+
// }
40+
41+
// const toolbar = document.getElementById('toolbar')
42+
// toolbar?.appendChild(schemaInput)
43+
44+
const variablesModel = monaco.editor.createModel(
45+
`{}`,
46+
'json',
47+
monaco.Uri.file('/1/variables.json'),
48+
);
49+
50+
const resultsEditor = monaco.editor.create(
51+
document.getElementById('results') as HTMLElement,
52+
{
53+
model: variablesModel,
54+
},
55+
);
56+
const variablesEditor = monaco.editor.create(
57+
document.getElementById('variables') as HTMLElement,
58+
{
59+
value: `{ }`,
60+
language: 'json',
61+
},
62+
);
63+
const model = monaco.editor.createModel(
64+
`
65+
query Example {
66+
allFilms {
67+
films {
68+
id
69+
}
70+
}
71+
}
72+
`,
73+
'graphqlDev',
74+
monaco.Uri.file('/1/operation.graphql'),
75+
);
76+
77+
const operationEditor = monaco.editor.create(
78+
document.getElementById('operation') as HTMLElement,
79+
{
80+
model,
81+
},
82+
);
83+
84+
/**
85+
* Basic Operation Exec Example
86+
*/
87+
88+
async function executeCurrentOp() {
89+
try {
90+
const operation = operationEditor.getValue();
91+
const variables = variablesEditor.getValue();
92+
const body: { variables?: string; query: string } = { query: operation };
93+
const parsedVariables = JSON.parse(variables);
94+
if (parsedVariables && Object.keys(parsedVariables).length) {
95+
body.variables = variables;
96+
}
97+
const result = await fetch(SCHEMA_URL, {
98+
method: 'POST',
99+
headers: { 'content-type': 'application/json' },
100+
body: JSON.stringify(body),
101+
});
102+
const resultText = await result.text();
103+
resultsEditor.setValue(JSON.stringify(JSON.parse(resultText), null, 2));
104+
} catch (err) {
105+
resultsEditor.setValue(err.toString());
106+
}
107+
}
108+
109+
const opAction: monaco.editor.IActionDescriptor = {
110+
id: 'graphql-run',
111+
label: 'Run Operation',
112+
contextMenuOrder: 0,
113+
contextMenuGroupId: 'graphql',
114+
keybindings: [
115+
// eslint-disable-next-line no-bitwise
116+
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
117+
],
118+
run: executeCurrentOp,
119+
};
120+
121+
operationEditor.addAction(opAction);
122+
variablesEditor.addAction(opAction);
123+
resultsEditor.addAction(opAction);
124+
125+
// add your own diagnostics? why not!
126+
// monaco.editor.setModelMarkers(
127+
// model,
128+
// 'graphql',
129+
// [{
130+
// severity: 5,
131+
// message: 'An example diagnostic error',
132+
// startColumn: 2,
133+
// startLineNumber: 4,
134+
// endLineNumber: 4,
135+
// endColumn: 0,
136+
// }],
137+
// );
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": "../../resources/tsconfig.base.cjs.json",
3+
"compilerOptions": {
4+
"rootDir": "./src",
5+
"outDir": "./dist",
6+
"composite": true,
7+
"jsx": "preserve",
8+
"baseUrl": ".",
9+
"strictPropertyInitialization": false,
10+
"types": ["node", "jest"],
11+
"typeRoots": ["../../node_modules/@types", "node_modules/@types"],
12+
"lib": ["dom"],
13+
"module": "umd"
14+
},
15+
"references": [{ "path": "../../packages/monaco-graphql" }],
16+
"include": ["src"],
17+
"exclude": ["**/__tests__/**", "**/build/**.*", "../../node_modules"]
18+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const path = require('path');
2+
const webpack = require('webpack');
3+
4+
const HtmlWebpackPlugin = require('html-webpack-plugin');
5+
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
6+
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
7+
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
8+
const isDev = process.env.NODE_ENV === 'development';
9+
10+
const relPath = (...args) => path.resolve(__dirname, ...args);
11+
const rootPath = (...args) => relPath(...args);
12+
13+
const resultConfig = {
14+
mode: process.env.NODE_ENV,
15+
entry: {
16+
app: './index.ts',
17+
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
18+
// 'json.worker': 'monaco-editor/esm/vs/language/json/json.worker.js',
19+
'graphql.worker': 'monaco-graphql/esm/graphql.worker.js',
20+
},
21+
context: rootPath('src'),
22+
output: {
23+
path: rootPath('bundle'),
24+
filename: '[name].js',
25+
globalObject: 'self',
26+
},
27+
devServer: {
28+
// bypass simple localhost CORS restrictions by setting
29+
// these to 127.0.0.1 in /etc/hosts
30+
allowedHosts: ['local.example.com', 'monaco-graphql.com'],
31+
},
32+
devtool: isDev ? 'cheap-module-eval-source-map' : 'source-map',
33+
node: {
34+
fs: 'empty',
35+
module: 'empty',
36+
},
37+
module: {
38+
rules: [
39+
// for graphql module, which uses .mjs
40+
{
41+
type: 'javascript/auto',
42+
test: /\.mjs$/,
43+
use: [],
44+
include: /node_modules/,
45+
exclude: /\.(ts|d\.ts|d\.ts\.map)$/,
46+
},
47+
// i think we need to add another rule for
48+
// codemirror-graphql esm.js files to load
49+
{
50+
test: /\.(js|jsx|ts|tsx)$/,
51+
use: [{ loader: 'babel-loader' }],
52+
exclude: /\.(d\.ts|d\.ts\.map|spec\.tsx)$/,
53+
},
54+
{
55+
test: /\.css$/,
56+
use: ['style-loader', 'css-loader'],
57+
},
58+
{
59+
test: /\.svg$/,
60+
use: [{ loader: 'svg-inline-loader' }],
61+
},
62+
{
63+
test: /\.(woff|woff2|eot|ttf|otf)$/,
64+
use: ['file-loader'],
65+
},
66+
],
67+
},
68+
plugins: [
69+
// in order to prevent async modules for CDN builds
70+
// until we can guarantee it will work with the CDN properly
71+
// and so that graphiql.min.js can retain parity
72+
new HtmlWebpackPlugin({
73+
template: relPath('src/index.html.ejs'),
74+
filename: 'index.html',
75+
}),
76+
new ForkTsCheckerWebpackPlugin({
77+
async: isDev,
78+
tsconfig: rootPath('tsconfig.json'),
79+
}),
80+
81+
new MonacoWebpackPlugin({
82+
languages: ['json'],
83+
}),
84+
],
85+
resolve: {
86+
extensions: ['.mjs', '.js', '.json', '.jsx', '.css', '.ts', '.tsx'],
87+
},
88+
};
89+
90+
if (process.env.ANALYZE) {
91+
resultConfig.plugins.push(
92+
new BundleAnalyzerPlugin({
93+
analyzerMode: 'static',
94+
openAnalyzer: false,
95+
reportFilename: rootPath('build/analyzer.html'),
96+
}),
97+
);
98+
}
99+
100+
module.exports = resultConfig;

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = {
4141
'!**/test/**',
4242
'!**/examples/**',
4343
'!**/codemirror-graphql/**',
44+
'!**/monaco-graphql/**',
4445
'!**/graphql-language-service-types/**',
4546
'!**/*.d.ts',
4647
'!**/types.ts',

packages/graphiql/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ dist/
22
esm/
33
cdn/
44
webpack/
5+
monaco/
56
cdn.html
67
analyzer.html
78
/graphiql.*js
@@ -15,4 +16,4 @@ test/pid
1516
/*.html
1617
renderGraphiql.js
1718
cypress/screenshots
18-
.awcache
19+
.awcache

0 commit comments

Comments
 (0)