A browser based code editor that powers Visual Studio Code
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Web worker configuration, cross-domain setup, and environment configuration for optimal Monaco Editor performance.
Monaco Editor uses web workers to run language services outside the main thread for better performance.
self.MonacoEnvironment: IMonacoEnvironmentGlobal configuration object for Monaco Editor environment.
interface IMonacoEnvironment {
getWorkerUrl?(moduleId: string, label: string): string;
getWorker?(moduleId: string, label: string): Worker;
globalAPI?: boolean;
}// 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';
}
};// 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';
}
}When Monaco Editor and its workers are served from different domains, additional configuration is required.
// 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';
}
};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.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'
]
})
]
};// 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.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
external: ['monaco-editor']
}
},
worker: {
format: 'es'
},
optimizeDeps: {
include: ['monaco-editor']
}
});// 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();
}
};// 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)
};// 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.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))
);
}
});// 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 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();