DEV Community

Lawrence Chen
Lawrence Chen

Posted on • Updated on

Monaco Editor + Svelte Kit

Here's the gist:

<script lang="ts">
    import type monaco from 'monaco-editor';
    import { onMount } from 'svelte';
    import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
    import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
    import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
    import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
    import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';

    let divEl: HTMLDivElement = null;
    let editor: monaco.editor.IStandaloneCodeEditor;
    let Monaco;

    onMount(async () => {
        // @ts-ignore
        self.MonacoEnvironment = {
            getWorker: function (_moduleId: any, label: string) {
                if (label === 'json') {
                    return new jsonWorker();
                }
                if (label === 'css' || label === 'scss' || label === 'less') {
                    return new cssWorker();
                }
                if (label === 'html' || label === 'handlebars' || label === 'razor') {
                    return new htmlWorker();
                }
                if (label === 'typescript' || label === 'javascript') {
                    return new tsWorker();
                }
                return new editorWorker();
            }
        };

        Monaco = await import('monaco-editor');
        editor = Monaco.editor.create(divEl, {
            value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
            language: 'javascript'
        });

        return () => {
            editor.dispose();
        };
    });
</script>

<div bind:this={divEl} class="h-screen" />

Enter fullscreen mode Exit fullscreen mode

Key points

Top comments (15)

Collapse
 
dan1ve profile image
Daniel Veihelmann • Edited

As it turned out, the code snippet of the original post (and the official documentation) causes subtle bugs Firefox (e.g. the highlighting in monaco's diff editor did not work).
I think this happens because Firefox needs an explicit type: module for registering the worker, otherwise an error happens: import declarations may only appear at top level of a module

Here is the version that works in Chrome + Firefox. Maybe you want to update your post accordingly, @lawrencecchen ?

onMount(async () => {
        // @ts-ignore
        self.MonacoEnvironment = {
            getWorker: function (workerId: string, label: string) {
                const getWorkerModule = (moduleUrl: string, label: string): Worker => {
                    // @ts-ignore
                    return new Worker(self.MonacoEnvironment.getWorkerUrl(moduleUrl), {
                        name: label,
                        type: 'module'
                    });
                };

                switch (label) {
                    case 'json':
                        return getWorkerModule('/monaco-editor/esm/vs/language/json/json.worker?worker', label);
                    case 'css':
                    case 'scss':
                    case 'less':
                        return getWorkerModule('/monaco-editor/esm/vs/language/css/css.worker?worker', label);
                    case 'html':
                    case 'handlebars':
                    case 'razor':
                        return getWorkerModule('/monaco-editor/esm/vs/language/html/html.worker?worker', label);
                    case 'typescript':
                    case 'javascript':
                        return getWorkerModule(
                            '/monaco-editor/esm/vs/language/typescript/ts.worker?worker',
                            label
                        );
                    default:
                        return getWorkerModule('/monaco-editor/esm/vs/editor/editor.worker?worker', label);
                }
            }
        };

        Monaco = await import('monaco-editor');
    });
Enter fullscreen mode Exit fullscreen mode

In addition, setting let Monaco: typeof monaco; will help TypeScript to figure stuff out ;-)

Collapse
 
enoy profile image
Enis

Thanks for pointing that out @dan1ve

I get the following warning in firefox:
self.MonacoEnvironment.getWorkerUrl is not a function

Do you know the solution to this?

Collapse
 
dan1ve profile image
Daniel Veihelmann

In the meantime, I used the monaco-loader library, which does the heavy lifting for us, and works best.

Blog post coming tomorrow (I'll link it here)

Thread Thread
 
dan1ve profile image
Daniel Veihelmann • Edited

No blog post yet, but here is the gist:

<script lang="ts">
    import loader from '@monaco-editor/loader';
    import { onDestroy, onMount } from 'svelte';
    import type * as Monaco from 'monaco-editor/esm/vs/editor/editor.api';

    let editor: Monaco.editor.IStandaloneCodeEditor;
    let monaco: typeof Monaco;
    let editorContainer: HTMLElement;

    onMount(async () => {

        // Remove this line to load the monaco editor from a CDN
        // see https://www.npmjs.com/package/@monaco-editor/loader#config
        loader.config({ paths: { vs: '/node_modules/monaco-editor/min/vs' } });

        monaco = await loader.init();

       // Sample
        const editor = monaco.editor.create(editorContainer);
        const model = monaco.editor.createModel(
            "console.log('Hello from Monaco! (the editor, not the city...)')",
            undefined,
            // Give monaco a hint which syntax highlighting to use
            monaco.Uri.file('sample.js')
        );
        editor.setModel(model);
    });

    onDestroy(() => {
        monaco?.editor.getModels().forEach((model) => model.dispose());
    });
</script>
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
enoy profile image
Enis

Works perfectly for both Firefox and Chrome @dan1ve
Thank you very much! Looking forward to read your blog article!

Thread Thread
 
dan1ve profile image
Daniel Veihelmann • Edited

You're welcome, but please be aware that this still loads the editor from a CDN if I remember correctly. (Meaning the comment is wrong). I've been fighting with this for a while, maybe tomorrow the post is finally ready.

If you are fine with a CDN, the loader library is a good solution.

Thread Thread
 
enoy profile image
Enis • Edited

I'd be fine with a CDN, but I get a 404 after building and deploying my app:
GET - 404 - https://my-domain.com/node_modules/monaco-editor/min/vs/loader.js

Seems like it doesn't use a CDN.

Thread Thread
 
dan1ve profile image
Daniel Veihelmann

Did you remove the loader.config() line? (You should :)

Thread Thread
 
dan1ve profile image
Daniel Veihelmann

A blog post with a full how-to is here: codelantis.com/blog/sveltekit-mona...

Collapse
 
samfilip profile image
Sam Filip

I've been struggling with monaco and svelte in electron. What does the ?worker at the end of the import statement do? It's required for me to build the app buit throws and Uncaught TypeError: Relative references must start with either "/", "./", or "../". in the console and doesn't run. Thanks for any additional insight.

Collapse
 
dan1ve profile image
Daniel Veihelmann

Nice, thanks man! I can confirm this works for me :)

Collapse
 
donpedro profile image
donpedro

This is great, thanks!

I had to add a min-height to the div so it wouldn't have a zero-height:

<div bind:this={divEl} class="h-screen" style="min-height: 300px;" />
Enter fullscreen mode Exit fullscreen mode
Collapse
 
donpedro profile image
donpedro

I just added an answer on StackOverflow which shows how to create a SvelteKit project from scratch around this code.

Collapse
 
alexvdvalk profile image
Alex van der Valk

I wrap my on onMount function with:

if (browser) {
onMount...
}

It usually helps with compile errors.

Is there any way to optimize this further? The compiled bundle is over 2MB.

Collapse
 
wtho profile image
wtho

Does not work for me :(
On npm run build && npm run preview I get the error:
self is not defined