6

I need to create a React component that integrates Microsoft's Monaco editor and monaco-languageclient from TypeFox. The goal is for this component to be able to communicate with a language server via the language server protocol.


import React, { useEffect, useRef, useState } from 'react'
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import _ from 'lodash'


import { listen } from 'vscode-ws-jsonrpc';
import {
  CloseAction,
  createConnection,
  ErrorAction,
  MonacoLanguageClient,
  MonacoServices
} from 'monaco-languageclient';


import normalizeUrl from 'normalize-url';
import ReconnectingWebSocket from 'reconnecting-websocket';

function createLanguageClient(connection) {
  return new MonacoLanguageClient({
    name: "Sample Language Client",
    clientOptions: {
      // use a language id as a document selector
      documentSelector: [ 'json' ],
      // disable the default error handler
      errorHandler: {
        error: () => ErrorAction.Continue,
        closed: () => CloseAction.DoNotRestart
      }
    },
    // create a language client connection from the JSON RPC connection on demand
    connectionProvider: {
      get: (errorHandler, closeHandler) => {
        return Promise.resolve(createConnection(connection, errorHandler, closeHandler))
      }
    }
  });
}

function createUrl(path) {
  // const protocol = 'ws';

  return normalizeUrl("ws://localhost:3000/sampleServer")

  // return normalizeUrl(`${protocol}://${location.host}${location.pathname}${path}`);
}

function createWebSocket(url) {
  const socketOptions = {
    maxReconnectionDelay: 10000,
    minReconnectionDelay: 1000,
    reconnectionDelayGrowFactor: 1.3,
    connectionTimeout: 10000,
    maxRetries: Infinity,
    debug: false
  };
  return new ReconnectingWebSocket(url, undefined, socketOptions);
}


const ReactMonacoEditor = ({ initialText, ...props }) => {
  let localRef = useRef(null)

  const [ value, setValue ] = useState(initialText)

  useEffect(() => {
    monaco.languages.register({
      id: 'json',
      extensions: [ '.json', '.bowerrc', '.jshintrc', '.jscsrc', '.eslintrc', '.babelrc' ],
      aliases: [ 'JSON', 'json' ],
      mimetypes: [ 'application/json' ],
    });

    const model = monaco.editor.createModel(value, 'json', monaco.Uri.parse('inmemory://model.json'))
    const editor = monaco.editor.create(localRef.current, {
      model,
      glyphMargin: true,
      lightbulb: {
        enabled: true
      }
    });

    editor.onDidChangeModelContent(_.debounce(e => {
      setValue(editor.getValue())
    }, 100))

    MonacoServices.install(editor);

    // create the web socket
    const url = createUrl('/sampleSer ver')
    const webSocket = createWebSocket(url);

// listen when the web socket is opened
    listen({
      webSocket,
      onConnection: connection => {
        // create and start the language client
        const languageClient = createLanguageClient(connection);
        const disposable = languageClient.start();
        connection.onClose(() => disposable.dispose());
      }
    });

    return () => editor.dispose()
  }, [])

  return (
    <div ref={localRef} style={{ width: 800, height: 600 }}/>
  )
}

export default ReactMonacoEditor

package.json

{
  "name": "prima-monaco",
  "version": "0.1.0",
  "author": "sosa corp",
  "publisher": "sosacorp",
  "private": true,
  "engines": {
    "vscode": "^1.1.18"
  },
  "dependencies": {
    "lodash": "^4.17.11",
    "monaco-editor": "^0.17.0",
    "monaco-languageclient": "^0.9.0",
    "normalize-url": "^4.3.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.1",
    "reconnecting-websocket": "^4.1.10",
    "vscode-ws-jsonrpc": "^0.0.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

I am expecting the component to render. Instead I am getting

Failed to compile.

./node_modules/vscode-base-languageclient/lib/workspaceFolders.js
Module not found: Can't resolve 'vscode' in '.../node_modules/vscode-base-languageclient/lib'
Waiting for the debugger to disconnect...

Not sure how to proceed.

Dmitry Shvedov
  • 3,169
  • 4
  • 39
  • 51

3 Answers3

10

I was struggeling with the same problem for hours. Eventually I found the following remark in the changelog of the monaco-languageclient:

  • vscode-compatibility should be used as an implementation of vscode module at runtime. To adjust module resolution with webpack:

    resolve: { alias: { 'vscode': require.resolve('monaco-languageclient/lib/vscode-compatibility') } }

After adding that alias to my webpack config (in my case quasar.conf), the compilation succeeded.

So in fact the monaco-languageclient is not dependent on the vscode module as the error messages suggest, instead a compatibility-stub inside ot the package itself should be used.

sdeigm
  • 116
  • 3
  • in my case, the new version: 1.0.1, should be { alias: { 'vscode': require.resolve('monaco-languageclient/vscode-compatibility') } }, because module exports vscode-compatibility in package.json. – just like before Jun 07 '22 at 03:49
1

It looks like monaco-languageclient has a dependency issue with vscode-base-languageclient, but it's not clear how to solve it. I set up a project with a similar package.json to yours, the issue is specifically with monaco-languageclient (if you comment out that import it compiles fine) Here's the closest issue I could find: https://github.com/theia-ide/theia/issues/2589#issuecomment-414035697

It looks like you built your project with create-react-app, which doesn't have a tsconfig by default. You may be able to add one, or you could set up a new app with create-react-app your-project --typescript to set up a typescript app. You can try the skipLibCheck option he describes, hope it helps!

helloitsjoe
  • 6,264
  • 3
  • 19
  • 32
0

Add extra-webpack.config.js with following code

module.exports = {
  "resolve": {
    "alias": {
      'vscode': require.resolve('monaco-languageclient/lib/vscode-compatibility')
    }
  },
  "node": {
    "fs": "empty",
    "global": true,
    "crypto": "empty",
    "tls": "empty",
    "net": "empty",
    "process": true,
    "module": false,
    "clearImmediate": false,
    "setImmediate": true
  },
}

Edit package.json

      "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "browserTarget": "angular-monaco-languageclient:build"
          },
          "configurations": {
            "production": {
              "browserTarget": "angular-monaco-languageclient:build:production"
            }
          }
        },
  

it will remove the errors