DevToys-app/DevToys · error · Error

Error : Monaco Editor library isn't loaded.

Error message

Error : Monaco Editor library isn't loaded.

What it means

Thrown by MonacoEditor.create() after it has already located and disposed any pre-existing editor for the given id. The guard `typeof monaco === "undefined"` fires when the global `monaco` symbol was never defined, meaning the AMD loader (require([...]) in the static block) has not yet finished pulling in vs/editor/editor.main, or the load failed outright. Because the check sits after the destructive dispose() of the old editor, hitting it also discards the previous editor's value. Monaco is loaded asynchronously via requirejs, so any create() call that races ahead of that async load triggers this.

Source

Thrown at src/app/dev/DevToys.Blazor/Assets/javascript/monacoEditor.ts:43

    // create a new instance of Monaco Editor.
    public static create(
        id: string,
        options: monaco.editor.IStandaloneEditorConstructionOptions,
        override: monaco.editor.IEditorOverrideServices,
        dotNetObjRef: DotNet.DotNetObject): void {
        if (options == null) {
            options = {};
        }

        const oldEditor = MonacoEditor.getStandaloneCodeEditor(id, true);
        if (oldEditor != null) {
            options.value = oldEditor.getValue();
            MonacoEditor.editors.splice(MonacoEditor.editors.findIndex(item => item.id == id), 1);
            oldEditor.dispose();
        }

        if (typeof monaco === "undefined") {
            throw new Error("Error : Monaco Editor library isn't loaded.");
        }

        // Enable semantic tokens provider
        (options as any)["semanticHighlighting.enabled"] = true;

        const newEditor = monaco.editor.create(document.getElementById(id), options, override);
        MonacoEditor.editors.push({
            id: id,
            editor: newEditor,
            dotNetObjRef: dotNetObjRef,
            isDiffEditor: false
        });
    }

    // create a new instance of Diff Monaco Editor.
    public static createDiffEditor(
        id: string,
        options: monaco.editor.IStandaloneDiffEditorConstructionOptions,

View on GitHub (pinned to 7e12df8448)

Solutions

  1. Verify the monaco assets exist at the served path: open _content/DevToys.Blazor/wwwroot/lib/monaco-editor/min/vs/loader.js in the browser and confirm 200 OK.
  2. Make create() wait on the loader: convert require([...]) into a Promise and `await` it inside create()/createDiffEditor() before the typeof check.
  3. Confirm require.config paths.vs matches the actual folder under wwwroot (it currently points at .../lib/monaco-editor/min/vs).
  4. Check the browser console for AMD load errors and relax CSP (script-src) for the monaco vs path if it is being blocked.
  5. As a safety net, call create() from OnAfterRenderAsync only after a small JS readiness check that resolves when `typeof monaco !== "undefined"`.

Example fix

// before
static {
    require.config({ paths: { "vs": "_content/DevToys.Blazor/wwwroot/lib/monaco-editor/min/vs" } });
    require(["vs/editor/editor.main"]); // fire-and-forget, async
}
// ...
if (typeof monaco === "undefined") {
    throw new Error("Error : Monaco Editor library isn't loaded.");
}

// after
private static _loaded: Promise<void> | null = null;
private static ensureLoaded(): Promise<void> {
    if (typeof monaco !== "undefined") return Promise.resolve();
    if (!MonacoEditor._loaded) {
        MonacoEditor._loaded = new Promise<void>((resolve, reject) => {
            require.config({ paths: { "vs": "_content/DevToys.Blazor/wwwroot/lib/monaco-editor/min/vs" } });
            require(["vs/editor/editor.main"], () => resolve(), (e: any) => reject(e));
        });
    }
    return MonacoEditor._loaded;
}
public static async create(id, options, override, dotNetObjRef): Promise<void> {
    await MonacoEditor.ensureLoaded();
    // ... existing body without the typeof throw
}
Defensive patterns

Strategy: validation

Validate before calling

// Run this before invoking MonacoEditor.create from .NET interop.
// Resolves true only when the global monaco object is ready.
public static isReady(): boolean {
    return typeof monaco !== "undefined" && !!monaco.editor;
}

// From Blazor OnAfterRenderAsync:
//   bool ready = await js.InvokeAsync<bool>("MonacoEditor.isReady");
//   if (!ready) { await js.InvokeVoidAsync("MonacoEditor.ensureLoaded"); }

Type guard

declare const monaco: any;
function isMonacoLoaded(): monaco is typeof monaco {
    return typeof monaco !== "undefined" && typeof monaco.editor === "object";
}

Try / catch

try {
    MonacoEditor.create(id, options, override, dotNetObjRef);
} catch (e) {
    if (e instanceof Error && /isn't loaded/.test(e.message)) {
        // monaco assets missing or still loading — await ensureLoaded() then retry once
        await MonacoEditor.ensureLoaded();
        MonacoEditor.create(id, options, override, dotNetObjRef);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling MonacoEditor.create(...) from a Blazor JS interop before the static block's `require(["vs/editor/editor.main"])` AMD callback resolves; the monaco-editor static assets under `_content/DevToys.Blazor/wwwroot/lib/monaco-editor/min/vs` being absent so require() fails silently; a Content-Security-Policy that blocks the vs/loader script; the page being offline or the dev server not serving the wwwroot assets.

Common situations: First render of a MonacoEditor Razor component on a cold start where JS init loses the race with component OnAfterRender; a publish/build that did not copy the monaco-editor lib folder; upgrading the monaco-editor npm package so the require.config path no longer matches the installed layout; running behind a strict CSP without allowing the app scheme origin.


AI-assisted analysis of DevToys-app/DevToys@7e12df8448 (2026-08-13). Data as JSON: /api/errors/e24ddd2f3d3dfc77. Report an issue: GitHub.