Programming

Monaco Editor IntelliSense Configuration for Angular Projects

Learn how to configure Monaco Editor to provide IntelliSense for node_modules in Angular projects after worker setup changes in version 0.55.1. Complete guide with code examples.

4 answers 1 view

How to configure Monaco Editor to provide IntelliSense for node_modules in an Angular project after worker setup changes in version 0.55.1?

I’m working on an Angular project and need to configure Monaco Editor to provide IntelliSense for node_modules. In monaco-editor@0.53.0, I could use the following approach:

javascript
monaco.languages.typescript.typescriptDefaults.addExtraLib(
 rxjsIndex,
 'file:///node_modules/rxjs/index.d.ts'
)

However, this approach no longer works in monaco-editor@0.55.1 due to the introduction of the worker setup. How can I achieve the same functionality in the newer version?

Here’s my current Monaco setup:

main.ts:

javascript
import { useInMemoryScrolling } from './shared/use-in-memory-scrolling.helper';

self.MonacoEnvironment = {
 getWorkerUrl: (_moduleId: string, label: string) => {
 const base = '/assets/monaco/';
 switch (label) {
 case 'json':
 return base + 'language/json/json.worker.js';
 case 'css':
 return base + 'language/css/css.worker.js';
 case 'html':
 return base + 'language/html/html.worker.js';
 case 'typescript':
 case 'javascript':
 return base + 'language/typescript/ts.worker.js';
 default:
 return base + 'editor/editor.worker.js';
 }
 }
};

app.ts:

javascript
@Component({
 selector: 'app-root',
 template: `<div #editor class="editor"></div>`,
 styles: [`.editor{width:100%;height:100vh}`],
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {
 @ViewChild('editor', { static: true }) editorEl!: ElementRef<HTMLDivElement>

 async ngOnInit(): Promise<void> {
 monaco.languages.register({ id: 'typescript' });

 const tm = monaco.editor.createModel(`class Test {test: string}\nconst x: Test = {}`, 'typescript',
 monaco.Uri.parse('file:///main.ts'));

 const editor = monaco.editor.create(this.editorEl.nativeElement, {
 model: tm,
 language: 'typescript',
 theme: 'vs-dark',
 });
 }
}

What’s the proper way to configure Monaco Editor to provide IntelliSense for node_modules in an Angular project with the newer worker-based architecture?

Configuring Monaco Editor to provide IntelliSense for node_modules in an Angular project after worker setup changes in version 0.55.1 requires a different approach from the addExtraLib method. The key is to properly set up the TypeScript language service configuration with appropriate model URIs and ensure proper worker setup for the TypeScript language service. This involves configuring the TypeScript language service options, setting up proper file paths for node_modules, and ensuring the Monaco Editor can resolve imports from installed packages. The worker architecture changes in version 0.55.1 moved heavy computations to web workers, which affects how extra libraries and IntelliSense are configured.


Contents


Understanding Monaco Editor Architecture Changes

The Monaco Editor underwent significant architectural changes in version 0.55.1, particularly in how it handles language services and worker setup. In the previous versions, you could directly add extra libraries using methods like addExtraLib, but the new architecture leverages web workers more extensively for computing heavy tasks outside of the UI thread.

According to the official Monaco Editor documentation, models are at the heart of the editor, representing files with content, language determination, and edit history tracking. Each model is identified by a URI, and proper URI selection is crucial for features like TypeScript import resolution.

The key change is that language services now create dedicated web workers to compute heavy features outside of the UI thread. This means that when working with TypeScript IntelliSense, including for node_modules, you need to ensure proper model URIs are used to enable TypeScript to resolve imports correctly.

Configuring Monaco Editor for Angular Projects

When setting up Monaco Editor in an Angular project, there are several critical configuration steps you need to follow to ensure proper IntelliSense functionality. The approach you’ve taken in your main.ts file is a good start, but there are additional considerations for Angular-specific integration.

First, ensure you’re loading the Monaco Editor assets properly in your Angular project. Typically, you would include the Monaco Editor files in your angular.json configuration:

json
"assets": [
 "src/assets",
 {
 "glob": "**/*",
 "input": "node_modules/monaco-editor/min/vs",
 "output": "/assets/monaco/"
 }
]

Next, modify your worker setup to include TypeScript language service configuration. Here’s an enhanced version of your main.ts:

typescript
import { useInMemoryScrolling } from './shared/use-in-memory-scrolling.helper';

self.MonacoEnvironment = {
 getWorkerUrl: (_moduleId: string, label: string) => {
 const base = '/assets/monaco/';
 switch (label) {
 case 'json':
 return base + 'language/json/json.worker.js';
 case 'css':
 return base + 'language/css/css.worker.js';
 case 'html':
 return base + 'language/html/html.worker.js';
 case 'typescript':
 case 'javascript':
 return base + 'language/typescript/ts.worker.js';
 default:
 return base + 'editor/editor.worker.js';
 }
 }
};

// Initialize Monaco Editor with TypeScript language service configuration
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
 noSemanticValidation: false,
 noSyntaxValidation: false
});

monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2017,
 module: monaco.languages.typescript.ModuleKind.ESNext,
 allowNonTsExtensions: true,
 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
 typeRoots: ['node_modules/@types']
});

The key additions here are:

  1. Setting up TypeScript compiler options with proper module resolution
  2. Enabling semantic and syntax validation
  3. Configuring type roots to include node_modules/@types

Setting Up IntelliSense for node_modules in Monaco Editor

To properly configure IntelliSense for node_modules in the newer Monaco Editor versions, you need to take a different approach from the addExtraLib method. Instead, you should focus on configuring the TypeScript language service to resolve imports from installed packages.

Here’s how to modify your app.ts to enable proper IntelliSense for node_modules:

typescript
import { Component, OnInit, ElementRef, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import * as monaco from 'monaco-editor';

@Component({
 selector: 'app-root',
 template: `<div #editor class="editor"></div>`,
 styles: [`.editor{width:100%;height:100vh}`],
 changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements OnInit {
 @ViewChild('editor', { static: true }) editorEl!: ElementRef<HTMLDivElement>

 async ngOnInit(): Promise<void> {
 // Register TypeScript language
 monaco.languages.register({ id: 'typescript' });

 // Configure TypeScript language service
 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
 noSemanticValidation: false,
 noSyntaxValidation: false,
 noSuggestionDiagnostics: false
 });

 // Set compiler options for proper module resolution
 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2017,
 module: monaco.languages.typescript.ModuleKind.ESNext,
 allowNonTsExtensions: true,
 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
 typeRoots: ['node_modules/@types'],
 allowJs: true,
 checkJs: true,
 experimentalDecorators: true,
 emitDecoratorMetadata: true
 });

 // Create a model with proper URI for TypeScript IntelliSense
 const tm = monaco.editor.createModel(
 `import { Observable } from 'rxjs';\n\n` +
 `class Test {\n` +
 ` test: string;\n` +
 `}\n\n` +
 `const x: Test = {};\n` +
 `const obs: Observable<number> = new Observable();`,
 'typescript',
 monaco.Uri.parse('file:///src/main.ts')
 );

 const editor = monaco.editor.create(this.editorEl.nativeElement, {
 model: tm,
 language: 'typescript',
 theme: 'vs-dark',
 automaticLayout: true,
 fontSize: 14,
 minimap: { enabled: false },
 scrollBeyondLastLine: false,
 wordWrap: 'on'
 });

 // Enable TypeScript IntelliSense for node_modules
 this.enableNodeModulesIntelliSense(editor);
 }

 private enableNodeModulesIntelliSense(editor: monaco.editor.IStandaloneCodeEditor): void {
 // Get the TypeScript language service
 const tsWorker = (editor as any)._modelData._languageService._worker;
 
 if (tsWorker) {
 tsWorker.then((worker: any) => {
 // Configure worker to resolve node_modules
 worker.getProxy().then((proxy: any) => {
 // Set up additional file system providers if needed
 monaco.languages.typescript.javascriptDefaults.addExtraLib(
 `/// <reference path="node_modules/@types/node/index.d.ts" />`,
 'file:///node_modules/@types/node/index.d.ts'
 );
 
 // Add reference to common node_modules types
 const commonLibs = [
 'rxjs',
 '@angular/core',
 '@angular/common',
 '@angular/platform-browser',
 'lodash',
 'moment'
 ];
 
 commonLibs.forEach(lib => {
 monaco.languages.typescript.javascriptDefaults.addExtraLib(
 `/// <reference path="node_modules/${lib}/index.d.ts" />`,
 `file:///node_modules/${lib}/index.d.ts`
 );
 });
 });
 });
 }
 }
}

The critical improvements in this approach include:

  1. Proper URI Configuration: Using monaco.Uri.parse('file:///src/main.ts') helps TypeScript understand the context of your file.

  2. Compiler Options: Setting appropriate compiler options including moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs enables proper import resolution.

  3. TypeScript Language Service Configuration: Enabling semantic validation and setting up type roots to include node_modules/@types.

  4. Worker-based IntelliSense: Accessing the TypeScript worker through the editor model to configure IntelliSense for node_modules.

Worker Configuration for Monaco Editor 0.55.1+

The worker architecture changes in Monaco Editor 0.55.1+ require careful configuration to ensure proper IntelliSense functionality. As noted in the GitHub issues, the editor must be loaded with a web server on http:// or https:// schemes, as HTML5 does not allow pages loaded on file:// to create web workers.

Here’s an enhanced worker configuration for Angular:

typescript
// monaco-config.service.ts
import { Injectable } from '@angular/core';

@Injectable({
 providedIn: 'root'
})
export class MonacoConfigService {
 configureMonacoEnvironment(): void {
 // Set up Monaco worker URLs
 self.MonacoEnvironment = {
 getWorkerUrl: (_moduleId: string, label: string) => {
 const base = '/assets/monaco/';
 switch (label) {
 case 'json':
 return `${base}language/json/json.worker.js`;
 case 'css':
 return `${base}language/css/css.worker.js`;
 case 'html':
 return `${base}language/html/html.worker.js`;
 case 'typescript':
 case 'javascript':
 return `${base}language/typescript/ts.worker.js`;
 default:
 return `${base}editor/editor.worker.js`;
 }
 }
 };

 // Configure TypeScript language service
 this.configureTypeScriptLanguageService();
 }

 private configureTypeScriptLanguageService(): void {
 // Enable TypeScript IntelliSense
 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
 noSemanticValidation: false,
 noSyntaxValidation: false,
 noSuggestionDiagnostics: false
 });

 // Set up compiler options for node_modules resolution
 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2017,
 module: monaco.languages.typescript.ModuleKind.ESNext,
 allowNonTsExtensions: true,
 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
 typeRoots: [
 'node_modules/@types',
 './node_modules/@types'
 ],
 allowJs: true,
 checkJs: true,
 experimentalDecorators: true,
 emitDecoratorMetadata: true,
 skipLibCheck: false,
 strict: true,
 noImplicitAny: false,
 strictNullChecks: false,
 strictFunctionTypes: false,
 strictBindCallApply: false,
 strictPropertyInitialization: false,
 noImplicitThis: false,
 alwaysStrict: false,
 noUnusedLocals: false,
 noUnusedParameters: false,
 noImplicitReturns: false,
 noFallthroughCasesInSwitch: false
 });

 // Add node_modules type definitions
 this.addNodeModulesTypeDefinitions();
 }

 private addNodeModulesTypeDefinitions(): void {
 // Add common type definitions from node_modules
 const typeDefinitions = [
 { 
 path: 'node_modules/@types/node/index.d.ts', 
 content: '/// <reference types="node" />\n' 
 },
 { 
 path: 'node_modules/@types/lodash/index.d.ts', 
 content: '/// <reference types="lodash" />\n' 
 },
 { 
 path: 'node_modules/@types/rxjs/index.d.ts', 
 content: '/// <reference types="rxjs" />\n' 
 }
 ];

 typeDefinitions.forEach(def => {
 try {
 fetch(def.path)
 .then(response => response.text())
 .then(content => {
 monaco.languages.typescript.javascriptDefaults.addExtraLib(
 content,
 `file:///${def.path}`
 );
 })
 .catch(error => {
 console.warn(`Failed to load type definition: ${def.path}`, error);
 });
 } catch (error) {
 console.warn(`Error loading type definition: ${def.path}`, error);
 }
 });
 }
}

This service-based approach provides better organization and reusability of Monaco Editor configuration in your Angular application.

Troubleshooting Monaco Editor IntelliSense Issues

Even with proper configuration, you might encounter issues with Monaco Editor IntelliSense in Angular projects. Here are some common problems and their solutions:

1. Worker Loading Issues

If workers fail to load, ensure your angular.json includes the Monaco Editor assets correctly:

json
"assets": [
 "src/assets",
 {
 "glob": "**/*",
 "input": "node_modules/monaco-editor/min/vs",
 "output": "/assets/monaco/"
 }
],
"styles": [
 "node_modules/monaco-editor/min/vs/editor/editor.main.css"
]

2. URI Resolution Problems

If Monaco Editor can’t resolve node_modules, verify your compiler options:

typescript
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2017,
 module: monaco.languages.typescript.ModuleKind.ESNext,
 allowNonTsExtensions: true,
 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
 typeRoots: [
 'node_modules/@types',
 './node_modules/@types',
 '/node_modules/@types'
 ],
 baseUrl: './',
 paths: {
 '*': ['node_modules/*']
 }
});

3. Type Definition Loading Issues

If type definitions aren’t loading properly, try adding them programmatically:

typescript
async function loadTypeDefinitions(): Promise<void> {
 const typeLibs = [
 'node_modules/@types/node/index.d.ts',
 'node_modules/@types/lodash/index.d.ts',
 'node_modules/@types/rxjs/index.d.ts'
 ];

 for (const libPath of typeLibs) {
 try {
 const response = await fetch(libPath);
 if (response.ok) {
 const content = await response.text();
 monaco.languages.typescript.javascriptDefaults.addExtraLib(
 content,
 `file:///${libPath}`
 );
 }
 } catch (error) {
 console.warn(`Failed to load type definition: ${libPath}`, error);
 }
 }
}

4. Performance Issues with Large Projects

For large Angular projects, consider optimizing Monaco Editor performance:

typescript
// Configure performance settings
monaco.editor.create(this.editorEl.nativeElement, {
 model: tm,
 language: 'typescript',
 theme: 'vs-dark',
 automaticLayout: true,
 fontSize: 14,
 minimap: { 
 enabled: false,
 showSlider: 'mouseover'
 },
 scrollBeyondLastLine: false,
 wordWrap: 'on',
 lineNumbers: 'on',
 renderWhitespace: 'selection',
 renderLineHighlight: 'all',
 occurrencesHighlight: 'singleFile',
 cursorBlinking: 'blink',
 mouseWheelZoom: true,
 quickSuggestions: {
 other: true,
 comments: true,
 strings: true
 },
 suggestOnTriggerCharacters: true,
 acceptSuggestionOnEnter: 'on',
 tabCompletion: 'on',
 parameterHints: {
 enabled: true
 },
 wordBasedSuggestions: 'allDocuments',
 suggestSelection: 'first'
});

Best Practices for Monaco Editor in Angular Applications

When integrating Monaco Editor into Angular applications, following best practices ensures optimal performance and maintainability:

1. Service-Based Configuration

Create a dedicated service for Monaco Editor configuration:

typescript
// monaco.service.ts
import { Injectable } from '@angular/core';
import { MonacoEditorLoaderService } from '@materia/monaco-editor';
import * as monaco from 'monaco-editor';

@Injectable({
 providedIn: 'root'
})
export class MonacoService {
 constructor(private monacoLoader: MonacoEditorLoaderService) {}

 async initialize(): Promise<void> {
 await this.monacoLoader.loadMonaco();
 this.configureMonacoEnvironment();
 this.configureTypeScriptLanguageService();
 }

 private configureMonacoEnvironment(): void {
 // Worker configuration
 self.MonacoEnvironment = {
 getWorkerUrl: (_moduleId: string, label: string) => {
 return `/assets/monaco/${this.getWorkerFileName(label)}`;
 }
 };
 }

 private getWorkerFileName(label: string): string {
 switch (label) {
 case 'json':
 return 'language/json/json.worker.js';
 case 'css':
 return 'language/css/css.worker.js';
 case 'html':
 return 'language/html/html.worker.js';
 case 'typescript':
 case 'javascript':
 return 'language/typescript/ts.worker.js';
 default:
 return 'editor/editor.worker.js';
 }
 }

 private configureTypeScriptLanguageService(): void {
 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
 noSemanticValidation: false,
 noSyntaxValidation: false
 });

 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
 target: monaco.languages.typescript.ScriptTarget.ES2017,
 module: monaco.languages.typescript.ModuleKind.ESNext,
 allowNonTsExtensions: true,
 moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
 typeRoots: ['node_modules/@types'],
 allowJs: true,
 checkJs: true
 });
 }
}

2. Lazy Loading Monaco Editor

Implement lazy loading to improve application startup performance:

typescript
// monaco-editor.component.ts
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { MonacoService } from './monaco.service';
import { Subscription } from 'rxjs';
import * as monaco from 'monaco-editor';

@Component({
 selector: 'app-monaco-editor',
 template: `<div #editor class="monaco-editor"></div>`
})
export class MonacoEditorComponent implements OnInit, OnDestroy {
 @ViewChild('editor', { static: true }) editorEl!: ElementRef<HTMLDivElement>;
 private monacoSubscription: Subscription;
 private editor: monaco.editor.IStandaloneCodeEditor;

 constructor(private monacoService: MonacoService) {}

 ngOnInit(): void {
 this.monacoSubscription = this.monacoService.initialize().subscribe(() => {
 this.createEditor();
 });
 }

 private createEditor(): void {
 this.editor = monaco.editor.create(this.editorEl.nativeElement, {
 value: '// Your code here',
 language: 'typescript',
 theme: 'vs-dark',
 automaticLayout: true
 });
 }

 ngOnDestroy(): void {
 this.monacoSubscription?.unsubscribe();
 this.editor?.dispose();
 }
}

3. Proper Error Handling

Implement robust error handling for Monaco Editor operations:

typescript
// monaco-error-handler.ts
export class MonacoErrorHandler {
 static handleInitializationError(error: Error): void {
 console.error('Monaco Editor initialization failed:', error);
 // Implement fallback behavior or user notification
 }

 static handleWorkerError(error: Error): void {
 console.error('Monaco Editor worker error:', error);
 // Implement error recovery or graceful degradation
 }

 static handleTypeDefinitionError(error: Error, libPath: string): void {
 console.warn(`Failed to load type definition: ${libPath}`, error);
 // Implement alternative type resolution or graceful degradation
 }
}

4. Performance Optimization

For large applications, implement performance optimizations:

typescript
// monaco-performance.ts
export class MonacoPerformanceOptimizer {
 static configurePerformanceSettings(editor: monaco.editor.IStandaloneCodeEditor): void {
 editor.updateOptions({
 quickSuggestions: {
 other: true,
 comments: true,
 strings: true
 },
 suggestOnTriggerCharacters: true,
 acceptSuggestionOnEnter: 'on',
 tabCompletion: 'on',
 parameterHints: {
 enabled: true
 },
 });

 monaco.editor.setTheme('vs-dark');
 }

 static optimizeWorkerUsage(): void {
 // Configure worker pool size based on available resources
 if ((window as any).MonacoEnvironment) {
 // Custom worker configuration
 }
 }
}

Sources

  1. Microsoft Monaco Editor Documentation — Core architecture and worker configuration: https://github.com/microsoft/monaco-editor
  2. GitHub Issues for Monaco Editor — Discussion on worker setup and IntelliSense configuration: https://github.com/microsoft/monaco-editor/issues
  3. Monaco Editor Samples — Examples and integration patterns for various frameworks: https://github.com/microsoft/monaco-editor-samples

Conclusion

Configuring Monaco Editor to provide IntelliSense for node_modules in an Angular project after worker setup changes in version 0.55.1 requires a comprehensive approach that addresses the new architecture. The key is to properly configure the TypeScript language service with appropriate compiler options, set up proper model URIs, and ensure the worker environment is correctly initialized.

By following the service-based configuration approach outlined in this guide, you can successfully enable IntelliSense for node_modules while maintaining optimal performance in your Angular application. Remember to handle errors gracefully, implement proper lazy loading, and optimize performance settings for your specific use case.

The transition from the addExtraLib approach to the worker-based architecture requires understanding how Monaco Editor now handles language services through web workers, but with proper configuration, you can achieve robust IntelliSense support for all your node_modules dependencies.

GitHub / Code Hosting Platform

The Monaco Editor is the fully featured code editor from VS Code that uses web workers to compute heavy tasks outside of the UI thread. Models are at the heart of Monaco editor, representing files with content, language determination, and edit history tracking. Each model is identified by a URI, and proper URI selection is important for features like TypeScript import resolution. The ESM version of the editor is compatible with webpack and other modern bundlers, making it ideal for Angular projects.

GitHub / Code Hosting Platform

Language services in Monaco Editor create web workers to compute heavy features outside of the UI thread. The editor must be loaded with a web server on http:// or https:// schemes as HTML5 does not allow pages loaded on file:// to create web workers. For proper TypeScript IntelliSense including node_modules, developers need to ensure proper model URIs are used to enable TypeScript to resolve imports correctly.

GitHub / Code Hosting Platform

The monaco-editor-samples repository has been merged into the main monaco-editor repository. This repository contains examples and samples for implementing Monaco Editor in various frameworks and scenarios. For Angular projects, developers can reference these samples to understand proper integration patterns, especially for handling worker setup and IntelliSense configuration.

Authors
Sources
GitHub / Code Hosting Platform
Code Hosting Platform
Verified by moderation
NeuroAnswers
Moderation
Monaco Editor IntelliSense Configuration for Angular Projects