or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

editor-core.mdindex.mdlanguages-and-providers.mdmodels-and-uris.mdother-languages.mdtypescript-language.mdworkers-and-environment.md

workers-and-environment.mddocs/

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

```