0
# Workers and Environment
1
2
Web worker configuration, cross-domain setup, and environment configuration for optimal Monaco Editor performance.
3
4
## Monaco Environment
5
6
Monaco Editor uses web workers to run language services outside the main thread for better performance.
7
8
### Environment Configuration
9
10
```typescript { .api }
11
self.MonacoEnvironment: IMonacoEnvironment
12
```
13
14
Global configuration object for Monaco Editor environment.
15
16
```typescript { .api }
17
interface IMonacoEnvironment {
18
getWorkerUrl?(moduleId: string, label: string): string;
19
getWorker?(moduleId: string, label: string): Worker;
20
globalAPI?: boolean;
21
}
22
```
23
24
### Basic Worker URL Configuration
25
26
```typescript
27
// Configure worker URLs
28
self.MonacoEnvironment = {
29
getWorkerUrl: function (moduleId, label) {
30
if (label === 'json') {
31
return './json.worker.bundle.js';
32
}
33
if (label === 'css' || label === 'scss' || label === 'less') {
34
return './css.worker.bundle.js';
35
}
36
if (label === 'html' || label === 'handlebars' || label === 'razor') {
37
return './html.worker.bundle.js';
38
}
39
if (label === 'typescript' || label === 'javascript') {
40
return './ts.worker.bundle.js';
41
}
42
return './editor.worker.bundle.js';
43
}
44
};
45
```
46
47
### Advanced Worker Configuration
48
49
```typescript
50
// Custom worker creation with additional configuration
51
self.MonacoEnvironment = {
52
getWorker: function(moduleId, label) {
53
const workerUrl = getWorkerUrl(moduleId, label);
54
55
// Create worker with custom options
56
const worker = new Worker(workerUrl, {
57
name: `monaco-${label}-worker`,
58
type: 'module' // for ES modules
59
});
60
61
// Add error handling
62
worker.addEventListener('error', (error) => {
63
console.error(`Monaco worker error (${label}):`, error);
64
});
65
66
return worker;
67
}
68
};
69
70
function getWorkerUrl(moduleId: string, label: string): string {
71
const baseUrl = '/static/monaco-workers/';
72
73
switch (label) {
74
case 'json':
75
return baseUrl + 'json.worker.js';
76
case 'css':
77
case 'scss':
78
case 'less':
79
return baseUrl + 'css.worker.js';
80
case 'html':
81
case 'handlebars':
82
case 'razor':
83
return baseUrl + 'html.worker.js';
84
case 'typescript':
85
case 'javascript':
86
return baseUrl + 'ts.worker.js';
87
default:
88
return baseUrl + 'editor.worker.js';
89
}
90
}
91
```
92
93
## Cross-Domain Configuration
94
95
When Monaco Editor and its workers are served from different domains, additional configuration is required.
96
97
### Cross-Origin Worker Setup
98
99
```typescript
100
// Cross-domain worker configuration
101
self.MonacoEnvironment = {
102
getWorkerUrl: function (moduleId, label) {
103
// Use a different domain for workers
104
const workerDomain = 'https://cdn.example.com/monaco-workers/';
105
106
if (label === 'json') {
107
return workerDomain + 'json.worker.js';
108
}
109
if (label === 'css' || label === 'scss' || label === 'less') {
110
return workerDomain + 'css.worker.js';
111
}
112
if (label === 'html' || label === 'handlebars' || label === 'razor') {
113
return workerDomain + 'html.worker.js';
114
}
115
if (label === 'typescript' || label === 'javascript') {
116
return workerDomain + 'ts.worker.js';
117
}
118
return workerDomain + 'editor.worker.js';
119
}
120
};
121
```
122
123
### Blob URL Workers
124
125
For complex cross-domain scenarios, create workers using blob URLs:
126
127
```typescript
128
self.MonacoEnvironment = {
129
getWorker: function(moduleId, label) {
130
// Create worker script as blob
131
const workerScript = `
132
importScripts('https://cdn.example.com/monaco-workers/${getWorkerFileName(label)}');
133
`;
134
135
const blob = new Blob([workerScript], { type: 'application/javascript' });
136
const workerUrl = URL.createObjectURL(blob);
137
138
const worker = new Worker(workerUrl);
139
140
// Clean up blob URL when worker terminates
141
const originalTerminate = worker.terminate;
142
worker.terminate = function() {
143
URL.revokeObjectURL(workerUrl);
144
return originalTerminate.call(this);
145
};
146
147
return worker;
148
}
149
};
150
151
function getWorkerFileName(label: string): string {
152
switch (label) {
153
case 'json': return 'json.worker.js';
154
case 'css':
155
case 'scss':
156
case 'less': return 'css.worker.js';
157
case 'html':
158
case 'handlebars':
159
case 'razor': return 'html.worker.js';
160
case 'typescript':
161
case 'javascript': return 'ts.worker.js';
162
default: return 'editor.worker.js';
163
}
164
}
165
```
166
167
## Webpack Configuration
168
169
### Monaco Editor Webpack Plugin
170
171
```javascript
172
// webpack.config.js
173
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
174
175
module.exports = {
176
plugins: [
177
new MonacoWebpackPlugin({
178
// Include only specific languages
179
languages: ['javascript', 'typescript', 'css', 'html', 'json'],
180
181
// Include specific features
182
features: [
183
'coreCommands',
184
'find',
185
'format',
186
'hover',
187
'inPlaceReplace'
188
]
189
})
190
]
191
};
192
```
193
194
### Manual Webpack Configuration
195
196
```javascript
197
// webpack.config.js without plugin
198
const path = require('path');
199
200
module.exports = {
201
entry: './src/index.js',
202
output: {
203
path: path.resolve(__dirname, 'dist'),
204
filename: 'bundle.js',
205
globalObject: 'self' // Important for workers
206
},
207
module: {
208
rules: [
209
{
210
test: /\.css$/,
211
use: ['style-loader', 'css-loader']
212
},
213
{
214
test: /\.ttf$/,
215
use: ['file-loader']
216
}
217
]
218
},
219
resolve: {
220
alias: {
221
'monaco-editor': path.resolve(__dirname, 'node_modules/monaco-editor/esm/vs/editor/editor.api.js')
222
}
223
}
224
};
225
```
226
227
## Vite Configuration
228
229
### Vite Setup with Workers
230
231
```typescript
232
// vite.config.ts
233
import { defineConfig } from 'vite';
234
235
export default defineConfig({
236
build: {
237
rollupOptions: {
238
external: ['monaco-editor']
239
}
240
},
241
worker: {
242
format: 'es'
243
},
244
optimizeDeps: {
245
include: ['monaco-editor']
246
}
247
});
248
```
249
250
### Vite Worker Import
251
252
```typescript
253
// Worker imports for Vite
254
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
255
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
256
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
257
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
258
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
259
260
self.MonacoEnvironment = {
261
getWorker: function (moduleId, label) {
262
if (label === 'json') {
263
return new jsonWorker();
264
}
265
if (label === 'css' || label === 'scss' || label === 'less') {
266
return new cssWorker();
267
}
268
if (label === 'html' || label === 'handlebars' || label === 'razor') {
269
return new htmlWorker();
270
}
271
if (label === 'typescript' || label === 'javascript') {
272
return new tsWorker();
273
}
274
return new editorWorker();
275
}
276
};
277
```
278
279
## Performance Optimization
280
281
### Worker Management
282
283
```typescript
284
// Worker pool for better resource management
285
class MonacoWorkerPool {
286
private workers: Map<string, Worker[]> = new Map();
287
private maxWorkers: number = 4;
288
289
getWorker(label: string): Worker {
290
const workerPool = this.workers.get(label) || [];
291
292
// Reuse existing worker if available
293
if (workerPool.length > 0) {
294
return workerPool.pop()!;
295
}
296
297
// Create new worker if under limit
298
if (this.getTotalWorkers() < this.maxWorkers) {
299
return this.createWorker(label);
300
}
301
302
// Return least recently used worker
303
return this.getLRUWorker();
304
}
305
306
private createWorker(label: string): Worker {
307
const workerUrl = this.getWorkerUrl(label);
308
const worker = new Worker(workerUrl);
309
310
worker.addEventListener('error', (error) => {
311
console.error(`Monaco worker error (${label}):`, error);
312
});
313
314
return worker;
315
}
316
317
private getTotalWorkers(): number {
318
return Array.from(this.workers.values())
319
.reduce((total, workers) => total + workers.length, 0);
320
}
321
322
private getLRUWorker(): Worker {
323
// Implementation for least recently used worker
324
// This is a simplified version
325
for (const workers of this.workers.values()) {
326
if (workers.length > 0) {
327
return workers.pop()!;
328
}
329
}
330
throw new Error('No workers available');
331
}
332
333
private getWorkerUrl(label: string): string {
334
// Worker URL logic
335
return `/workers/${label}.worker.js`;
336
}
337
338
releaseWorker(label: string, worker: Worker): void {
339
const workerPool = this.workers.get(label) || [];
340
workerPool.push(worker);
341
this.workers.set(label, workerPool);
342
}
343
}
344
345
const workerPool = new MonacoWorkerPool();
346
347
self.MonacoEnvironment = {
348
getWorker: (moduleId, label) => workerPool.getWorker(label)
349
};
350
```
351
352
### Memory Management
353
354
```typescript
355
// Worker cleanup and memory management
356
class MonacoEnvironmentManager {
357
private workerRefs: WeakMap<Worker, string> = new WeakMap();
358
private cleanupInterval: number;
359
360
constructor() {
361
// Periodic cleanup
362
this.cleanupInterval = setInterval(() => {
363
this.cleanupWorkers();
364
}, 300000); // 5 minutes
365
}
366
367
createEnvironment(): IMonacoEnvironment {
368
return {
369
getWorker: (moduleId, label) => {
370
const worker = new Worker(this.getWorkerUrl(label));
371
this.workerRefs.set(worker, label);
372
373
// Auto-terminate idle workers
374
let idleTimer: number;
375
const resetIdleTimer = () => {
376
clearTimeout(idleTimer);
377
idleTimer = setTimeout(() => {
378
worker.terminate();
379
}, 600000); // 10 minutes idle
380
};
381
382
worker.addEventListener('message', resetIdleTimer);
383
resetIdleTimer();
384
385
return worker;
386
}
387
};
388
}
389
390
private getWorkerUrl(label: string): string {
391
// Worker URL implementation
392
const baseUrl = '/monaco-workers/';
393
const workerMap: Record<string, string> = {
394
'json': 'json.worker.js',
395
'css': 'css.worker.js',
396
'scss': 'css.worker.js',
397
'less': 'css.worker.js',
398
'html': 'html.worker.js',
399
'handlebars': 'html.worker.js',
400
'razor': 'html.worker.js',
401
'typescript': 'ts.worker.js',
402
'javascript': 'ts.worker.js'
403
};
404
405
return baseUrl + (workerMap[label] || 'editor.worker.js');
406
}
407
408
private cleanupWorkers(): void {
409
// Cleanup logic for terminated workers
410
// This is automatically handled by WeakMap
411
console.log('Performing worker cleanup...');
412
}
413
414
dispose(): void {
415
clearInterval(this.cleanupInterval);
416
}
417
}
418
419
const envManager = new MonacoEnvironmentManager();
420
self.MonacoEnvironment = envManager.createEnvironment();
421
```
422
423
## Service Worker Integration
424
425
### Service Worker Caching
426
427
```javascript
428
// service-worker.js
429
const MONACO_CACHE = 'monaco-editor-v1';
430
const MONACO_URLS = [
431
'/monaco-editor/min/vs/editor/editor.main.js',
432
'/monaco-editor/min/vs/editor/editor.main.css',
433
'/monaco-editor/min/vs/base/worker/workerMain.js',
434
'/monaco-editor/min/vs/language/typescript/ts.worker.js',
435
'/monaco-editor/min/vs/language/json/json.worker.js',
436
'/monaco-editor/min/vs/language/css/css.worker.js',
437
'/monaco-editor/min/vs/language/html/html.worker.js'
438
];
439
440
self.addEventListener('install', (event) => {
441
event.waitUntil(
442
caches.open(MONACO_CACHE)
443
.then((cache) => cache.addAll(MONACO_URLS))
444
);
445
});
446
447
self.addEventListener('fetch', (event) => {
448
if (MONACO_URLS.some(url => event.request.url.includes(url))) {
449
event.respondWith(
450
caches.match(event.request)
451
.then((response) => response || fetch(event.request))
452
);
453
}
454
});
455
```
456
457
## Error Handling
458
459
### Worker Error Handling
460
461
```typescript
462
// Comprehensive error handling for workers
463
class MonacoErrorHandler {
464
static setupWorkerErrorHandling(): void {
465
self.MonacoEnvironment = {
466
getWorker: function(moduleId, label) {
467
const worker = new Worker(MonacoErrorHandler.getWorkerUrl(label));
468
469
worker.addEventListener('error', (error) => {
470
console.error(`Monaco worker error (${label}):`, {
471
message: error.message,
472
filename: error.filename,
473
lineno: error.lineno,
474
colno: error.colno,
475
error: error.error
476
});
477
478
// Attempt worker recovery
479
MonacoErrorHandler.handleWorkerError(label, error);
480
});
481
482
worker.addEventListener('messageerror', (error) => {
483
console.error(`Monaco worker message error (${label}):`, error);
484
});
485
486
return worker;
487
}
488
};
489
}
490
491
private static handleWorkerError(label: string, error: ErrorEvent): void {
492
// Implement error recovery logic
493
if (error.message.includes('NetworkError')) {
494
// Handle network-related errors
495
console.warn(`Network error for ${label} worker, retrying...`);
496
// Implement retry logic
497
} else if (error.message.includes('SecurityError')) {
498
// Handle cross-origin errors
499
console.error(`Security error for ${label} worker, check CORS settings`);
500
} else {
501
// Generic error handling
502
console.error(`Unknown error for ${label} worker`);
503
}
504
}
505
506
private static getWorkerUrl(label: string): string {
507
// Fallback URLs for error recovery
508
const primaryBaseUrl = '/monaco-workers/';
509
const fallbackBaseUrl = 'https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs/';
510
511
try {
512
return primaryBaseUrl + MonacoErrorHandler.getWorkerFileName(label);
513
} catch (error) {
514
console.warn('Using fallback worker URL');
515
return fallbackBaseUrl + MonacoErrorHandler.getWorkerFileName(label);
516
}
517
}
518
519
private static getWorkerFileName(label: string): string {
520
const workerFiles: Record<string, string> = {
521
'json': 'language/json/json.worker.js',
522
'css': 'language/css/css.worker.js',
523
'scss': 'language/css/css.worker.js',
524
'less': 'language/css/css.worker.js',
525
'html': 'language/html/html.worker.js',
526
'handlebars': 'language/html/html.worker.js',
527
'razor': 'language/html/html.worker.js',
528
'typescript': 'language/typescript/ts.worker.js',
529
'javascript': 'language/typescript/ts.worker.js'
530
};
531
532
return workerFiles[label] || 'base/worker/workerMain.js';
533
}
534
}
535
536
// Initialize error handling
537
MonacoErrorHandler.setupWorkerErrorHandling();
538
```
539
540
## Complete Integration Example
541
542
```typescript
543
// Complete Monaco Editor setup with optimized worker configuration
544
class MonacoSetup {
545
static initialize(): void {
546
// Configure environment
547
self.MonacoEnvironment = {
548
getWorkerUrl: (moduleId, label) => {
549
return MonacoSetup.getOptimizedWorkerUrl(label);
550
}
551
};
552
553
// Load Monaco Editor
554
import('monaco-editor').then(monaco => {
555
MonacoSetup.configureLanguages(monaco);
556
MonacoSetup.createEditor(monaco);
557
});
558
}
559
560
private static getOptimizedWorkerUrl(label: string): string {
561
const baseUrl = process.env.NODE_ENV === 'development'
562
? '/dev-workers/'
563
: '/prod-workers/';
564
565
const workerMap: Record<string, string> = {
566
'json': 'json.worker.js',
567
'css': 'css.worker.js',
568
'scss': 'css.worker.js',
569
'less': 'css.worker.js',
570
'html': 'html.worker.js',
571
'handlebars': 'html.worker.js',
572
'razor': 'html.worker.js',
573
'typescript': 'ts.worker.js',
574
'javascript': 'ts.worker.js'
575
};
576
577
return baseUrl + (workerMap[label] || 'editor.worker.js');
578
}
579
580
private static configureLanguages(monaco: typeof import('monaco-editor')): void {
581
// Configure TypeScript
582
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
583
target: monaco.languages.typescript.ScriptTarget.ES2020,
584
allowNonTsExtensions: true,
585
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
586
module: monaco.languages.typescript.ModuleKind.CommonJS,
587
noEmit: true
588
});
589
590
// Configure other languages
591
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
592
validate: true,
593
allowComments: true
594
});
595
}
596
597
private static createEditor(monaco: typeof import('monaco-editor')): void {
598
const editor = monaco.editor.create(document.getElementById('editor')!, {
599
value: 'console.log("Hello Monaco!");',
600
language: 'typescript',
601
theme: 'vs-dark',
602
automaticLayout: true
603
});
604
605
// Setup error handling
606
editor.onDidChangeModelContent(() => {
607
const model = editor.getModel();
608
if (model) {
609
const markers = monaco.editor.getModelMarkers({ resource: model.getUri() });
610
if (markers.length > 0) {
611
console.log('Editor has validation errors:', markers);
612
}
613
}
614
});
615
}
616
}
617
618
// Initialize Monaco Editor
619
MonacoSetup.initialize();
620
```