Embedding Monaco Editor into a Web Application
Today, if you want to include a code editing features into your saas project, you don't have many alternatives other than the Monaco editor, a browser-based version of the popular Visual Studio Code editor.
Today, if you want to include a code editing features into your saas project, you don't have many alternatives other than the Monaco editor, a browser-based version of the popular Visual Studio Code editor.
In a nutshell, the Monaco is a JavaScript library bundled and packaged from the VS Code source. It doesn't have all the features of the parent project but still stands out anything in the area, providing rich and extensive code editing capabilities.
First and the most disappointing distinction of the Monaco from its parent project is the inability to run VS Code extensions. They simply won't work. TextMate won't work there too. There is a logical explanation for that unfortunate. Original Visual Studio Code editor runs in Node.JS environment where C bindings are available for fast native text processing libraries (like oniguruma). Here is a note from the original documentation on why doesn't the editor support TextMate grammars:
In Monaco, we are constrained to a browser environment where we cannot do anything similar. We have experimented with Emscripten to compile the C library to asm.js, but performance was very poor even in Firefox (10x slower) and extremely poor in Chrome (100x slower). We can revisit this once WebAssembly gets traction in the major browsers, but we will still need to consider the browser matrix we support, i.e. if we support IE11 and only Edge will add WebAssembly support, what will the experience be in IE11, etc.
If you use webpack in your project, the most convenient way to include the library is to use Webpack plugin for the Monaco editor. Moreover, it provides a way to tailor the editor for your needs by disabling support of the selected languages and turning off some of the editor's features, like multiple cursor editing or word suggest autocomplete. Turning off unused languages and/or features will also reduce the bundle size.
If you're going to use a subset of the features, explicitly import ESM build of the library. For example, if you build a Vue application with Vue CLI, add an alias in vue.config.js (this can change in the future versions):
module.exports = {
...
chainWebpack: config => {
config.resolve.alias
.set('monaco-editor', 'monaco-editor/esm/vs/editor/editor.api.js');
}
...
}
Initiating the editor is as easy as a few lines of code:
import * as monaco from 'monaco-editor'
monaco.editor.create(document.getElementById('container'), {
value: 'console.log("Hello, world")',
language: 'javascript'
});
There is a good set of examples on the official site that will help to set up and configure editor basic features. The Monaco editor is under active development thus sometimes API documentation is a bit misleading. Reading VS Code sources and skimming through the Monaco repository issues helps a lot.
The Editor runs with a dedicated worker so I haven't noticed any performance issues or UI freezes in a normal workflow. Memory footprint and bundle size (can be as small as ~150kb) is good.
One of the things to be aware of is not to forget to free all the custom handlers registered via Monaco languages API. Methods like addAction or registerCompletionItemProvider return an object implementing disposable interface that should be called upon the end of the application/component lifecycle as well as for an editor itself (an example of a Vue component destructor):
beforeDestroy() {
...
// Call dispose() on all handlers that were registred with Monaco API
...
if (this.editor) {
this.editor.dispose();
}
...
},
One of Monaco advantages is out-of-box support for the TypeScript. We can't imagine building a reliable web application without the assistance of a static typing system. Visual Studio Code itself is built using TypeScript and you already have type definition for all the classes and interfaces of the Monaco editor API which can be found in /monaco-editor/monaco.d.ts.
Underneath our new product, SQL Studio there's a code intelligence API which is served from a remote host so we needed special handling for Monaco's intellisence features. Fortunately, the Monaco uses promises in most demanding places which helps run the editor without interruptions. For example, to enable async fetch of autocomplete items you can return a Promise in the corresponding method.
const disp = monaco.languages.registerCompletionItemProvider(LANGUAGE_ID, {
...
provideCompletionItems(model: IReadOnlyModel, position: Position) {
return new Promise((resolve, reject) => {
// Do heavy/async stuff, then call resolve()
})
}
...
}
This is a relatively new feature and wasn't well documented in the time of writing. There is also a complementary method resolveCompletionItem, which can be used to asynchronously load additional data for auto-completion popup.
Monaco's API allows a lot of customization for any aspect of an editor: hotkeys, theming, autocomplete items, text fragment highlighting, codelenses, colors, hovers, glyphs, decorations and many more.
If you're not convinced by our subjective experience with the library, there are some open source alternatives around the web:
- Ace https://ace.c9.io.
- Code Mirror https://codemirror.net
But they are not so powerful on features and don't have such support as VS Code, which rapidly becomes number one code editor in the world. If long-term support is one of your considerations about integrated code editor, the Monaco is a non-disputable favorite here.
After reading the code of an editor I was impressed by its overall high quality, brought to us by Microsoft guys from Switzerland. They are also very responsive in the Github issue queue and welcome on contributions.
So far, we are happy with features packaged in the Monaco editor and the user experience it provides within our application. If your web application has code editing features and you expect large source files to be viewed in a browser, you can't avoid using an editor library in your application. And don't even try to start with your own editor implementation until you have a lot of time to spend on very specific data structures like Rope.