CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-monaco-editor

A browser based code editor that powers Visual Studio Code

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

workers-and-environment.mddocs/

Workers and Environment

Web worker configuration, cross-domain setup, and environment configuration for optimal Monaco Editor performance.

Monaco Environment

Monaco Editor uses web workers to run language services outside the main thread for better performance.

Environment Configuration

self.MonacoEnvironment: IMonacoEnvironment

Global configuration object for Monaco Editor environment.

interface IMonacoEnvironment {
    getWorkerUrl?(moduleId: string, label: string): string;
    getWorker?(moduleId: string, label: string): Worker;
    globalAPI?: boolean;
}

Basic Worker URL Configuration

// Configure worker URLs
self.MonacoEnvironment = {
    getWorkerUrl: function (moduleId, label) {
        if (label === 'json') {
            return './json.worker.bundle.js';
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return './css.worker.bundle.js';
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return './html.worker.bundle.js';
        }
        if (label === 'typescript' || label === 'javascript') {
            return './ts.worker.bundle.js';
        }
        return './editor.worker.bundle.js';
    }
};

Advanced Worker Configuration

// Custom worker creation with additional configuration
self.MonacoEnvironment = {
    getWorker: function(moduleId, label) {
        const workerUrl = getWorkerUrl(moduleId, label);
        
        // Create worker with custom options
        const worker = new Worker(workerUrl, {
            name: `monaco-${label}-worker`,
            type: 'module' // for ES modules
        });
        
        // Add error handling
        worker.addEventListener('error', (error) => {
            console.error(`Monaco worker error (${label}):`, error);
        });
        
        return worker;
    }
};

function getWorkerUrl(moduleId: string, label: string): string {
    const baseUrl = '/static/monaco-workers/';
    
    switch (label) {
        case 'json':
            return baseUrl + 'json.worker.js';
        case 'css':
        case 'scss':
        case 'less':
            return baseUrl + 'css.worker.js';
        case 'html':
        case 'handlebars':
        case 'razor':
            return baseUrl + 'html.worker.js';
        case 'typescript':
        case 'javascript':
            return baseUrl + 'ts.worker.js';
        default:
            return baseUrl + 'editor.worker.js';
    }
}

Cross-Domain Configuration

When Monaco Editor and its workers are served from different domains, additional configuration is required.

Cross-Origin Worker Setup

// Cross-domain worker configuration
self.MonacoEnvironment = {
    getWorkerUrl: function (moduleId, label) {
        // Use a different domain for workers
        const workerDomain = 'https://cdn.example.com/monaco-workers/';
        
        if (label === 'json') {
            return workerDomain + 'json.worker.js';
        }
        if (label === 'css' || label === 'scss' || label === 'less') {
            return workerDomain + 'css.worker.js';
        }
        if (label === 'html' || label === 'handlebars' || label === 'razor') {
            return workerDomain + 'html.worker.js';
        }
        if (label === 'typescript' || label === 'javascript') {
            return workerDomain + 'ts.worker.js';
        }
        return workerDomain + 'editor.worker.js';
    }
};

Blob URL Workers

For complex cross-domain scenarios, create workers using blob URLs:

self.MonacoEnvironment = {
    getWorker: function(moduleId, label) {
        // Create worker script as blob
        const workerScript = `
            importScripts('https://cdn.example.com/monaco-workers/${getWorkerFileName(label)}');
        `;
        
        const blob = new Blob([workerScript], { type: 'application/javascript' });
        const workerUrl = URL.createObjectURL(blob);
        
        const worker = new Worker(workerUrl);
        
        // Clean up blob URL when worker terminates
        const originalTerminate = worker.terminate;
        worker.terminate = function() {
            URL.revokeObjectURL(workerUrl);
            return originalTerminate.call(this);
        };
        
        return worker;
    }
};

function getWorkerFileName(label: string): string {
    switch (label) {
        case 'json': return 'json.worker.js';
        case 'css':
        case 'scss':
        case 'less': return 'css.worker.js';
        case 'html':
        case 'handlebars':
        case 'razor': return 'html.worker.js';
        case 'typescript':
        case 'javascript': return 'ts.worker.js';
        default: return 'editor.worker.js';
    }
}

Webpack Configuration

Monaco Editor Webpack Plugin

// webpack.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = {
    plugins: [
        new MonacoWebpackPlugin({
            // Include only specific languages
            languages: ['javascript', 'typescript', 'css', 'html', 'json'],
            
            // Include specific features
            features: [
                'coreCommands',
                'find',
                'format',
                'hover',
                'inPlaceReplace'
            ]
        })
    ]
};

Manual Webpack Configuration

// webpack.config.js without plugin
const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        globalObject: 'self' // Important for workers
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.ttf$/,
                use: ['file-loader']
            }
        ]
    },
    resolve: {
        alias: {
            'monaco-editor': path.resolve(__dirname, 'node_modules/monaco-editor/esm/vs/editor/editor.api.js')
        }
    }
};

Vite Configuration

Vite Setup with Workers

// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
    build: {
        rollupOptions: {
            external: ['monaco-editor']
        }
    },
    worker: {
        format: 'es'
    },
    optimizeDeps: {
        include: ['monaco-editor']
    }
});

Vite Worker Import

// Worker imports for Vite
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';

self.MonacoEnvironment = {
    getWorker: function (moduleId, label) {
        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();
    }
};

Performance Optimization

Worker Management

// Worker pool for better resource management
class MonacoWorkerPool {
    private workers: Map<string, Worker[]> = new Map();
    private maxWorkers: number = 4;
    
    getWorker(label: string): Worker {
        const workerPool = this.workers.get(label) || [];
        
        // Reuse existing worker if available
        if (workerPool.length > 0) {
            return workerPool.pop()!;
        }
        
        // Create new worker if under limit
        if (this.getTotalWorkers() < this.maxWorkers) {
            return this.createWorker(label);
        }
        
        // Return least recently used worker
        return this.getLRUWorker();
    }
    
    private createWorker(label: string): Worker {
        const workerUrl = this.getWorkerUrl(label);
        const worker = new Worker(workerUrl);
        
        worker.addEventListener('error', (error) => {
            console.error(`Monaco worker error (${label}):`, error);
        });
        
        return worker;
    }
    
    private getTotalWorkers(): number {
        return Array.from(this.workers.values())
            .reduce((total, workers) => total + workers.length, 0);
    }
    
    private getLRUWorker(): Worker {
        // Implementation for least recently used worker
        // This is a simplified version
        for (const workers of this.workers.values()) {
            if (workers.length > 0) {
                return workers.pop()!;
            }
        }
        throw new Error('No workers available');
    }
    
    private getWorkerUrl(label: string): string {
        // Worker URL logic
        return `/workers/${label}.worker.js`;
    }
    
    releaseWorker(label: string, worker: Worker): void {
        const workerPool = this.workers.get(label) || [];
        workerPool.push(worker);
        this.workers.set(label, workerPool);
    }
}

const workerPool = new MonacoWorkerPool();

self.MonacoEnvironment = {
    getWorker: (moduleId, label) => workerPool.getWorker(label)
};

Memory Management

// Worker cleanup and memory management
class MonacoEnvironmentManager {
    private workerRefs: WeakMap<Worker, string> = new WeakMap();
    private cleanupInterval: number;
    
    constructor() {
        // Periodic cleanup
        this.cleanupInterval = setInterval(() => {
            this.cleanupWorkers();
        }, 300000); // 5 minutes
    }
    
    createEnvironment(): IMonacoEnvironment {
        return {
            getWorker: (moduleId, label) => {
                const worker = new Worker(this.getWorkerUrl(label));
                this.workerRefs.set(worker, label);
                
                // Auto-terminate idle workers
                let idleTimer: number;
                const resetIdleTimer = () => {
                    clearTimeout(idleTimer);
                    idleTimer = setTimeout(() => {
                        worker.terminate();
                    }, 600000); // 10 minutes idle
                };
                
                worker.addEventListener('message', resetIdleTimer);
                resetIdleTimer();
                
                return worker;
            }
        };
    }
    
    private getWorkerUrl(label: string): string {
        // Worker URL implementation
        const baseUrl = '/monaco-workers/';
        const workerMap: Record<string, string> = {
            'json': 'json.worker.js',
            'css': 'css.worker.js',
            'scss': 'css.worker.js',
            'less': 'css.worker.js',
            'html': 'html.worker.js',
            'handlebars': 'html.worker.js',
            'razor': 'html.worker.js',
            'typescript': 'ts.worker.js',
            'javascript': 'ts.worker.js'
        };
        
        return baseUrl + (workerMap[label] || 'editor.worker.js');
    }
    
    private cleanupWorkers(): void {
        // Cleanup logic for terminated workers
        // This is automatically handled by WeakMap
        console.log('Performing worker cleanup...');
    }
    
    dispose(): void {
        clearInterval(this.cleanupInterval);
    }
}

const envManager = new MonacoEnvironmentManager();
self.MonacoEnvironment = envManager.createEnvironment();

Service Worker Integration

Service Worker Caching

// service-worker.js
const MONACO_CACHE = 'monaco-editor-v1';
const MONACO_URLS = [
    '/monaco-editor/min/vs/editor/editor.main.js',
    '/monaco-editor/min/vs/editor/editor.main.css',
    '/monaco-editor/min/vs/base/worker/workerMain.js',
    '/monaco-editor/min/vs/language/typescript/ts.worker.js',
    '/monaco-editor/min/vs/language/json/json.worker.js',
    '/monaco-editor/min/vs/language/css/css.worker.js',
    '/monaco-editor/min/vs/language/html/html.worker.js'
];

self.addEventListener('install', (event) => {
    event.waitUntil(
        caches.open(MONACO_CACHE)
            .then((cache) => cache.addAll(MONACO_URLS))
    );
});

self.addEventListener('fetch', (event) => {
    if (MONACO_URLS.some(url => event.request.url.includes(url))) {
        event.respondWith(
            caches.match(event.request)
                .then((response) => response || fetch(event.request))
        );
    }
});

Error Handling

Worker Error Handling

// Comprehensive error handling for workers
class MonacoErrorHandler {
    static setupWorkerErrorHandling(): void {
        self.MonacoEnvironment = {
            getWorker: function(moduleId, label) {
                const worker = new Worker(MonacoErrorHandler.getWorkerUrl(label));
                
                worker.addEventListener('error', (error) => {
                    console.error(`Monaco worker error (${label}):`, {
                        message: error.message,
                        filename: error.filename,
                        lineno: error.lineno,
                        colno: error.colno,
                        error: error.error
                    });
                    
                    // Attempt worker recovery
                    MonacoErrorHandler.handleWorkerError(label, error);
                });
                
                worker.addEventListener('messageerror', (error) => {
                    console.error(`Monaco worker message error (${label}):`, error);
                });
                
                return worker;
            }
        };
    }
    
    private static handleWorkerError(label: string, error: ErrorEvent): void {
        // Implement error recovery logic
        if (error.message.includes('NetworkError')) {
            // Handle network-related errors
            console.warn(`Network error for ${label} worker, retrying...`);
            // Implement retry logic
        } else if (error.message.includes('SecurityError')) {
            // Handle cross-origin errors
            console.error(`Security error for ${label} worker, check CORS settings`);
        } else {
            // Generic error handling
            console.error(`Unknown error for ${label} worker`);
        }
    }
    
    private static getWorkerUrl(label: string): string {
        // Fallback URLs for error recovery
        const primaryBaseUrl = '/monaco-workers/';
        const fallbackBaseUrl = 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs/';
        
        try {
            return primaryBaseUrl + MonacoErrorHandler.getWorkerFileName(label);
        } catch (error) {
            console.warn('Using fallback worker URL');
            return fallbackBaseUrl + MonacoErrorHandler.getWorkerFileName(label);
        }
    }
    
    private static getWorkerFileName(label: string): string {
        const workerFiles: Record<string, string> = {
            'json': 'language/json/json.worker.js',
            'css': 'language/css/css.worker.js',
            'scss': 'language/css/css.worker.js',
            'less': 'language/css/css.worker.js',
            'html': 'language/html/html.worker.js',
            'handlebars': 'language/html/html.worker.js',
            'razor': 'language/html/html.worker.js',
            'typescript': 'language/typescript/ts.worker.js',
            'javascript': 'language/typescript/ts.worker.js'
        };
        
        return workerFiles[label] || 'base/worker/workerMain.js';
    }
}

// Initialize error handling
MonacoErrorHandler.setupWorkerErrorHandling();

Complete Integration Example

// Complete Monaco Editor setup with optimized worker configuration
class MonacoSetup {
    static initialize(): void {
        // Configure environment
        self.MonacoEnvironment = {
            getWorkerUrl: (moduleId, label) => {
                return MonacoSetup.getOptimizedWorkerUrl(label);
            }
        };
        
        // Load Monaco Editor
        import('monaco-editor').then(monaco => {
            MonacoSetup.configureLanguages(monaco);
            MonacoSetup.createEditor(monaco);
        });
    }
    
    private static getOptimizedWorkerUrl(label: string): string {
        const baseUrl = process.env.NODE_ENV === 'development' 
            ? '/dev-workers/' 
            : '/prod-workers/';
        
        const workerMap: Record<string, string> = {
            'json': 'json.worker.js',
            'css': 'css.worker.js',
            'scss': 'css.worker.js', 
            'less': 'css.worker.js',
            'html': 'html.worker.js',
            'handlebars': 'html.worker.js',
            'razor': 'html.worker.js',
            'typescript': 'ts.worker.js',
            'javascript': 'ts.worker.js'
        };
        
        return baseUrl + (workerMap[label] || 'editor.worker.js');
    }
    
    private static configureLanguages(monaco: typeof import('monaco-editor')): void {
        // Configure TypeScript
        monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
            target: monaco.languages.typescript.ScriptTarget.ES2020,
            allowNonTsExtensions: true,
            moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
            module: monaco.languages.typescript.ModuleKind.CommonJS,
            noEmit: true
        });
        
        // Configure other languages
        monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
            validate: true,
            allowComments: true
        });
    }
    
    private static createEditor(monaco: typeof import('monaco-editor')): void {
        const editor = monaco.editor.create(document.getElementById('editor')!, {
            value: 'console.log("Hello Monaco!");',
            language: 'typescript',
            theme: 'vs-dark',
            automaticLayout: true
        });
        
        // Setup error handling
        editor.onDidChangeModelContent(() => {
            const model = editor.getModel();
            if (model) {
                const markers = monaco.editor.getModelMarkers({ resource: model.getUri() });
                if (markers.length > 0) {
                    console.log('Editor has validation errors:', markers);
                }
            }
        });
    }
}

// Initialize Monaco Editor
MonacoSetup.initialize();

docs

editor-core.md

index.md

languages-and-providers.md

models-and-uris.md

other-languages.md

typescript-language.md

workers-and-environment.md

tile.json