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
- 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.
- Make create() wait on the loader: convert require([...]) into a Promise and `await` it inside create()/createDiffEditor() before the typeof check.
- Confirm require.config paths.vs matches the actual folder under wwwroot (it currently points at .../lib/monaco-editor/min/vs).
- Check the browser console for AMD load errors and relax CSP (script-src) for the monaco vs path if it is being blocked.
- 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
- Expose ensureLoaded() as a Promise around require(["vs/editor/editor.main"], cb) and await it before every create/createDiffEditor.
- Confirm the published assets at _content/DevToys.Blazor/wwwroot/lib/monaco-editor/min/vs return 200 in CI smoke tests.
- Gate the MonacoEditor Razor component's OnAfterRenderAsync interop on a readiness probe rather than firing on first render.
- Pin the monaco-editor npm version and assert the require.config path matches the installed folder layout in a build check.
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.