Geek Culture
Published in

Geek Culture

How To Use Monaco Editor In VueJS: The missing guide

Monaco Port Hercule, Photo by Matthias Mullie on Unsplash

If you know what you looking for or see some code immediately check this git repo.

With the boom of eduTech platforms it’s no surprise that one day you might land a gig on a website where someone wants to teach coding. Teaching coding is an interactive process. You have to show codes, let the student write codes, codes need to be tested somewhere and all sorts of trouble.

Today I want to focus just on showing/writing code in a powerful code editor inside a browser. Isn’t it great to be able to provide vs-code like functionality on the browser with syntax highlighting, automatic indentation, even autocompletion?

Yeah, that can be done with monaco-editor. It’s an awesome thing. But I struggled with it. I struggled to integrate with Vue-js. Yes, there are many third party plugins already that makes it a breeze to use monaco-editor with Vue. But using them I couldn’t enable the functionality of auto-complete, docstring visualization, go-to definition etc. That’s why I took the hard way.

This article is the summary of skimming through SO, medium posts, and github issues for many days. I am finally able to make it work. But I have no shame confessing that, I am still confused about some parts, and please do suggest if you know/found any better way. So let’s start:

The Frontend

I’m assuming that you already have a vue js project up and running. I am using an empty vue project and just created an empty component like this:

<template>
<div></div>
</template>

<script>
export default {
name: 'Editor'
}
</script>

Next thing you need to do is, to install monaco editor and some other dependencies to work with vue js.

npm install monaco-editor monaco-editor-webpack-plugin monaco-languageclient @monaco-editor/loader

Now you need to add some configuration to make these libraries work with your vue project. If you don’t have a vue.config.js file in your project, create on at the same level where package.json exists, and put the following content in it:

const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const path = require("path");
module.exports = {
configureWebpack: {
plugins: [
new MonacoWebpackPlugin({
languages: ["python"],//configure your languages here
features: ["coreCommands", "find"],
}), // Place it here
],
},
chainWebpack: (config) => {
config.resolve.alias.set(
"vscode",
path.resolve(
"./node_modules/monaco-languageclient/lib/vscode-compatibility"
)
);
},
};

You can see I am configuring it just for python , and you should be able to do it with any supported language(s).

Now we can head over to our component and initialize an editor there.

Let’s give the div inside the template an id and set a height and width for now.

<template>
<div id="editor" style="width: 500px; height: 500px"></div>
</template>

This height and width doesn’t have to be fixed like this. You set these programmatically too. But you have to set them anyway otherwise the editor won’t be visible.

Now in the script part, you have to initialize the editor like this:

import loader from "@monaco-editor/loader";

export default {
name: "Editor",
async mounted() {

loader.init().then((monaco) => {

monaco.editor.create(document.getElementById("editor"));

});

},
};

Here, loader will do some under the hood work to make the editor work in the browser, and give you an instance of the monaco , which you need to tune as per your need.

After this step if you reload the browser you may see an editor, where you will be able to write codes. Here is a snippet of the editor with some code that I pasted in it:

Editor on browser fist look

Oh yeah! There is a nice looking editor with line numbers, and minimap on the right side too which we are used to seeing in the vs-code. But hey! Where is the syntax highlighting? And if you try to write by yourself instead of pasting, you’ll notice that automatic indentation that all editor support for python is not working either.

What we have to do is during initialization we have to mention which language the editor is dealing with, like this:

export default {
name: "Editor",
async mounted() {

loader.init().then((monaco) => {
const editorOptions = {
language: "python",
minimap: { enabled: false },
};

monaco.editor.create(document.getElementById("editor"), editorOptions);

});

},
};

Now let’s see how the editor looks:

Editor looks great with syntax highlighting!

Yess! Now there is syntax highlighting and automatic indentation also functional. I have by choice disabled the minimap to demonstrate that it is possible. Here you can find all the available options. We created an object that contains our options and we passed it to the factory during editor initialization.

But still one thing is missing. That is code suggestion, auto complete etc. The browser solely cannot do this. Now you have to have a backend language server for that.

The Backend

Somewhere in your pc, ideally just adjacent to your vue project’s folder create an empty directory called lang-server or anything meaningful. Cd to the directory and initialize an npm package with this:

npm init

This will create a packages.json and node_modules inside lang-server folder. This will help to keep the dependencies separated from the base node installation in your machine. Now install the required libraries with following command:

npm install  vscode-ws-jsonrpc vscode-languageserver ws

Now you need to install a python library called python-language-server that will actually provide the functionalities of python’s autocompletions, suggestions etc.

pip install python-language-server

I assume that you already have python installed in your machine. If you don’t you have to, but that is beyond the scope of this article.

Now you have to create a middleware that will ensure communication between your frontend and python-language-server. Create a file named py-ls-middleware.js inside the lang-server directory with the following content:

const rpc = require('vscode-ws-jsonrpc')
const server = require('vscode-ws-jsonrpc/lib/server')
const lsp = require('vscode-languageserver')

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8989 });

function launch (socket) {
const reader = new rpc.WebSocketMessageReader(socket)
const writer = new rpc.WebSocketMessageWriter(socket)
const socketConnection = server.createConnection(reader, writer, () => socket.dispose())
const serverConnection = server.createServerProcess('JSON', 'pyls')
server.forward(socketConnection, serverConnection, message => {
console.log(message)
if (rpc.isRequestMessage(message)) {
if (message.method === lsp.InitializeRequest.type.method) {
const initializeParams = message.params
initializeParams.processId = process.pid
}
}
return message
})
}

wss.on('connection', function connection(ws) {
const socket = {
send:(content)=>ws.send(content,(error)=>{
if(error){
console.log(error)
}
}),
onMessage:(cb)=>ws.on('message',cb),
onError:(cb)=>ws.on('error',cb),
onClose:(cb)=>ws.on('close',cb),
dispose:(cb)=>ws.close()
}
launch(socket)
})

It exposes a websocket connection at port 8989 and conveys messages between language client and language server.

Now only one thing left. Which is to establish communication between vue app and this middleware.

The Frontend …again

We have to now install monaco-languageclient and vscode-ws-jsonrpc in our vue app for this.

npm install monaco-languageclient vscode-ws-jsonrpc

Now do the following imports inside the script tag:

import { listen } from "vscode-ws-jsonrpc";

window.setImmediate = window.setTimeout;

import {
MonacoLanguageClient,
CloseAction,
ErrorAction,
createConnection,
MonacoServices,
} from "monaco-languageclient";

Then add the following two lines at the end of loader’s init method’s callback function:

MonacoServices.install(monaco);
this.connectToLangServer();

First line will enable language client to work with the editor instance. Second line is a method call which we have to write. We will write that inside vue’s methods.

It looks like this:

connectToLangServer: function () {
const webSocket = new WebSocket("ws://127.0.0.1:8989");

listen({
webSocket: webSocket,
onConnection: (connection) => {
var languageClient = this.createLanguageClient(connection);
var disposable = languageClient.start();

connection.onClose(function () {
return disposable.dispose();
});

connection.onError(function (error) {
console.log(error);
});
},
});
},

Here we are connecting to the socket opened by lang-server middleware, then overriding the listen method of vscode-ws-jsonrpc with required parameters. We have to write another method that was called inside connectToLangServer,which is this one:

createLanguageClient: function (connection) {

return new MonacoLanguageClient({
name: "Monaco language client",
clientOptions: {
documentSelector: ["python"],
errorHandler: {
error: () => ErrorAction.Continue,
closed: () => CloseAction.DoNotRestart,
},
},

connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(
createConnection(connection, errorHandler, closeHandler)
);
},
},
});
},

Now to run the whole system, you have to run both the middleware and the dev server. Run the middleware like this:

node py-ls-middleware.js

Now check the editor, it shows suggestions:

Also shows docstring and class signature:

Shows other attributes of a class too!

That’s all to get you started. This is just some toy code. There can be many level of hardenning and refactoring required. Like handling sudden disconnection with lang server, modulerizing vue components and much more. I have plan to experiment with other language servers too. Keep an eye on the mentioned repo bellow.

https://github.com/Ruhshan/Vue-monaco-demo

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ruhshan Ahmed Abir

Ruhshan Ahmed Abir

Started with poetry, ended up with codes. Have a university degree on Biotechnology. Works and talks about Java, Python, JS. www.buymeacoffee.com/ruhshan